CSS Text underlining too long when letter-spacing

2020-01-26 11:13发布

问题:


Whenever letter-spacing is applied to something with an underline or a bottom border, it seems like the underline extends beyond the text on the right. Is there any way to do prevent the underline from extending beyond the last letter in the text?

For example:

<span style="letter-spacing: 1em; border-bottom: 1px solid black;">Test</span>

Perhaps it'd be possible with a fixed width <div> and a border, but that is really not an option to surround every underlined element.

Thanks!

回答1:

The problem is that "letter-spacing" adds space by adding white space after each letter, but this space belongs to the letter, and thus the last one has an extra underline that makes it look like it's expanding beyond the last letter (if you select with the mouse the last letter of the div, you'll see what I mean).

You could solve it by having the last letter of the text block not having the "letter-spacing" property. For example:

<span style="letter-spacing: 1em; text-decoration: underline;">SPACEEE</span>
<span style="text-decoration: underline;">.</span>

It's far from ideal, but unless it's just a one line text (there, you could use a negative margin to the right), I can't think of any other solution.



回答2:

It's not perfect, but the best solution I have come up with so far is to mask it with a :after pseudo element. This way there's no need for extra elements throughout your text.

For example:

h1 {
  /* A nice big spacing so you can see the effect */
  letter-spacing: 1em;
  /* we need relative positioning here so our pseudo element stays within h1's box */
  position: relative;
  /* centring text throws up another issue, which we'll address in a moment */
  text-align: center;
  /* the underline */
  text-decoration: underline;
}

h1:after {
  /* absolute positioning keeps it within h1's relative positioned box, takes it out of the document flow and forces a block-style display */
  position: absolute;
  /* the same width as our letter-spacing property on the h1 element */
  width: 1em;
  /* we need to make sure our 'mask' is tall enough to hide the underline. For my own purpose 200% was enough, but you can play and see what suits you */
  height: 200%;
  /* set the background colour to the same as whatever the background colour is behind your element. I've used a red box here so you can see it on your page before you change the colour ;) */
  background-color: #990000;
  /* give the browser some text to render (if you're familiar with clearing floats like this, you should understand why this is important) */
  content: ".";
  /* hide the dynamic text you've just added off the screen somewhere */
  text-indent: -9999em;
  /* this is the magic part - pull the mask off the left and hide the underline beneath */
  margin-left: -1em;
}

<h1>My letter-spaced, underlined element!</h1>

And that's it!

You can also use borders if you want finer control over colour, positioning, etc but these require you to add a span element unless you have a fixed width.

For example, I'm currently working on a site which requires h3 elements to have 2px letter spacing, centred text and an underline with added space between the text and the underline. My css is as follows:

h3.location {
  letter-spacing: 2px;
  position: relative;
  text-align: center;
  font-variant: small-caps;
  font-weight: normal;
  margin-top: 50px;
}

h3.location span {
  padding-bottom: 2px;
  border-bottom: 1px #000000 solid;
}

h3.location:after {
  position: absolute;
  width: 2px;
  height: 200%;
  background-color: #f2f2f2;
  content: ".";
  text-indent: -9999em;
  margin-left: -2px;
}

and my HTML is:

<h3><span>Heading</span></h3>

Notes:

It's not 100% pretty CSS but it does at least mean you don't have to modify & hack your HTML to achieve the same result.

I haven't yet had to try this on an element with a background image, so haven't yet thought of a method of achieving this.

Centring text makes the browser display the text in the wrong place (it accounts for the text + extra spacing afterwards), so everything is pulled left. Adding a text-indent 0.5em (half the 1em letter spacing we used in the top example) directly on the h1 element (not the :after pseudo element) should fix this, though I've not tested this yet.

Any feedback is gratefully received!

Neal



回答3:

You can also use an absolute positioned pseudo element to achieve the underline, and counter the offset by specifying its with using the left and right property. Click below for an example.

<span style="letter-spacing: 5px; position: relative">My underlined text</span>

span:after {
    content: '';
    border-bottom:1px solid #000;
    display: block;
    position: absolute;
    right: 5px;
    left: 0;
}

https://codepen.io/orangehaze/pen/opdMoO



回答4:

As far as I know, there’s nothing you can do about this. I guess most browsers add letter spacing after each letter, including the last one. There aren’t any CSS properties that offer finer control over letter spacing.

This seems to solve the issue, sort-of, in Firefox 3.6 on my Mac, but it’s not super-practical to do this for every underlined element either.

<span style="border-bottom: 1px solid black;">
    <span style="letter-spacing: 1em;">Tes</span>t
</span>


回答5:

Thanks for the responses, I suppose it is a limitation of css/html. Luckily these links were being put in via php so I wrote a script to surround them appropriately, although not an entirely ideal solution, it works:

$part1 = substr($string, 0, -1);
$part2 = substr($string, -1);
$full = "<span style='letter-spacing: 1em'>".$part1."</span><span>".$part2."</span>";

(the second span for the last letter I included so I could apply a style with no letter-spacing when I needed to wrap the whole element in a link or something else with an underline etc)

Too bad the control for underlines is rather limited with html/css (especially trying to define the distance between the line and the text, thickness etc), but I suppose this works for now.



回答6:

If you don't want to split the last letter of "Test", you can just wrap it with a span and do this:

span {
    letter-spacing: 1.1em;
    margin-right: -1.1em;
}

As you can see it even works with elastic units. If don't have a custom margin on a, you can skip adding the span and apply this style directly to a

It works wonders for me =)



回答7:

I wouldn't break tex<span>t</span> for SEO reasons, so I prefer to replace the underline with a fake one. CSS calc can help you with its width.

a{
  text-decoration:none;
  letter-spacing:15px;
  color:red;
  display:inline-block;
}

a .underline{
  height:2px;
  background-color:red;
  width: calc(100% - 15px); /*minus letter spacing*/
}
<a href="#">Text
<div class="underline"></div></a>