CSS: pogrubienie tekstu bez zmiany rozmiaru jego kontenera

119

Mam poziome menu nawigacyjne, które jest w zasadzie tylko <ul>z elementami ustawionymi obok siebie. Nie definiuję szerokości, ale po prostu używam dopełnienia, ponieważ chciałbym, aby szerokości były definiowane przez szerokość pozycji menu. Pogrubię aktualnie wybrany element.

Problem w tym, że w pogrubieniu słowo staje się nieco szersze, co powoduje, że pozostałe elementy przesuwają się nieznacznie w lewo lub w prawo. Czy istnieje sprytny sposób, aby temu zapobiec? Coś w rodzaju nakazania wyściółce ignorowania dodatkowej szerokości spowodowanej pogrubieniem? Moją pierwszą myślą było po prostu odjęcie kilku pikseli od wypełnienia elementu „aktywnego”, ale ta ilość jest różna.

Jeśli to możliwe, chciałbym uniknąć ustawiania statycznej szerokości dla każdego wpisu, a następnie wyśrodkowywania, w przeciwieństwie do rozwiązania wypełniania, które mam obecnie, aby ułatwić przyszłe zmiany wpisów.

Mala
źródło
dlaczego zmiana rozmiaru jest problemem? Czy to w jakiś sposób psuje układ?
Chaulky
Myślę, że pytania o programowanie internetowe najlepiej zadawać na Stack Overflow. Może mod przeniesie to tam.
Ciaran
2
Chaulky: ponieważ jest kilka rzeczy, których nienawidzę bardziej, jeśli chodzi o układ, niż kiedy przyciski, które powinieneś próbować klikać, przeskakują
Mala
Doejo.com/blog/… to jedno rozwiązanie, które znalazłem, które robi dokładnie to, co John i Blowski rozmawiali o jscript.
thebulfrog

Odpowiedzi:

152

Miałem ten sam problem, ale uzyskałem podobny efekt przy niewielkim kompromisie, zamiast tego użyłem text-shadow.

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

Oto działający przykład:

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>

przykład jsfiddle

Thorgeir
źródło
51
Uwielbiam to rozwiązanie, chociaż polecam odwrócenie tego do 1px 0px 0px. Wygląda trochę bardziej odważnie.
M. Herold,
Potrzebowałem tylko rozwiązania CSS i to jest odpowiedź.
JCasso
jesteś prawdziwym geniuszem! prostota sprawia, że ​​jest niesamowity!
lufi,
1
Dopiero dodanie li:hover {text-shadow: 1px 0 0 black;}spowodowało, że tekst ze zbyt małymi odstępami między literami. Aby jeszcze raz to poprawić, ustawiliśmyli {letter-spacing: 1px;}
Patrick Hammer
5
Użyłem -0.5px 0 #fff, 0.5px 0 #fff, ponieważ widzieliśmy małą lukę między samym tekstem a cieniem. A także pogrubiony tekst dodaje wagi po obu stronach.
atorscho
101

Najlepsze działające rozwiązanie wykorzystujące :: 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;
}

Dodaje niewidoczny pseudoelement z szerokością pogrubionego tekstu, którego źródłem jest titleatrybut.

text-shadowRozwiązanie wygląda nienaturalnie na Mac i nie wykorzystuje całe piękno, które renderowania tekstu na Mac ofert .. :)

http://jsfiddle.net/85LbG/

Kredyt: https://stackoverflow.com/a/20249560/5061744

pracoholik_gangster911
źródło
11
To najbardziej niezawodne i czyste rozwiązanie, po prostu rezerwuje miejsce na pogrubiony tekst w elemencie. W moim przypadku dodatkowy pseudoelement spowodował zmianę wysokości. Dodanie negatywu margin-topdo ::afterbloku rozwiązało ten problem.
Vaclav Novotny
2
To świetne rozwiązanie! Użyłem JS do dodania atrybutu „data-content”, który zawiera tekst elementu, więc mogłem uniknąć zmiany kodu HTML.
mistykristie
1
Niesamowite. To powinna być najlepsza odpowiedź. Zauważ, że jeśli inny element niż li, być może będziesz musiał użyć display: block; na: po elemencie nadrzędnym.
Blackbam
Świetne rozwiązanie! Działa dobrze z :: przed również, w moim przypadku zapobiega to dodatkowemu marginesowi na dole.
Thomas Champion
Dziękuję za to rozwiązanie. Początkowo użyłem text-shadow, ale to nie działa w Safari, ponieważ zaokrągla wartości dziesiętne. Ponieważ używałem 0.1px, to było zaokrąglane do 0. Twoje rozwiązanie działało bez zarzutu. Zastąpiłem atrybut tytułu tylko nazwą, ponieważ nie chciałem, aby pokazywały się podpowiedzi
Jonathan Cardoz,
32

