Javascript怎样改变window.location.hash而不滚动页面,但仍能触发CSS :target伪类?

4
据我所知,有两种方法可以更改URL末尾的哈希值:window.location.hashhistory.pushState。但是,history.pushState不会触发CSS的:target 伪类,因此不可行。 修改document.location.hash而不导致页面滚动的方法的答案演示了一种修改location.hash的方法,但这种解决方法无法触发具有匹配ID的元素上的:target伪类。
下面是一个简单的示例。有两个链接按预期工作::target被触发并显示选项卡,但如果需要,它们也会滚动到视图中。另外两个链接不起作用::target未被触发,但滚动被阻止。

(function() {
  function setHash(event) {
    event.preventDefault();
    var decoy = document.querySelector(".dummy-tabpanel");
    var tabId = event.currentTarget.getAttribute('href');
    var id = tabId.replace( /^#/, '' );
    var tabPanel = document.getElementById(id);
    
    decoy.style.top = document.body.scrollTop + 'px';
    
    tabPanel.setAttribute('id', '');
    decoy.setAttribute('id', id);
    
    window.location.hash = tabId;
    
    decoy.setAttribute('id', '');
    tabPanel.setAttribute('id', id);
    
    return false;
  }
  
  function setHashDirectly(event) {
    event.preventDefault();
    var tabId = event.currentTarget.getAttribute('href');
    window.location.hash = tabId;
    return false;
  }
  
  var tabLinks = document.querySelectorAll('a[href^="#"]');
  
  for (var tabLink of tabLinks) {
    tabLink.addEventListener("click", tabLink.classList.contains('direct') ? setHashDirectly : setHash );
  }
})()
.tabs {
  margin-top: 300px; // attempt to make scrolling necessary
}

.tabs [role="tabpanel"] {
  display: none;
}

.tabs [role="tabpanel"]:target {
  display: block;
}

.dummy-tabpanel {
  position: absolute;
  visibility: hidden;
}
<nav>
  <ul>
    <li>
      <a href="#one">one (doesn't scroll or trigger <code>:target</code>)</a>
    </li>
    <li>
      <a href="#two">two (doesn't scroll or trigger <code>:target</code>)</a>
    </li>
    <li>
      <a href="#three" class="direct">three (triggers <code>:target</code> but scrolls)</a>
    </li>
    <li>
      <a href="#four" class="direct">four (triggers <code>:target</code> but scrolls)</a>
    </li>
  </ul>
</nav>
<div class="tabs">
  <div role="tabpanel" id="one">
    this is the first tab (won't display)
  </div>
  <div role="tabpanel" id="two">
    this is the two tab (won't display)
  </div>
  <div role="tabpanel" id="three">
    this is the third tab (should display)
  </div>
  <div role="tabpanel" id="four">
    this is the forth tab (should display)
  </div>
</div>
<div class="dummy-tabpanel"></div>

有没有一种方法可以实现"两全其美",即更改窗口哈希值而不滚动页面,并且触发CSS的:target伪类?
注意:我已在OS X 10.13上测试了Chrome 64和Firefox 58。

你可以为每个元素添加一个类或ID,并在其上使用CSS。 - vityavv
换句话说,您希望通过链接通过:target显示隐藏内容,但是您不想跳转到:target目标吗? - zer00ne
@zer00ne 没错,但我想避免添加和删除类或修改内联样式。 - Tom
1个回答

1
实际上,history.pushState 可能应该更新CSS的 :target 选择器,因为在使用浏览器的前进和后退按钮导航后,使用 history.pushState 确实会更新它。这在webkit中有 一个未解决的bug,并且有 关于标准化行为的讨论,而不是当前的不一致行为。如果他们选择更新CSS,那么你的代码将像原来一样工作。如果他们选择不更新CSS,则浏览器将不得不更改后退和前进按钮的行为,不更新由 history.pushState 创建的条目的CSS,或者可能是任何条目,这似乎是一个明显错误的举动。
因此,你的代码可能在未来无需更改即可正常工作,但目前你需要通过调用history.pushState并让用户进行前进和后退操作来解决这个问题。以下是基于GitHub上laughinghan的解决方案的一个解决方法:
function pushHashAndFixTargetSelector(hash) {
    history.pushState({}, document.title, hash); //called as you would normally
    const onpopstate = window.onpopstate; //store the old event handler to restore it later
    window.onpopstate = function() { //this will be called when we call history.back()
        window.onpopstate = onpopstate; //restore the original handler
        history.forward(); //go forward again to update the CSS
    };
    history.back(); //go back to trigger the above function
}

理论上,即使他们标准化了预期的行为,这种解决方法仍将继续起作用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接