Текст над строкой
Простые задачи в CSS часто имеют не самые очевидные решения. С одним таким случаем мне пришлось недавно столкнуться. Задача была очень простой — добавить на сайт знак ®.
Задача
В отличие от знака торговой марки ™, знак ® не поднят над строкой. Тем не менее, общепринятая практика — располагать его таким же образом, чуть уменьшив в размере.
В HTML есть подходящий элемент — <sup>
. По умолчанию, у него есть нужные CSS-свойства: vertical-align:super
и font-size:smaller
.
<sup>®</sup>
Супер!
В идеальном мире история на этом должна закончиться. В реальности все только начинается.
По условиям задачи знак может находиться практически в любом месте сайта: в основном тексте, заголовках, в элементах интерфейса, и важный момент — внутри ссылок. Это означает, что верстка должна быть гибкой. HTML-код должен быть универсальным, а CSS-стили не должны зависеть от контекста.
Проблема
То, что тег <sup>
со стилями по умолчанию не является решением стало понятно сразу.
Наличие выравнивания vertical-align:super
приводит к увеличению высоты строки, и к скачущему интерлиньяжу. А font-size:smaller
— не самый надежный способ уменьшить размер текста относительно контекста. Реальное значение будет зависеть от контекста не напрямую, а будет вычислено с оглядкой на таблицу абсолютных значений согласно спецификации. В целом браузеры справляются, хотя и по-разному, но есть и исключения. Где-то кегль будет уменьшен недостаточно, а где-то слишком сильно. Привет, IE.
Кроме того, Webkit-браузеры разрывают стандартное подчеркивание (text-decoration: underline
), встретив vertical-align:super
внутри строки (на самом деле не поэтому, но как один из случаев). Не знаю логичное это поведение или нет, но ссылка с разорванным подчеркиванием выглядит уродливо.
Уменьшить текст, выровнять его по вертикали и оставить подчеркивание на месте — эти задачи легко решаются, по отдельности. Проблема — совместить решения друг с другом.
Нагуглить готовое решение не получилось. Неплохая попытка — https://gist.github.com/unruthless/413930, но не решена проблема с подчеркиванием. Обращу внимание на хороший комментарий со ссылкой на тест-кейсы для разных знаков.
Решение
Если подстроке задать вертикальное выравнивание не по базовой линии, изменить размер шрифта, или задать позиционирование со смещением, то подчеркивание внутри строки сломается. И это не исправить.
Имитировать text-decoration:underline
другими CSS-свойствами в общем случае невозможно. Толщина и положение линии подчеркивания зависят от шрифтовой гарнитуры, кегля, браузера, ОС и режима масштабирования.
Стандартное подчеркивание необходимо сохранить, а значит сокращается набор средств для уменьшения и позиционирования текста над строкой.
Суть решения
-
Стилизовать не элемент
<sup>
, а псевдо-элемент с таким же содержимым. -
Содержимое псевдо-элемента (свойство
content
) задать черезdata
-атрибут. Так дублирование контента будет независимым от CSS и приемлимым с точки зрения HTML-семантики. -
Добавить обертку и назначить псевдо-элемент на нее. Это нужно, чтобы спрятать оригинальное содержимое с помощью
color:transparent
, тем самым, оставив его в потоке и резервируя место под стилизованный псевдо-элемент. -
Заменить
sup
наspan
. (опционально) Объяснение ниже. -
Добавить
слезы единорогапробел нулевой длины (опционально). Объяснение ниже.
DEMO
Сравнение с эталонами — показывает ход решения.
Тестирование решения на разных кеглях.
Детали
Span vs. sup
В IE нельзя задать для sup
размер шрифта. Серьезно. IE лучше знает как нужно — тест. Плохо это тем, что ширина площадки, которая резервируется оригинальным содержимым, в IE будет меньше, чем в других браузерах. Для знака ® можно заменить <sup>
на <span>
, но это недопустимо в других случаях, когда важно сохранить семантику: цифры сносок, степень числа и т.д.
Магические числа vs. font-size:smaller
Font-size:smaller по-разному вычисляется в браузерах, поэтому трудно подобрать компенсирующее смещение для резервирующей площадки.
Магические числа зависят только от ширины гарнитуры. Для распространенных системных шрифтов эти числа колеблются около 0.75em для кегля и 0.25em для смещения.
Для меня вариант с магическими числами был проще, так как их требовалось подобрать только для одной гарнитуры.
Zero-width space. WTF?
В Webkit-браузерах найден странный баг.
Если подчеркнутому тексту задать смену цвета по наведению, и тексту, используя внутреннюю обертку, поменять цвет, то по наведению цвет подчеркивания не меняется.
Проще это увидеть. Тест.
Устранить баг получается, только добавлением текста непосредственно внутрь DOM-узла с подчеркиванием. Пробел нулевой длины подходит для этого лучше остальных символов.
Хаки для старых IE
В IE8 не работает color:transparent
, вместо него можно безопасно использовать visibility:hidden
— подчеркивание сохранится;
Для IE7 можно написать экспрешны для before и для color:inherit или упростить стили.
Кроссбраузерность
Решение кроссбраузерное. Тестировалось в:
- Internet Explorer 8-11
- Opera 12.16
- Mozilla Firefox 33 и 3.6.*
- Google Chrome 39
- Yandex Browser 14
- Safari 8
Использовались Windows, Linux и OS X (лень расписывать соответствие версиям браузеров).
Недостатки решения
-
Некрасивое выделение текста — при выделении оригинальный текст перестает быть прозрачным. Этого не видно в OS X, но заметно в Linux и Windows. Эффект можно ослабить, добавив
::selection {background:transparent}
на оригинальный контент. Он исчезнет, но при этом прервется выделение. -
В IE псевдо-элемент копируется вместе с остальным текстом. Во всех браузерах кроме IE контент псевдо-элементов не выделяется и не копируется. В IE иначе, псевдо-элемент выделяется и визуально все хорошо, но при вставке скопированного текста, в нем будут и оригинальный контент и контент псевдо-элемента.
-
Нулевой пробел копируется вместе с остальным текстом. Мне трудно оценить негативные последствия, но ясно, что если они есть, то пользователю будет сложно определить их причину.
-
В старых IE подчеркивание дублируется и поднимается вместе с текстом.
-
Для некрофилов замечу, что в IE6 пробел нулевой длины заменяется на нераспознанный символ.
Итог
Решение получилось достаточно гибким и кроссбраузерным. Трюк с псевдо-элементом не самый оригинальный, но применительно к задаче, оброс нюансами.
BTW
Во время работы над задачей родился пример использования несуществующего селектора ::last-letter. С помощью него было бы удобно убирать нижнее подчеркивание для последнего знака над строкой. Но ребята из W3C уже давно решили, что этому не бывать.
P.S.
— Если ты встаешь на путь извращений, ты должен быть готов идти до конца — говорит один фронтендер другому, глядя в код