14

Markup:

<h1 class="title">Hello World</h1>

CSS:

.title {
  border-bottom: 3px solid #aaa;
  position: relative;
}
.title:after {
  content: "";
  width: 100px;
  border-bottom: 3px solid red;
  display: block;
  position: absolute;
}

Demo: http://codepen.io/anon/pen/HDBqe

I wanted to change the .title:after's width based on the text's width, how do I change :after's width using javascript?

$.fn.textWidth = function(){
  var html_org = $(this).html();
  var html_calc = '<span>' + html_org + '</span>';
  $(this).html(html_calc);
  var width = $(this).find('span:first').width();
  $(this).html(html_org);
  return width;
};

$('.title').each(function(){
  // Change :after's width based on the text's width
  // .css('width', $(this).textWidth());
});
Jürgen Paul
  • 14,299
  • 26
  • 93
  • 133
  • You can't access this pseudo-selector using JS. – gvee Sep 09 '13 at 08:37
  • 1
    He can change the style in the stylesheet – Itay Sep 09 '13 at 08:41
  • 1
    @Itay, that would be the approach that I'd take. Since pseudo-elements are not part of the DOM, you will have to target the stylesheet. See [this answer](http://stackoverflow.com/a/5041526/268093). – MasterAM Sep 09 '13 at 08:45

4 Answers4

18

I've found a trick which works in this case. I've updated your demo:

http://codepen.io/anon/pen/bHLtk

.title {
  border-bottom: 3px solid #aaa;
  position: relative;
  min-width: 100%;
}

.title:after {
  content: "";
  width: inherit;
  border-bottom: 3px solid red;
  display: block;
  position: absolute;
}

Notice, that .title:after has width set to inherit but his parent (.title) has overridden width with min-width. Now I can freely to set width by JavaScript to title and it take effect only on his nested pseudoelement:

$('.title').each(function(){
  $(this).css('width', $(this).textWidth());
});
Terite
  • 1,097
  • 13
  • 23
4

A pseudo-element is not part of the DOM. Therefore, you cannot change its CSS properties directly through JS.

In order to get your desired effect the way you want it, my best guess would be YUI.StyleSheet and manipulate the stylesheet itself, although I have to admit I haven't tested it myself in recent years.

Including such a utility and doing all of this calculation seems like a lot of work for width matching.

If you are willing to compromise a little bit on the semantic HTML, there is a working technique:

Your element takes the entire width of the screen. Wrapping the text with a span and adding the pseudo-element to that, as an inline-block should allow you to get the border under the text only

HTML:

<h1 class="title"><span>Hello World</span></h1>

CSS:

.title {
    border-bottom: 3px solid #aaa;
    position: relative;
}

.title span{
    display:inline-block;
    position:relative;
}

.title span:after {
    content: "";
    width: 100%;
    border-bottom: 3px solid red;
    display: block;
    position: absolute;
}

Here is my version of the codePen.

For future reference:

There is a W3C Candidate Recommendation that suggests the capability of using attributes for CSS properties other than content.

This way, if and when the recommendation is approved and implemented, it might be possible to have the pseudo-element reflect the parent's attributes, as such:

this.setAttribute("length", $(this).textWidth());

And the relevant CSS:

.title:after {
    ...
    width: attr(length px);
    ...
}
MasterAM
  • 16,283
  • 6
  • 45
  • 66
  • how did you know you should be using `display:block`? My first thought was [`bottom: -3px; left: 0`](http://codepen.io/anon/pen/mFxiE) but the `display:block` trick worked. – Jürgen Paul Sep 09 '13 at 19:00
  • FYI As of Chrome 59 this is still not supported :( – Alex White Sep 05 '17 at 17:23
  • I don't know where you've got "width: attr(length px);" from but that doesn't work at all – A Friend Sep 06 '18 at 05:26
  • 1
    As stated in this (fairly old) answer, this is a __candidate recommendation__, so there was no guarantee that it would ever be accepted. That said, having an attribute prefixed with `data-` might work. – MasterAM Sep 06 '18 at 07:03
1

How's this for a different approach.... http://jsfiddle.net/mayYt/

Added CSS

.title span {
    border-bottom: 3px solid red;
}

JQuery

$('.title').wrapInner('<span />');
gvee
  • 16,732
  • 35
  • 50
0

With just a simple trick any pseudo-element can be changed (or at least replaced with something else):

$('.something').click(function(){
  $(this).toggleClass('to_show');
});
.something
{
  background: red;
  height: 40px;
  width: 120px;
  position: relative;
}
.something.to_show:after
{
  content: "X";
  color: white;
  background: green;
  width: 20px;
  height: 20px;
  position: absolute;
  right: 0px;
  top: 0px;
  display: block;
  text-align: center;
}
.something:after
{
  content: "O";
  color: white;
  background: blue;
  width: 30px;
  height: 25px;
  position: absolute;
  right: 0px;
  top: 0px;
  display: block;
  text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
  <body>
    <div class="something">
      click here!
    </div>
  </body>
</html>
Zsolti
  • 1,571
  • 1
  • 11
  • 22