CSS: mettre du texte en gras sans changer la taille de son conteneur

119

J'ai un menu de navigation horizontal, qui est fondamentalement juste un <ul>avec les éléments placés côte à côte. Je ne définit pas la largeur, mais j'utilise simplement un remplissage, car je voudrais que les largeurs soient définies par la largeur de l'élément de menu. Je mets en gras l'élément actuellement sélectionné.

Le problème est qu'en gras, le mot devient légèrement plus large, ce qui fait que le reste des éléments se déplace légèrement vers la gauche ou la droite. Y a-t-il un moyen intelligent d'empêcher que cela se produise? Quelque chose dans le sens de dire au rembourrage d'ignorer la largeur supplémentaire causée par le gras? Ma première pensée a été de simplement soustraire quelques pixels du rembourrage de l'élément "actif", mais cette quantité varie.

Si possible, j'aimerais éviter de définir une largeur statique sur chaque entrée, puis de centrer par opposition à la solution de remplissage que j'ai actuellement, afin de simplifier les futures modifications des entrées.

Mala
la source
pourquoi le changement de taille est-il un problème? Est-ce que cela perturbe la mise en page?
Chaulky
Je pense que les questions de programmation Web sont mieux posées sur Stack Overflow. Peut-être qu'un mod le fera migrer là-bas.
Ciaran
2
Chaulky: parce qu'il y a peu de choses que je déteste plus, en termes de mise en page, que lorsque les boutons sur lesquels vous êtes censé essayer de cliquer sautent autour
Mala
doejo.com/blog/ ... est une solution que j'ai trouvée qui fait exactement ce que John et Blowski parlaient de jscript.
thebulfrog

Réponses:

152

J'ai eu le même problème, mais j'ai eu un effet similaire avec un petit compromis, j'ai utilisé text-shadow à la place.

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

Voici un exemple de travail:

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>

exemple jsfiddle

Thorgeir
la source
51
J'adore cette solution, même si je recommande de retourner celle-ci à 1px 0px 0px. Ça a l'air un peu plus audacieux.
M. Herold
J'avais besoin d'une solution css uniquement et c'est la réponse.
JCasso
tu es un vrai génie! la simplicité le rend génial!
lufi
1
Seul l'ajout a li:hover {text-shadow: 1px 0 0 black;}donné lieu à un texte avec un espacement insuffisant entre les lettres. Pour améliorer à nouveau cela, nous avons également définili {letter-spacing: 1px;}
Patrick Hammer
5
J'ai utilisé -0.5px 0 #fff, 0.5px 0 #fff, parce que nous pouvions voir le petit écart entre le texte lui-même et l'ombre. Et aussi lorsque le texte est en gras, il ajoute du poids des deux côtés.
atorscho
101

La meilleure solution de travail en utilisant :: 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;
}

Il ajoute un pseudo-élément invisible avec une largeur de texte en gras, provenant d'un titleattribut.

le text-shadow solution ne semble pas naturelle sur Mac et n'utilise pas toute la beauté qu'offre le rendu de texte sur Mac .. :)

http://jsfiddle.net/85LbG/

Crédit: https://stackoverflow.com/a/20249560/5061744

workaholic_gangster911
la source
11
C'est la solution la plus fiable et la plus propre , elle réserve simplement de l'espace pour le texte en gras dans l'élément. Dans mon cas, le pseudo-élément supplémentaire a provoqué un changement de hauteur. L'ajout de négatif margin-topau ::afterbloc a résolu cela.
Vaclav Novotny
2
Cette solution est géniale! J'ai utilisé JS pour ajouter un attribut "data-content" qui contient le texte de l'élément afin que je puisse éviter de changer le HTML.
mistykristie
1
Impressionnant. Cela devrait être la meilleure réponse. Notez que si l'élément différent de li, vous devrez peut-être utiliser display: block; sur l'élément: après parent.
Blackbam
Excellente solution! Fonctionne bien avec :: avant aussi, dans mon cas, cela empêche une marge supplémentaire en bas.
Thomas Champion
Merci pour cette solution. J'ai initialement utilisé text-shadow mais cela ne fonctionne pas dans Safari car il arrondit les valeurs décimales. Depuis que j'utilisais 0,1px, ce chiffre était arrondi à 0. Votre solution fonctionnait parfaitement. J'ai seulement remplacé l'attribut title par name car je ne voulais pas que les info-bulles s'affichent
Jonathan Cardoz
32

La solution la plus portable et la plus agréable visuellement serait d'utiliser text-shadow. Cela révise et montre des exemples de la réponse de Thorgeir en utilisant Alexxali et mes propres ajustements:

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

Cela met de minuscules "ombres" en noir (utilisez le nom / code de couleur de votre police au lieu de blacksi nécessaire) sur les deux côtés de chaque lettre en utilisant des unités qui seront correctement mises à l'échelle avec le rendu de la police.

avertissement Attention: les px valeurs supportent les valeurs décimales, mais elles n'auront pas l'air si belles lorsque la taille de la police change (par exemple, l'utilisateur met la vue à l'échelle avec Ctrl+ +). Utilisez plutôt des valeurs relatives.

