9

Is there a way to determine that given HTML element has pseudo element (::before / ::after) applied to it without calling

window.getComputedStyle(element, ":before/:after");

EDITED: the answer is NO

The problem with getComputedStyle is in performance. I need to validate every single element on the page whether it has a pseudo element or not - that's why performance is very critical. I tried to access cssRules from document.styleSheets to find selectors with ::before or ::after at the end, remove it and find elements matched with the rest of selector, but this solution has its own issues:

  • styleSheets from different domain are not accessible
  • there could be thousands of cssRules to check (again performance)

I also tried to compare element's sizes and offsets with and without pseudo elements but nothing worked. Even if there is no accurate method I would be happy to find a way to cut an amount of elements to check at least by 30%.


When I'm sure that element has a pseudo element I can finally call getComputedStyle to investigate pseudo element style and change it. This part perfectly works.


EDITED: I have no control over a source page. Page styles could be dynamicly uploaded from different domains. Assume my code as a content script or library which for example has to change all pseudo elements foregrounds to some color calculated based on other CSS properties of that pseudo element.

So main problem that changes are based on other CSS properties and you can not set the same style for all pseudo elements without actually retrieving computed style of the pseudo element and calculating changes.

For example you have youtube.com page and you need to find all pseudo elements and

  • If this pseudo element has background image then scale the element
  • If this pseudo element has only a text then change it color to red
  • etc...
Pavel Agarkov
  • 3,633
  • 22
  • 18
  • Can you provide some more detail on what exactly it is you are doing and why? Perhaps the goal you are trying to achieve can be accomplished in a different way than you are imagining... – Alexander Nied Oct 23 '16 at 19:23
  • I need to find all pseudo elements on the page and change their style. For example font color. – Pavel Agarkov Oct 23 '16 at 19:55
  • Click the F12 button in your browser. Find this `` Copy and paste that `href` value to your browser address bar and go there. Copy and paste to your post. There is no way we could help you unless you can meet us halfway, so post your code and you should read about [mcve] – zer00ne Oct 23 '16 at 21:09
  • not sure if it would really improve things, but you could exclude replaced elements like `img`, `input`, `textarea`, `select` etc that do not allow pseudo elements. – Gabriele Petrioli Oct 24 '16 at 11:21
  • This is interesting - did not know that some elements could not have pseudo - will check it. thanks. – Pavel Agarkov Oct 24 '16 at 11:29
  • @GabyakaG.Petrioli, actually, the note saying that the behavior for `replaced elements` is not yet defined in the specs isn't there anymore, and Firefox 50 seems to apply pseudo-elements to it too... So the only filtering OP can do is on elements vs non-elements nodes. – Kaiido Oct 24 '16 at 12:47
  • @Kaiido it is directing to css2 which includes the note, and in css4 draft https://drafts.csswg.org/css-pseudo-4/#generated-content it says "*pseudo-elements generate boxes as if they were immediate children of their originating element*". The elements i posted in the comment do not allow immediate children. – Gabriele Petrioli Oct 24 '16 at 13:46
  • @Kaiido btw if you have a link about firefox i would really like to read about it. I am interested in how they would tackle some issues. – Gabriele Petrioli Oct 24 '16 at 13:50
  • @GabyakaG.Petrioli, I don't have any link about it, I just found this out this morning when writing my answer, and found this behavior on my FF 50.0b on osX. It could also be a bug in FF that should be reported. It's still there in Nightly 52 though, but it's actually only for `::after` pseudo-element... quite weird. – Kaiido Oct 24 '16 at 14:01
  • @Kaiido cool, thanks for the info. – Gabriele Petrioli Oct 24 '16 at 14:40

2 Answers2

3

What exactly is causing problems with your existing solution?

This method of walking through elements:

var allElements = document.getElementsByTagName("*");
for (var i = allElements.length; i--;) {
  //var usedStyle = window.getComputedStyle(allElements[i], ":before/:after");    
  ...
}

is about thousands of elements per 1ms.

And call of getComputedStyle shall not be too slow as it fetches data that is already calculated at the moment of call (on rendered document).

So all this shall really work fast enough. Yes, it is O(N) task in principle but gets run once, right?

c-smile
  • 26,734
  • 7
  • 59
  • 86
  • _“And call of getComputedStyle shall not be too slow as it fetches data that is already calculated at the moment of call”_ - no, it’s not as easy as that. A call to getComputedStyle causes a reflow, see http://stackoverflow.com/a/1278213/1427878 – CBroe Oct 24 '16 at 11:23
  • yes it is very slow - for example with youtube page it would take almost 5 seconds! – Pavel Agarkov Oct 24 '16 at 11:31
  • finally it turned out it is not that slow - I just had another process at the same time which slowed everything down. So I will stick with getComputedStyle – Pavel Agarkov Oct 24 '16 at 17:20
2

I think that getComputedStyle() is the only reliable way to know whether an element has a pseudo-element attached to it.

But since there is no way to edit it from DOM method, this means you'll have to append a new rule in the document.

So a simple solution if, as stated in your last edit you will apply the same rules on all pseudo elements, is to use the wildcard * selector:

*:before,*:after{ /*your rules*/ }

var style = document.createElement('style');
style.innerHTML = `*:before,*:after{
  color: green !important;
}`;
setTimeout(function() {
  document.head.appendChild(style);
}, 1500);
div:before {
  content: 'hello';
  color: blue;
}
div:after {
  content: 'world';
  color: red;
}
<div></div>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Yes, I do use similar approach right now. The only difference that I set custom attribute for the owner element: – Pavel Agarkov Oct 24 '16 at 11:18
  • `
    ...
    ` => [before-style="1231243523341"]:before { color: red; }
    – Pavel Agarkov Oct 24 '16 at 11:20
  • @pasza keep in mind though that the already defined rule for the `:before`,`:after` can have higher specificity than your rule, so your styling might not get applied. – Gabriele Petrioli Oct 24 '16 at 11:25
  • I forgot to mention this in my question (now it is) that I need different changes for each pseudo element based on its other CSS properties... so I still need to get actual computed styles. So the main question is how to figure it out that element has pseudo. – Pavel Agarkov Oct 24 '16 at 11:25
  • yes that's why I actually use something like this: [before-style="1231243523341"]:not(important):before { color: red !important; } but even this way can not guarantee the result... – Pavel Agarkov Oct 24 '16 at 11:27
  • @pasza, that's what I was afraid of. Then, what you do is the only way. I posted a now deleted comment saying that you could filter out replaced elements where pseudo-elements don't apply, but actually, the noe in specs about it has disappeared, and even if I couldn't find anything in latest revs, Firefox already applies pseudo-elements on replaced-elements. So your only filtering option is to target only elements, which is done in c-smile's quite correct answer (IMM you should accept it). – Kaiido Oct 24 '16 at 12:49