CSS: выделение текста жирным шрифтом без изменения размера контейнера

У меня есть горизонтальное меню навигации, которое по сути представляет собой просто <ul> с элементами, расположенными рядом. Я не определяю ширину, а просто использую отступы, потому что я хочу, чтобы ширина определялась шириной пункта меню. Я выделил выделенный в данный момент элемент жирным шрифтом.

Проблема в том, что при выделении жирным шрифтом слово становится немного шире, что приводит к небольшому смещению остальных элементов влево или вправо. Есть ли хитрый способ предотвратить это? Что-то вроде указания отступу игнорировать лишнюю ширину, вызванную полужирным шрифтом? Моя первая мысль заключалась в том, чтобы просто вычесть несколько пикселей из заполнения «активного» элемента, но это количество варьируется.

Если возможно, я бы хотел избежать установки статической ширины для каждой записи, а затем центрирования, в отличие от решения для заполнения, которое у меня есть в настоящее время, чтобы упростить будущие изменения записей.


person Mala    schedule 15.04.2011    source источник
comment
почему проблема с изменением размера? Это как-то портит макет?   -  person Chaulky    schedule 16.04.2011
comment
Я думаю, что вопрос о веб-программировании лучше всего задавать в Stack Overflow. Может быть, мод перенесет это туда.   -  person Ciaran    schedule 16.04.2011
comment
Chaulky: потому что есть несколько вещей, которые я ненавижу больше с точки зрения компоновки, чем кнопки, которые вы должны нажимать, прыгать по ним   -  person Mala    schedule 17.04.2011
comment
doejo. com / blog / - одно из найденных мной решений, которое делает именно то, о чем говорили Джон и Блоуски, jscript.   -  person thebulfrog    schedule 13.07.2011


Ответы (12)


У меня была та же проблема, но я получил аналогичный эффект с небольшим компромиссом, вместо этого я использовал text-shadow.

li:hover {text-shadow:0px 0px 1px black;}

Вот рабочий пример:

body {
  font-family: segoe ui;
}

ul li {
  display: inline-block;
  border-left: 1px solid silver;
  padding: 5px
}

.textshadow :hover {
  text-shadow: 0px 0px 1px black;
}

.textshadow-alt :hover {
  text-shadow: 1px 0px 0px black;
}

.bold :hover {
  font-weight: bold;
}
<ul class="textshadow">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li><code>text-shadow: 0px 0px 1px black;</code></li>
</ul>

<ul class="textshadow-alt">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li><code>text-shadow: 1px 0px 0px black;</code></li>
</ul>

<ul class="bold">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li><code>font-weight: bold;</code></li>
</ul>

пример jsfiddle

person Thorgeir    schedule 20.02.2013
comment
Мне нравится это решение, хотя я рекомендую перевернуть его на 1px 0px 0px. Выглядит более смелым. - person M. Herold; 28.03.2013
comment
Мне нужно было решение только для css, и вот ответ. - person JCasso; 27.06.2013
comment
ты настоящий гений! простота делает это потрясающе! - person lufi; 10.01.2014
comment
Только добавление li:hover {text-shadow: 1px 0 0 black;} привело к тому, что в тексте был слишком маленький интервал между буквами. Чтобы еще раз улучшить это, мы также установили li {letter-spacing: 1px;} - person Patrick Hammer; 08.01.2016
comment
Я использовал -0.5px 0 #fff, 0.5px 0 #fff, потому что мы могли видеть небольшой промежуток между самим текстом и тенью. А также, когда текст выделен жирным шрифтом, он добавляет веса обеим сторонам. - person atorscho; 17.12.2016
comment
Хорошее решение, но тени не поддерживаются IE 8. - person ; 02.09.2017
comment
мета-обсуждение meta.stackoverflow.com/questions/364040/ - person Martin Smith; 04.03.2018
comment
@ M.Herold Да, или повторение его таким образом делает его в основном идентичным полужирному: text-shadow: 0px 0px 0px # 000, 0px 0px 0px # 000, 0px 0px 0px # 000, 0px 0px 0px # 000, 0px 0px 0px # 000, 0px 0px 0px # 000; - person Martin Zvarík; 24.11.2018
comment
Я не думаю, что это хорошее решение, жирный шрифт - это не просто более широкий шрифт, большинство шрифтов имеют специальные формы букв для жирного шрифта. - person Nikolai Mavrenkov; 09.11.2020