Cette réponse utilise des fractions d' exunités puisqu'elles sont mises à l'échelle avec la police.
Dans ~ la plupart des navigateurs par défaut * , attendez 1ex8pxet donc 0.025ex0.1px.

Voir par vous-même:

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>

Survolez les lignes rendues pour voir en quoi elles diffèrent du texte standard.

Modifiez le niveau de zoom de votre navigateur ( Ctrl+ +et Ctrl+- ) pour voir comment ils varient.

J'ai ajouté deux autres solutions ici à titre de comparaison: l' astuce d'espacement des lettres de @ cgTag , qui ne fonctionne pas si bien car elle implique de deviner les plages de largeurs de police, et l'astuce de @ workaholic_gangster911 :: après le dessin , qui laisse un espace supplémentaire gênant pour que le texte en gras puisse s'étendre sans pousser les éléments de texte voisins (je place l'attribution après le texte en gras pour que vous puissiez voir comment il ne bouge pas).


À l'avenir, nous aurons plus de polices variables capables de modifier la qualité de la police via font-variation-settings. La prise en charge des navigateurs augmente (Chrome 63+, Firefox 62+) mais cela nécessite encore plus que de simples polices standard et peu de polices existantes le prennent en charge.

Si vous incorporez une police variable, vous pourrez utiliser CSS comme ceci:

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

Il existe une démo en direct avec un curseur pour jouer avec différentes notes sur le guide des polices variables de Mozilla. L' introduction de Google aux polices variables sur le Web a un GIF animé démontrant une bascule entre une note élevée et aucune note:

démo animée de polices Amstelvar Alpha avec basculement de l'axe de pente

Adam Katz
la source
1
Si simple, si beau.
Randy Hall
18

J'ai trouvé que la plupart des polices sont de la même taille lorsque vous ajustez l'espacement des lettres de 1 px.

a {
   letter-spacing: 1px;
}

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

Bien que cela change la police normale de sorte que chaque lettre ait un espacement de pixels supplémentaire. Pour les menus, les titres sont si courts que cela ne pose pas de problème.

Reactgular
la source
3
c'est la solution la meilleure et la plus sous-estimée, même si je l'ai changée pour qu'elle commence par 0 et passe à l'espacement négatif des lettres en gras. vous devez également l'ajuster pour chaque police et taille de police. J'ai également mis à jour le violon de @ Thorgeir pour inclure ceci (comme 2ème solution) jsfiddle.net/dJcPn/50/
robotik
Les navigateurs modernes prennent désormais en charge la précision sous-pixel. Ainsi, vous pouvez affiner l'espacement en utilisant 0.98pxou des fractions plus petites.
Reactgular
Cela semble un peu étrange car l'algorithme d'espacement n'est pas exactement le même (certaines lettres dansent un peu) et, plus important encore, cela ne fonctionne pas lorsque les polices sont mises à l'échelle, même si vous convertissez les 1pxvaleurs relatives comme 0.025exou 0.0125ex. Voir ma réponse pour une démonstration en direct.
Adam Katz
@AdamKatz Je soupçonne que le rendu des polices a changé depuis que cette réponse a été publiée, et cela dépend beaucoup de la police que vous utilisez.
Reactgular
5

Pour une réponse plus à jour, vous pouvez utiliser -webkit-text-stroke-width:

.element {
  font-weight: normal;
}

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

