2

I am trying to get the targeted element with the pseudo-class :target after document load. I created the following example to illustrate the problem.

<!DOCTYPE html>
<html>
    <head>
        <script>
            document.addEventListener("DOMContentLoaded",function(){
                console.log(document.querySelector(":target"));
            });
        </script>
    </head>
    <body>
        <div id="test"></div>
    </body>
</html>

If I load test.html, then the console outputs :

null

If I load test.html#test on Chrome and Opera, then the console outputs :

null

If I load test.html#test on Firefox and IE11, then the console outputs :

<div id="test"></div>

My questions are :

  1. Which browsers have the correct behaviour ?
  2. Does DOMContentLoaded is the correct event to call querySelector(":target") ?
  3. Is there another way to get targeted element after document load ?

PS : I succeeded to fix the problem on Chrome and Opera thanks to setTimeout but It is not a good solution. Does someone has a better idea ?

EDIT : I found a similar issue with JQuery Selecting :target on document.ready()

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Baptistou
  • 1,749
  • 1
  • 13
  • 24
  • 2
    From the [Selectors API Level 1](https://www.w3.org/TR/selectors-api/#grammar): _"Authors are advised that while the use of **pseudo-elements in selectors is permitted, they will not match any elements in the document, and thus would not result in any elements being returned**. Therefore, authors are advised to avoid the use of pseudo-elements in selectors that are passed to the methods defined in this specification. "_ – Andreas Apr 15 '21 at 09:02
  • 3
    But `:target` is a [pseudo class](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes) not a [pseudo element](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements) – Baptistou Apr 15 '21 at 09:31
  • In `onload` it works [fine for me](https://test-target-selector.glitch.me/#target) in Chrome, but not in Safari... An hack around would be to have a small transition (0.01s) set on `:target` and listen for the transitionend event. – Kaiido Apr 17 '21 at 11:30

2 Answers2

2

This is a known issue with WebKit- and Blink-based browsers that has never been directly addressed. The workaround suggested by web-platform-tests is to request an animation frame, which only happens after page rendering, at which point the :target pseudo seems to match successfully:

async_test(function() {
  var frame = document.createElement("iframe");
  var self = this;
  frame.onload = function() {
    // :target doesn't work before a page rendering on some browsers.  We run
    // tests after an animation frame because it may be later than the first
    // page rendering.
    requestAnimationFrame(self.step_func_done(init.bind(self, frame)));
  };
  frame.src = "ParentNode-querySelector-All-content.xht#target";
  document.body.appendChild(frame);
})

My testing shows that simply using onload works fine, but the author may be on to something and besides, a single call to requestAnimationFrame() costs practically nothing, so you may as well follow suit.

The following test uses onload (as opposed to DOMContentLoaded, which fires immediately after the DOM tree has been constructed but not necessarily rendered):

data:text/html,<!DOCTYPE html><script>window.onload=function(){console.log(document.querySelector(":target"));};</script><div id="test"></div>#test

The following test uses requestAnimationFrame() in conjunction with onload:

data:text/html,<!DOCTYPE html><script>window.onload=requestAnimationFrame(function(){console.log(document.querySelector(":target"));});</script><div id="test"></div>#test
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • Regarding the first paragraph, it seems to be necessary to call requestAnimationFrame in `onload`, at least for Chrome, and certainly because of https://crbug.com/1018120 – Kaiido Apr 17 '21 at 11:35
1

It looks like Firefox has the ideal behaviour, though maybe not the correct one.

Nevertheless, as an alternative, you can use:

document.addEventListener('DOMContentLoaded', () => document.querySelector(window.location.hash));

and that will work in all browsers.

Rounin
  • 27,134
  • 9
  • 83
  • 108
  • _"It looks like Firefox has the ideal behaviour..."_ - How is the violation of the specification an _"ideal behaviour"_? – Andreas Apr 15 '21 at 09:17
  • 2
    @Andreas - Specs are neither infallible nor immutable. – Rounin Apr 15 '21 at 09:22
  • 1
    If I load the document with an empty location.hash, I get an error `Uncaught DOMException`. That means that I need to add a check before `querySelector`, nevertheless it is a good alternative to `:target`. – Baptistou Apr 15 '21 at 09:50
  • Re: _"That means that I need to add a check before `querySelector`"_. Right. You can straightforwardly write something like: `if (window.location.hash !== '') { console.log(document.querySelector(window.location.hash)); } else { console.log('The current URL contains no hashfragment.'); }`. – Rounin Apr 15 '21 at 12:37
  • This *will* not catch https://github.com/WICG/scroll-to-text-fragment (already there in Chrome) since the [`:target`](https://github.com/WICG/scroll-to-text-fragment#target) matches the parent-element of the text-fragment, but `location.hash` is not set. – Kaiido Apr 17 '21 at 11:12
  • @Andreas: Note that Baptistou has now corrected you above. Firefox is not the one in violation here. The old [selectors-api tests](https://dev.w3.org/2006/webapi/selectors-api-testsuite/level1-additional-report.html) show that Chrome and Safari are in violation. web-platform-tests contains a workaround that uses requestAnimationFrame(). – BoltClock Apr 17 '21 at 11:13