Лучшее рабочее решение с использованием :: after

HTML

<li title="EXAMPLE TEXT">
  EXAMPLE TEXT
</li>

CSS

li::after {
  display: block;
  content: attr(title);
  font-weight: bold;
  height: 1px;
  color: transparent;
  overflow: hidden;
  visibility: hidden;
}

Он добавляет невидимый псевдоэлемент с полужирным шрифтом ширины, полученный с помощью атрибута title.

Решение text-shadow выглядит неестественно на Mac и не использует всю красоту, которую предлагает рендеринг текста на Mac .. :)

http://jsfiddle.net/85LbG/

Кредит: https://stackoverflow.com/a/20249560/5061744

person Slava Eremenko    schedule 14.09.2015
comment
Это наиболее надежное и чистое решение, оно просто резервирует место для полужирного текста в элементе. В моем случае дополнительный псевдоэлемент вызвал изменение высоты. Добавление отрицательного margin-top в блок ::after решило эту проблему. - person Rudolf Gröhling; 20.05.2016
comment
Это отличное решение! Я использовал JS для добавления атрибута содержимого данных, содержащего текст элемента, чтобы я мог избежать изменения HTML. - person mistykristie; 08.02.2017
comment
Потрясающий. Это должен быть лучший ответ. Обратите внимание, что если элемент отличается от li, возможно, вам придется использовать display: block; на: после родительского элемента. - person Blackbam; 16.12.2017
comment
Отличное решение! Также отлично работает с :: before, в моем случае это предотвращает появление дополнительных полей внизу. - person Thomas Champion; 06.11.2018
comment
Спасибо за это решение. Сначала я использовал text-shadow, но в Safari это не работает, так как округляет десятичные значения. Поскольку я использовал 0,1 пикселя, это округлялось до 0. Ваше решение работало безупречно. Я заменил только атрибут заголовка на имя, так как не хотел, чтобы всплывающие подсказки отображались. - person Jonathan Cardoz; 20.12.2018
comment
Удивительный! Спасибо! - person Ahmed Mahmoud; 21.11.2019
comment
Это потрясающе! - person Leigh Cheri; 13.12.2019
comment
Как это подходящее решение, когда вы вынуждены использовать title, а оно даже не работает, если у вас нет атрибута title ... - person codenamezero; 17.07.2020
comment
codenamezero, вы можете просто изменить атрибут на data-title, а CSS на content: attr (data-title); - person Slava Eremenko; 29.07.2020
comment
@ workaholic_gangster911, что означает для всего, что нам нужно, добавить дополнительный атрибут для извлечения контента ... есть ли способ просто указать ему использовать родительский контент? - person codenamezero; 29.01.2021

Наиболее портативным и визуально приятным решением было бы использование _1 _. Здесь пересматриваются и показаны примеры ответа Торгейра с использованием Alexxali и мои собственные настройки:

  li:hover { text-shadow: -0.06ex 0 black, 0.06ex 0 black; }

Это помещает крошечные «тени» в черный цвет (используйте название цвета / код вашего шрифта вместо black, если необходимо) с обеих сторон каждой буквы, используя единицы, которые будут правильно масштабироваться с рендерингом шрифта.

warningПредупреждение: px значения поддерживают десятичные значения, но они не будут выглядеть так отлично, когда размер шрифта изменяется (например, пользователь масштабирует вид с помощью Ctrl + +). Вместо этого используйте относительные значения.

В этом ответе используются доли единиц ex, поскольку они масштабируются с шрифт.
В ~ большинстве настроек браузера по умолчанию *, ожидайте 1ex8px и, следовательно, 0.025ex0.1px.

Посмотреть на себя:

li             { color: #000; } /* set text color just in case */
.shadow0       { text-shadow: inherit; }
.shadow2       { text-shadow: -0.02ex 0 #000, 0.02ex 0 #000; }
.shadow4       { text-shadow: -0.04ex 0 #000, 0.04ex 0 #000; }
.shadow6       { text-shadow: -0.06ex 0 #000, 0.06ex 0 #000; }
.shadow8       { text-shadow: -0.08ex 0 #000, 0.08ex 0 #000; }
.bold          { font-weight: bold; }
.bolder        { font-weight: bolder; }
.after span        { display:inline-block; font-weight: bold; } /* workaholic… */
.after:hover span  { font-weight:normal; }
.after span::after { content: attr(title); font-weight: bold; display:block; 
                     height: 0; overflow: hidden; }
.ltrsp         { letter-spacing:0; font-weight:bold; } /* @cgTag */
li.ltrsp:hover { letter-spacing:0.0125ex; }
li:hover       { font-weight: normal!important; text-shadow: none!important; }
<li class="shadow0">MmmIii123 This line tests shadow0 (plain)</li>
<li class="shadow2">MmmIii123 This line tests shadow2 (0.02ex)</li>
<li class="shadow4">MmmIii123 This line tests shadow4 (0.04ex)</li>
<li class="shadow6">MmmIii123 This line tests shadow6 (0.06ex)</li>
<li class="shadow8">MmmIii123 This line tests shadow8 (0.08ex)</li>
<li class="after"><span title="MmmIii123 This line tests [title]"
                   >MmmIii123 This line tests [title]</span> (@workaholic…)</li>
<li class="ltrsp"  >MmmIii123 This line tests ltrsp (@cgTag)</li>
<li class="bold"   >MmmIii123 This line tests bold</li>
<li class="bolder" >MmmIii123 This line tests bolder</li>
<li class="shadow2 bold">MmmIii123 This line tests shadow2 (0.02ex) + bold</li>
<li class="shadow4 bold">MmmIii123 This line tests shadow4 (0.04ex) + bold</li>
<li class="shadow6 bold">MmmIii123 This line tests shadow6 (0.06ex) + bold</li>
<li class="shadow8 bold">MmmIii123 This line tests shadow8 (0.08ex) + bold</li>

Наведите указатель мыши на отображаемые строки, чтобы увидеть, чем они отличаются от стандартного текста.

Измените уровень масштабирования вашего браузера (Ctrl + + и Ctrl + -), чтобы увидеть, как они меняются.

Я добавил сюда два других решения для сравнения: трюк с интервалом между буквами @ cgTag, который работает не так хорошо, поскольку предполагает угадывание диапазоны ширины шрифта и @ workaholic_gangster911 :: после уловки рисования, что оставляет неудобное дополнительное пространство, поэтому жирный текст может расширяться без подталкивания соседние текстовые элементы (я помещаю атрибуцию после полужирного текста, чтобы вы могли видеть, как он не перемещается).


В будущем у нас будет больше переменных шрифтов может выполнять такие действия, как изменение степени шрифта с помощью font-variation-settings. Поддержка браузеров растет (Chrome 63+, Firefox 62+), но для этого все еще требуется больше, чем просто стандартные шрифты, и несколько существующих шрифтов поддерживают его.

Если вы встроите переменный шрифт, вы сможете использовать CSS следующим образом:

/* Grade: Increase the typeface's relative weight/density */
@supports (font-variation-settings: 'GRAD' 150) {
  li:hover { font-variation-settings: 'GRAD' 150; }
}
/* Failover for older browsers: tiny shadows at right & left of the text
 * (replace both instances of "black" with the font color) */
@supports not (font-variation-settings: 'GRAD' 150) {
  li:hover { text-shadow: -0.06ex 0 black, 0.06ex 0 black; }
}

Существует интерактивная демонстрация с ползунком для поиграйте с различными классами в Руководстве по вариативным шрифтам Mozilla. Введение в вариативные шрифты в Интернете имеет анимированный GIF, демонстрирующий переключение между высокой и нулевой оценкой:

демонстрация анимированного шрифта Amstelvar Alpha с переключением оси оценок

person Adam Katz    schedule 27.09.2017
comment
Так просто, так красиво. - person Randy Hall; 21.02.2019
comment
Это проблема с Firefox 82 (пока я печатаю) и Safari. Кажется, использование 0.5px - лучший вариант. - person codenamezero; 17.11.2020
comment
Вы можете использовать currentColor вместо black или любой конкретный цвет (хотя я не тестировал во многих браузерах) - person David Gilbertson; 07.02.2021

Я обнаружил, что большинство шрифтов имеют одинаковый размер, когда вы изменяете расстояние между буквами на 1 пиксель.

a {
   letter-spacing: 1px;
}

a:hover {
   font-weight: bold;
   letter-spacing: 0px;
}

Хотя это меняет обычный шрифт, так что каждая буква имеет дополнительный интервал пикселей. Заголовки меню настолько короткие, что это не проблема.

person Reactgular    schedule 22.05.2014
comment
это лучшее и наиболее недооцененное решение, хотя я изменил его так, чтобы оно начиналось с 0 и переходило к отрицательному межбуквенному интервалу при выделении жирным шрифтом. также вы должны настроить его для каждого шрифта и размера шрифта. Я также обновил скрипку @ Thorgeir, чтобы включить это (в качестве второго решения) jsfiddle.net/dJcPn/50/ - person robotik; 01.09.2015
comment
Современные браузеры теперь поддерживают субпиксельную точность. Таким образом, вы можете точно настроить интервал, используя дробь 0.98px или меньше. - person Reactgular; 04.08.2016
comment
@AdamKatz Я подозреваю, что рендеринг шрифтов изменился с тех пор, как был опубликован этот ответ, и это во многом зависит от того, какой шрифт вы используете. - person Reactgular; 04.11.2017

Для более актуального ответа вы можете использовать -webkit-text-stroke-width:

.element {
  font-weight: normal;
}

.element:hover {
  -webkit-text-stroke-width: 1px;
  -webkit-text-stroke-color: black;
}

Это позволяет избежать любых псевдоэлементов (что является плюсом для программ чтения с экрана) и текстовых теней (которые выглядят беспорядочно и все же могут создавать небольшой эффект «скачка») или установки любой фиксированной ширины (что может быть непрактично).

Он также позволяет вам сделать элемент более жирным, чем 1 пиксель (теоретически вы можете сделать шрифт настолько полужирным, насколько захотите, а также может быть дрянной тренировкой для создания жирной версии шрифта, у которого нет полужирного вариант, например пользовательские шрифты (edit: переменные шрифты обесценивают это предложение). Хотя этого следует избегать, поскольку из-за этого некоторые шрифты, вероятно, будут казаться царапающими и неровными)

Я определенно работает в Edge, Firefox, Chrome и Opera (на момент публикации) и в Safari (edit: @Lars Blumberg, спасибо за подтверждение). Он НЕ работает в IE11 или ниже.

Также обратите внимание, что он использует префикс -webkit, поэтому он не является стандартным, и поддержка может быть прекращена в будущем, поэтому не полагайтесь на это жирным шрифтом, действительно важно - лучше избегать этого метода, если он не является чисто эстетическим.

person Oliver    schedule 24.05.2018
comment
Также работает в Safari 13.0.5. Пока что это самое чистое решение для меня. - person Lars Blumberg; 26.05.2020
comment
Это работает в Firefox, но имейте в виду, что это может выглядеть ужасно, размыто. - person MightyPork; 20.10.2020
comment
Не лучшее решение, шрифт становится более округлым и, как упоминал @MightyPork, ... размытым. - person codenamezero; 17.11.2020

К сожалению, единственный способ избежать изменения ширины, когда текст выделен жирным шрифтом, - это определить ширину элемента списка, однако, как вы заявили, выполнение этого вручную занимает много времени и не масштабируется.

Единственное, что я могу придумать, - это использовать некоторый javascript, который вычисляет ширину вкладки до того, как она становится полужирным, а затем применяет ширину в то же время, когда требуется полужирный шрифт (либо при наведении курсора, либо при щелчке).

person ajcw    schedule 16.04.2011
comment
хм, я буду экспериментировать с этим, хотя я хочу, чтобы это выглядело, по крайней мере, разумно (т.е. выделено жирным шрифтом) для пользователей, не использующих JS. Может быть, я могу отправить его полужирным шрифтом, использовать JS, чтобы выделить его, вычислить ширину, а затем снова выделить его? Или это просто глупо? - person Mala; 17.04.2011
comment
Да, похоже, это лучший способ приспособиться к пользователям, не использующим JavaScript. - person ajcw; 18.04.2011

Используйте JavaScript, чтобы установить фиксированную ширину li на основе содержимого без жирного шрифта, затем выделите содержимое жирным шрифтом, применив стиль к тегу <a> (или добавьте диапазон, если <li> не имеет дочерних элементов).

person Dan Blows    schedule 16.04.2011
comment
Упс - извините за повторение: $ - person Dan Blows; 16.04.2011
comment
В любом случае спасибо за ответ :) - person Mala; 17.04.2011
comment
@Mala Вы нашли решение? Как ни странно, у меня похожая проблема. - person Dan Blows; 29.04.2011

Это очень старый вопрос, но я возвращаюсь к нему, потому что у меня была эта проблема в приложении, которое я разрабатываю, и я нашел здесь все необходимые ответы.

(Пропустите этот абзац для TL; DR ...) Я использую шрифт Gotham с сайта cloud.typography.com, и у меня есть кнопки, которые начинаются пустыми (с белой рамкой / текстом и прозрачный фон) и получить цвет фона при наведении курсора. Я обнаружил, что некоторые из используемых мной цветов фона плохо контрастируют с белым текстом, поэтому я хотел изменить текст на черный для этих кнопок, но - из-за визуального трюка или обычных методов сглаживания - темный текст на светлом фоне всегда кажется более светлым, чем белый текст на темном фоне. Я обнаружил, что увеличение веса темного текста с 400 до 500 обеспечивает почти такой же «визуальный» вес. Тем не менее, он увеличивал ширину кнопки на крошечную величину - доли пикселя - но этого было достаточно, чтобы кнопки казались слегка «дрожащими», от чего я хотел избавиться.

Решение:

Очевидно, что это действительно привередливая проблема, поэтому ее нужно было решить. В конечном итоге я использовал отрицательный letter-spacing для более жирного текста, поскольку cgTag рекомендовал выше, но 1px был бы излишним, поэтому я просто рассчитал именно ту ширину, которая мне нужна.

Изучив кнопку в Chrome devtools, я обнаружил, что ширина моей кнопки по умолчанию была 165,47 пикселей, а при наведении - 165,69 пикселей, разница в 0,22 пикселей. На кнопке было 9 символов, поэтому:

0.22 / 9 = 0.024444px

Преобразуя это в em-единицы, я мог бы сделать настройку независимой от размера шрифта. Моя кнопка использовала размер шрифта 16 пикселей, поэтому:

0.024444 / 16 = 0.001527em

Итак, для моего конкретного шрифта следующий CSS сохраняет кнопки точно одинаковой ширины при наведении курсора:

.btn {
  font-weight: 400;
}

.btn:hover {
  font-weight: 500;
  letter-spacing: -0.001527em;
}

Пройдя небольшое тестирование и используя приведенную выше формулу, вы сможете найти точное значение letter-spacing для вашей ситуации, и оно должно работать независимо от размера шрифта.

Одно предостережение заключается в том, что разные браузеры используют немного разные вычисления субпикселей, поэтому, если вы стремитесь к этому уровню OCD с точностью до субпикселя, вам нужно будет повторить тестирование и установить разные значения для каждого браузера. Стили CSS, ориентированные на браузер, как правило, не одобряются по уважительной причине, но я думаю, что это один из вариантов использования, когда это единственный вариант, который имеет смысл.

person dannymcgee    schedule 01.10.2018

Я настоятельно рекомендую в этом случае использовать шрифт uniwidth.

Это по-прежнему пропорциональные шрифты (в отличие от моноширинных), но они занимают один и тот же размер при разной толщине шрифта. Для сохранения постоянного размера не требуется никаких CSS, JS или хитроумных приемов. Это встроено прямо в шрифт.

Вот пример из этой замечательной статьи, в которой сравнивается пропорциональный шрифт IBM Plex Sans в uniwidth Recursive.

сравнение пропорционально шрифту с одинаковой шириной

Вы можете попробовать бесплатные шрифты uniwidth:

Есть также много платных вариантов, на которые есть ссылки в вышеупомянутой статье.

person aardvarkk    schedule 11.05.2021

Интересный вопрос. Я полагаю, вы используете float, верно?

Что ж, я не знаю какой-либо техники, которую вы можете использовать, чтобы избавиться от этого увеличения шрифта, поэтому они будут пытаться соответствовать минимальной требуемой ширине - и изменение толщины шрифта изменит это значение.

Я знаю уникальное решение, позволяющее избежать этого изменения, - это то, о чем вы сказали, что не хотите: установить фиксированные размеры на li.

person Davis Peixoto    schedule 16.04.2011

Вы можете реализовать это, например, на сайте amazon.com «Магазин по отделам» в контекстном меню. Он использует широкий div. Вы можете создать широкий div и скрыть его правую часть

person Andrus    schedule 04.04.2013

ОБНОВЛЕНИЕ: пришлось использовать тег B для заголовка, потому что в IE11 псевдокласс i: after не отображался, когда у меня была видимость: скрытый.

В моем случае я хочу выровнять (специально разработанный) флажок ввода / радио с текстом метки, где текст выделяется жирным шрифтом, когда ввод отмечен.

Предлагаемое здесь решение не сработало для меня в Chrome. Вертикальное выравнивание input и label было испорчено из-за: after psuedo class и -margins не исправили это.

Вот исправление, при котором у вас не возникает проблем с вертикальным выравниванием.

/* checkbox and radiobutton */
label
{
    position: relative;
    display: inline-block;
    padding-left: 30px;
    line-height: 28px;
}

/* reserve space of bold text so that the container-size remains the same when the label text is set to bold when checked. */
label > input + b + i
{
    font-weight: bold;
    font-style: normal;
    visibility: hidden;
}

label > input:checked + b + i
{
    visibility: visible;
}

/* set title attribute of label > b */
label > input + b:after
{
    display: block;
    content: attr(title);
    font-weight: normal;
    position: absolute;
    left: 30px;
    top: -2px;
    visibility: visible;
}

label > input:checked + b:after
{
    display: none;
}

label > input[type="radio"],
label > input[type="checkbox"]
{
    position: absolute;
    visibility: hidden;
    left: 0px;
    margin: 0px;
    top: 50%;
    transform: translateY(-50%);
}

    label > input[type="radio"] + b,
    label > input[type="checkbox"]  + b
    {
        display: block;
        position: absolute;
        left: 0px;
        margin: 0px;
        top: 50%;
        transform: translateY(-50%);
        width: 24px;
        height: 24px;
        background-color: #00a1a6;
        border-radius: 3px;
    }

    label > input[type="radio"] + b
    {
        border-radius: 50%;
    }

    label > input:checked + b:before
    {
        display: inline-block;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%) rotate(45deg);
        content: '';
        border-width: 0px 3px 3px 0px;
        border-style: solid;
        border-color: #fff;
        width: 4px;
        height: 8px;
        border-radius: 0px;
    }

    label > input[type="checkbox"]:checked + b:before
    {
        transform: translate(-50%, -60%) rotate(45deg);
    }

    label > input[type="radio"]:checked + b:before
    {
        border-width: 0px;
        border-radius: 50%;
        width: 8px;
        height: 8px;
    }
<label><input checked="checked" type="checkbox"/><b title="Male"></b><i>Male</i></label>
<label><input type="checkbox"/><b title="Female"></b><i>Female</i></label>

person Erik    schedule 29.09.2017