Cela évite les pseudo-éléments (ce qui est un plus pour les lecteurs d'écran) et les ombres de texte (qui semblent désordonnées et peuvent toujours créer un léger effet de `` saut '') ou de définir des largeurs fixes (ce qui peut être peu pratique).

Il vous permet également de définir un élément pour qu'il soit plus audacieux que 1px (en théorie, vous pouvez créer une police aussi audacieuse que vous le souhaitez et pourrait également être un entraînement de mauvaise qualité pour créer une version audacieuse d'une police qui n'a pas de gras variante, comme les polices personnalisées ( modifier: les polices variables déprécient cette suggestion ). Cependant, cela devrait être évité car cela rendra probablement certaines polices irrégulières et irrégulières)

Je cela fonctionne certainement dans Edge, Firefox, Chrome et Opera (au moment de la publication) et dans Safari ( modifier: @Lars Blumberg merci de confirmer cela ). Cela ne fonctionne PAS dans IE11 ou en dessous.

Notez également qu'il utilise le -webkitpréfixe, donc ce n'est pas standard et le support peut être abandonné à l'avenir, alors ne vous fiez pas à cela, c'est vraiment important - il est préférable d'éviter cette technique à moins qu'elle ne soit simplement esthétique.

Oliver
la source
1
Fonctionne également sur Safari 13.0.5. La solution la plus propre pour moi à ce jour.
Lars Blumberg
4

Malheureusement, le seul moyen d'éviter que la largeur change lorsque le texte est en gras est de définir la largeur de l'élément de liste, mais comme vous l'avez indiqué, cela prend du temps et n'est pas évolutif.

La seule chose à laquelle je peux penser est d'utiliser du javascript qui calcule la largeur de l'onglet avant qu'il ne soit en gras, puis applique la largeur en même temps que le gras est requis (soit lorsque vous survolez ou cliquez).

ajcw
la source
hm, je vais expérimenter avec cela, même si je veux que cela semble au moins raisonnable (c'est-à-dire en gras) pour les utilisateurs non JS. Peut-être que je peux l'envoyer en gras, utiliser JSto pour le déplier, calculer la largeur, puis le mettre à nouveau en gras? Ou est-ce juste idiot?
Mala
Oui, cela semble être la meilleure façon d'accueillir les utilisateurs non javascript.
ajcw
2

Utilisez JavaScript pour définir une largeur fixe du li en fonction du contenu non plié, puis mettez le contenu en gras en appliquant un style à la <a>balise (ou ajoutez une étendue si le <li>n'a pas d'enfants).

Dan souffle
la source
Oups, désolé de répéter: $
Dan Blows
Merci pour votre réponse quand même :)
Mala
@Mala Avez-vous trouvé une solution? Curieusement, j'ai un problème similaire.
Dan Blows
1

C'est une question très ancienne, mais je la revisite car j'ai eu ce problème dans une application que je développe et j'ai trouvé toutes les réponses voulues.

(Passer ce paragraphe pour le TL; DR ...)J'utilise la police Web Gotham de cloud.typography.com, et j'ai des boutons qui commencent creux (avec une bordure / texte blanc et un fond transparent) et acquièrent une couleur d'arrière-plan en survol. J'ai trouvé que certaines des couleurs d'arrière-plan que j'utilisais ne contrastaient pas bien avec le texte blanc, donc je voulais changer le texte en noir pour ces boutons, mais - que ce soit à cause d'une astuce visuelle ou des méthodes d'anti-aliasing courantes - sombre le texte sur fond clair semble toujours plus léger que le texte blanc sur fond sombre. J'ai trouvé que l'augmentation du poids de 400 à 500 pour le texte sombre maintenait presque exactement le même poids «visuel». Cependant, cela augmentait la largeur du bouton d'une toute petite quantité - une fraction de pixel - mais c'était suffisant pour que les boutons semblent légèrement «vaciller», ce dont je voulais me débarrasser.

Solution:

De toute évidence, il s'agit d'un problème très délicat, il a donc fallu une solution capricieuse. En fin de compte, j'ai utilisé un négatif letter-spacingsur le texte plus audacieux comme cgTag recommandé ci-dessus, mais 1px aurait été bien excessif, alors j'ai juste calculé exactement la largeur dont j'aurais besoin.

En inspectant le bouton dans Chrome devtools, j'ai constaté que la largeur par défaut de mon bouton était de 165,47 pixels et de 165,69 pixels en survol, une différence de 0,22 pixels. Le bouton avait 9 caractères, donc:

0,22 / 9 = 0,024444px

En convertissant cela en unités em, je pourrais rendre le réglage de la taille de la police agnostique. Mon bouton utilisait une taille de police de 16 pixels, donc:

0,024444 / 16 = 0,001527em

Donc, pour ma police particulière, le CSS suivant garde les boutons exactement de la même largeur en survol:

.btn {
  font-weight: 400;
}

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

Avec un peu de test et en utilisant la formule ci-dessus, vous pouvez trouver exactement le bon letter-spacing valeur pour votre situation, et cela devrait fonctionner quelle que soit la taille de la police.

La seule mise en garde est que différents navigateurs utilisent des calculs de sous-pixels légèrement différents, donc si vous visez ce niveau OCD de précision sub-pixel-perfect, vous devrez répéter le test et définir une valeur différente pour chaque navigateur. Les styles CSS ciblés par navigateur sont généralement mal vus , pour une bonne raison, mais je pense que c'est un cas d'utilisation où c'est la seule option qui a du sens.

dannymcgee
la source
0

Question interessante. Je suppose que vous utilisez float, non?

Eh bien, je ne connais aucune technique que vous pouvez utiliser pour vous débarrasser de cet agrandissement de la police, par conséquent, ils essaieront de s'adapter à la largeur minimale requise - et une épaisseur de police variable changera cette valeur.

La solution unique que je connais pour éviter ce changement est celle que vous avez dit ne pas vouloir: définir des tailles fixes sur li.

Davis Peixoto
la source
0

Vous pouvez l'implémenter comme le menu déroulant "Magasiner par département" sur amazon.com. Il utilise une division large. Vous pouvez créer une division large et masquer sa partie droite

Andrus
la source
0

MISE À JOUR: J'ai dû utiliser la balise B pour le titre car dans IE11 la pseudo classe i: after ne s'affichait pas quand j'avais visibilité: hidden.

Dans mon cas, je veux aligner une case à cocher / radio d'entrée (conçue sur mesure) avec le texte d'étiquette où le texte devient gras lorsque l'entrée est cochée.

La solution fournie ici ne fonctionnait pas pour moi dans Chrome. L'alignement vertical de l'entrée et de l'étiquette a été perturbé par la classe: after psuedo et -margins n'a pas résolu ce problème.

Voici un correctif qui ne pose aucun problème avec les alignements verticaux.

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