Najbardziej przenośnym i przyjemnym wizualnie rozwiązaniem byłoby użycie text-shadow. To zmienia i pokazuje przykłady odpowiedzi Thorgeira przy użyciu poprawek Alexxali i moich własnych:

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

Powoduje to umieszczenie małych „cieni” w kolorze czarnym (w blackrazie potrzeby użyj nazwy / kodu koloru czcionki ) po obu stronach każdej litery przy użyciu jednostek, które będą odpowiednio skalowane podczas renderowania czcionki.

ostrzeżenie Ostrzeżenie: px wartości obsługują wartości dziesiętne, ale nie będą wyglądać tak dobrze, gdy zmieni się rozmiar czcionki (np. Użytkownik skaluje widok za pomocą Ctrl+ +). Zamiast tego użyj wartości względnych.

Ta odpowiedź używa ułamków exjednostek, ponieważ skalują się one wraz z czcionką.
W ~ większości domyślnych przeglądarek * oczekuj 1ex≈, 8pxa zatem 0.025ex0.1px.

Sam zobacz:

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>

Najedź na renderowane linie, aby zobaczyć, czym różnią się od standardowego tekstu.

Zmień poziom powiększenia przeglądarki ( Ctrl+ +i Ctrl+ -), aby zobaczyć, jak się różnią.

Dodałem tutaj dwa inne rozwiązania dla porównania: sztuczka @ cgTag dotycząca odstępów między literami , która nie działa tak dobrze, ponieważ polega na zgadywaniu zakresów szerokości czcionek, oraz @ workaholic_gangster911's :: po rysowaniu sztuczki , która pozostawia niezręczną dodatkową przestrzeń, dzięki czemu pogrubiony tekst może się rozszerzyć bez szturchania sąsiednich elementów tekstowych (atrybucję umieszczam po pogrubionym tekście, abyś mógł zobaczyć, jak się nie porusza).


W przyszłości będziemy mieć więcej czcionek zmiennych umożliwiających zmianę oceny czcionki za pomocą font-variation-settings. Obsługa przeglądarek rośnie (Chrome 63+, Firefox 62+), ale nadal wymaga to czegoś więcej niż tylko standardowych czcionek i obsługuje je tylko kilka istniejących czcionek.

Jeśli osadzisz czcionkę zmienną, będziesz mógł używać CSS w następujący sposób:

/* 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; }
}

W przewodniku Mozilla Variable Fonts Guide znajduje się demonstracja na żywo z suwakiem do gry z różnymi stopniami. Wprowadzenie Google do czcionek zmiennych w Internecie zawiera animowany plik GIF przedstawiający przełączanie między wysoką oceną a brakiem oceny:

animowane demo czcionki Amstelvar Alpha z przełączaną osią nachylenia

Adam Katz
źródło
1
Tak proste, takie piękne.
Randy Hall
18

Zauważyłem, że większość czcionek ma ten sam rozmiar, gdy dostosujesz odstępy między literami o 1 piksel.

a {
   letter-spacing: 1px;
}

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

Chociaż powoduje to zmianę zwykłej czcionki, dzięki czemu każda litera ma dodatkowe odstępy między pikselami. W przypadku menu tytuły są tak krótkie, że nie stanowi to problemu.

Reactgular
źródło
3
jest to najlepsze i najbardziej niedoceniane rozwiązanie, chociaż zmieniłem je tak, że zaczyna się od 0, a pogrubienie przechodzi do ujemnych odstępów między literami. musisz również dostosować go do każdej czcionki i rozmiaru czcionki. Zaktualizowałem również skrzypce @ Thorgeir, aby uwzględnić to (jako drugie rozwiązanie) jsfiddle.net/dJcPn/50/
robotik
Nowoczesne przeglądarki obsługują teraz dokładność poniżej piksela. Możesz więc precyzyjnie dostroić odstępy za pomocą 0.98pxlub mniejszych ułamków.
Reactgular
Wygląda to trochę dziwnie, ponieważ algorytm odstępów nie jest dokładnie taki sam (niektóre litery trochę tańczą) i, co ważniejsze, nie działa to, gdy czcionki są skalowane, nawet jeśli konwertujesz na 1pxwartości względne, takie jak 0.025exlub 0.0125ex. Zobacz moją odpowiedź na demonstrację na żywo.
Adam Katz
@AdamKatz Podejrzewam, że renderowanie czcionek zmieniło się od czasu opublikowania tej odpowiedzi i zależy to w dużej mierze od używanej czcionki.
Reactgular
5

Aby uzyskać bardziej aktualną odpowiedź, możesz użyć -webkit-text-stroke-width:

.element {
  font-weight: normal;
}

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

Pozwala to uniknąć wszelkich pseudoelementów (co jest plusem dla czytników ekranu) i cieni tekstu (które wyglądają niechlujnie i nadal mogą powodować lekki efekt „skoku”) lub ustawiania jakichkolwiek stałych szerokości (co może być niepraktyczne).

Pozwala również ustawić element jako odważniejszy niż 1px (teoretycznie możesz ustawić czcionkę tak odważną, jak chcesz, a także może być tandetnym treningiem do tworzenia odważnej wersji czcionki, która nie ma pogrubienia wariant, jak czcionki niestandardowe ( edycja: czcionki zmienne osłabiają tę sugestię ). Należy tego unikać, ponieważ prawdopodobnie spowoduje to, że niektóre czcionki będą wyglądały na szorstkie i postrzępione)

I to na pewno działa w Edge, Firefox, Chrome i Opera (w momencie publikacji) oraz w Safari ( edytuj: @Lars Blumberg dzięki za potwierdzenie ). NIE działa w IE11 lub starszym.

Zauważ również, że używa -webkitprzedrostka, więc nie jest to standardowe i wsparcie może zostać usunięte w przyszłości, więc nie polegaj na tym, że jest to bardzo ważne - najlepiej unikać tej techniki, chyba że jest tylko estetyczna.

Oliver
źródło
1
Działa również w przeglądarce Safari 13.0.5. Jak dotąd najczystsze rozwiązanie dla mnie.
Lars Blumberg
4

Niestety jedynym sposobem uniknięcia zmiany szerokości, gdy tekst jest pogrubiony, jest zdefiniowanie szerokości elementu listy, jednak, jak powiedziałeś, zrobienie tego ręcznie jest czasochłonne i nie jest skalowalne.

Jedyne, o czym przychodzi mi do głowy, to użycie javascript, który oblicza szerokość karty, zanim zostanie pogrubiona, a następnie zastosuje szerokość w tym samym czasie, gdy pogrubienie jest wymagane (po najechaniu kursorem lub kliknięciu).

ajcw
źródło
hm, będę z tym eksperymentować, chociaż chcę, aby przynajmniej wyglądało rozsądnie (tj. pogrubione) dla użytkowników innych niż JS. Może mogę wysłać pogrubioną czcionkę, użyć JS, aby ją rozwinąć, obliczyć szerokość, a następnie ponownie pogrubić? A może to po prostu głupie?
Mała
Tak, to brzmi jak najlepszy sposób na dostosowanie się do użytkowników nieobsługujących języka JavaScript.
ajcw
2

Użyj JavaScript, aby ustawić stałą szerokość li w oparciu o nie pogrubioną treść, a następnie pogrub zawartość, stosując styl do <a>tagu (lub dodaj rozpiętość, jeśli <li>nie ma żadnych dzieci).

Dan Blows
źródło
Ups - przepraszam, że powtarzam: $
Dan Blows
Mimo wszystko dzięki za odpowiedź :)
Mała
@Mala Czy znalazłeś rozwiązanie? Co zabawne, mam podobny problem.
Dan Blows
1

To bardzo stare pytanie, ale wracam do niego, ponieważ miałem ten problem w aplikacji, którą tworzę, i znalazłem wszystkie potrzebne odpowiedzi.

(Pomiń ten akapit w przypadku TL; DR ...)Używam czcionki internetowej Gotham z cloud.typography.com i mam przyciski, które zaczynają się puste (z białą obwódką / tekstem i przezroczystym tłem) i uzyskują kolor tła po najechaniu kursorem. Zauważyłem, że niektóre kolory tła, których używałem, nie kontrastowały dobrze z białym tekstem, więc chciałem zmienić tekst na czarny dla tych przycisków, ale - czy to z powodu wizualnej sztuczki, czy zwykłych metod wygładzania - ciemny tekst na jasnym tle zawsze wydaje się być lżejszy niż biały tekst na ciemnym tle. Zauważyłem, że zwiększenie wagi z 400 do 500 dla ciemnego tekstu zachowało prawie dokładnie tę samą „wizualną” wagę. Zwiększyło to jednak nieznacznie szerokość przycisku - ułamek piksela - ale wystarczyło, aby przyciski wydawały się lekko „drżeć”, czego chciałem się pozbyć.

Rozwiązanie:

Oczywiście jest to naprawdę drobny problem, więc wymagał wyrafinowanego rozwiązania. Ostatecznie użyłem negatywu letter-spacingna odważniejszym tekście, jak zalecono powyżej cgTag, ale 1px byłby znacznie przesadzony, więc po prostu obliczyłem dokładnie potrzebną szerokość.

Po sprawdzeniu przycisku w devtools Chrome stwierdziłem, że domyślna szerokość mojego przycisku wynosiła 165,47px, a 165,69px po najechaniu kursorem, czyli różnica 0,22px. Przycisk miał 9 znaków, więc:

0,22 / 9 = 0,024444px

Konwertując to na jednostki em, mogłem zmienić rozmiar czcionki na agnostyczny. Mój przycisk miał rozmiar czcionki 16px, więc:

0,024444 / 16 = 0,001527em

Więc dla mojej konkretnej czcionki poniższy kod CSS utrzymuje przyciski dokładnie tej samej szerokości po najechaniu kursorem:

.btn {
  font-weight: 400;
}

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

Po krótkim przetestowaniu i zastosowaniu powyższego wzoru możesz znaleźć dokładnie odpowiednią letter-spacingwartość dla swojej sytuacji i powinna działać niezależnie od rozmiaru czcionki.

Jedynym zastrzeżeniem jest to, że różne przeglądarki używają nieco innych obliczeń subpikseli, więc jeśli dążysz do tego poziomu OCD doskonałej precyzji subpikseli, musisz powtórzyć testy i ustawić inną wartość dla każdej przeglądarki. Style CSS ukierunkowane na przeglądarkę są generalnie źle widziane , nie bez powodu, ale myślę, że jest to jeden przypadek użycia, w którym jest to jedyna sensowna opcja.

dannymcgee
źródło
0

Interesujące pytanie. Przypuszczam, że używasz float, prawda?

Cóż, nie znam żadnej techniki, której możesz użyć, aby pozbyć się powiększania tej czcionki, dlatego spróbują zmieścić się w wymaganej minimalnej szerokości - a zmiana grubości czcionki zmieni tę wartość.

Unikalne rozwiązanie, które znam, aby uniknąć tej zmiany, to takie, o którym powiedziałeś, że nie chcesz: ustawienie stałych rozmiarów na li.

Davis Peixoto
źródło
0

Możesz to zaimplementować jak na amazon.com menu „Kupuj według działu”. Używa szerokiego div. Możesz stworzyć szeroki div i ukryć jego prawą część

Andrus
źródło
0

AKTUALIZACJA: Musiałem użyć tagu B w tytule, ponieważ w IE11 pseudo klasa i: after nie była wyświetlana, gdy miałem widoczność: ukryta.

W moim przypadku chcę wyrównać (zaprojektowane na zamówienie) pole wyboru / radio z tekstem etykiety, gdzie tekst jest pogrubiony po sprawdzeniu danych wejściowych.

Przedstawione tutaj rozwiązanie nie działało u mnie w Chrome. Pionowe wyrównanie wejścia i etykiety zostało pomieszane z klasą: after psuedo i -margins nie naprawiły tego.

Oto poprawka pozwalająca uniknąć problemów z wyrównaniem w pionie.

/* 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>

Erik
źródło