A jump link is a path that ends with a hash, e.g. /mypage#heading
. After navigating to the path portion (e.g. /mypage
), the browser will:
- Look for an element with an ID that matches the hash (e.g.
#heading
would be matched by<h1 id="heading">
) and if one is found will scroll (or "jump") that element into view - Make the matching element have the CSS
:target
pseudoselector state
The first item is easy to implement with $element.scrollIntoView
.
It’s also easy to change the appearance of the target element by using JS to toggle CSS classes and HTML attributes.
But I can’t figure out how to use JavaScript to make an element have the :target
pseudoselector state. For accessibility reasons (and for my own curiosity) I want to reproduce the browser’s native jump link behavior, not something that just looks like it.
Manually dispatching HashChangeEvent('hashchange')
doesn’t appear to have the desired effect.
Note when you run the demo code below: if you click a jump link for an element on the current "page" then that element is correctly highlighted, but if you click a jump link for an element on a different page then it is not highlighted, indicating :target
is not set.
How can JavaScript make an element have the CSS :target
pseudoselector state?
for (const $anchor of document.querySelectorAll(`a`)) {
$anchor.innerHTML = $anchor.getAttribute(`href`);
$anchor.addEventListener(`click`, onClick);
}
render(`/js`); // This is the path StackOverflow uses to run demo code snippets
function onClick(event) {
const [path, hash] = event.target.getAttribute(`href`).split(`#`);
if (path === location.pathname) {
return;
}
event.preventDefault();
window.history.pushState({}, ``, path);
render(path);
if (hash) {
const $target = document.getElementById(hash);
if ($target) {
$target.scrollIntoView();
}
window.dispatchEvent(new HashChangeEvent(`hashchange`));
}
}
function render(pageName) {
const page = document.getElementById(pageName).innerHTML;
document.getElementById(`output`).innerHTML = page;
}
#output {
height: 200px;
outline: 1px solid #00000060;
width: 300px;
}
*:target {
background: gold;
}
<template id="/js">
<h1 id="target1">Target 1</h1>
<h1 id="target2">Target 2</h1>
</template>
<template id="/other">
<h1 id="target3">Target 3</h1>
<h1 id="target4">Target 4</h1>
</template>
<ul>
<li><a href="/js#target1" target="_self"></a></li>
<li><a href="/js#target2" target="_self"></a></li>
<li><a href="/other#target3" target="_self"></a></li>
<li><a href="/other#target4" target="_self"></a></li>
</ul>
<p>Current "page":</p>
<div id="output"></div>
2
Answers
I don’t think you can do that. But for your problem, a simple solution can solve it without modify
:target
.Since you let browser do native page jump if you have same pathname. I suggest you re-click the link when you rendered new page instead of using
HashChangeEvent(
hashchange)
This is how I do it, hope it’ll help
the CSS
:target
pseudo selector will simply grab whatever the current URL hash is and if that hash is a valid htmlid
present on the page, it will mark that element as "target". it doesn’t matter how its added, be it by clicking a link with hash, or a hash directly added on the link when opening, or hash updated logically via JS, it simply listen for url hash,the reason it directly works without javascript is simply because if you click on a link with hash in it e.i.
<a href="#t1">Test 1</a>
the hash will be appended on the URL by default.Here’s a simple example,
window.location.hash = target.getAttribute("href")
it will not work because the hash is not appended on the URL