如何检测自动滚动到锚点?

Posted

技术标签:

【中文标题】如何检测自动滚动到锚点?【英文标题】:How to detect automatic scrolling to anchors? 【发布时间】:2015-12-31 06:06:40 【问题描述】:

我有一个单页网站,example.com。有两个部分:页面顶部的 intro 和页面底部的 contact。如果我想让某人访问 contact 部分而无需滚动浏览 intro,我会给他们这个链接:example.com/#contact .我在下面谈论这些访问。

浏览器自动向下滚动到contact部分,但是它忽略了页面顶部的固定导航,所以contact部分滚动到导航后面,所以contact 部分的顶部变得不可见。这就是我想使用 javascript 通过从滚动位置减去固定导航的高度来纠正的问题。让我们将此函数称为scrollCorrector。问题是我不知道这种自动滚动的确切时间,所以scrollCorrector 并不是每次都应该调用。

什么时候应该调用scrollCorrector?由于散列部分而发生自动滚动时。为什么不使用onscroll?因为这样我就无法区分自动滚动和用户滚动。为什么不在每个<a href="example.com/#contact"> 上使用onclick?我会使用它,但如果用户通过浏览器的后退按钮导航怎么办?好的,我也会使用onpopstate。但是,如果用户通过手动将 URL 重写为 example.com/#contact 而来自 example.com/#intro 怎么办?好的,我也会使用onhashchange。但是,如果用户已经在 example.com/#contact 上,点击地址栏,然后按 Enter 键而不做任何修改怎么办?以上都没有帮助。

那我应该听什么事件?如果不存在这样的事件,scrollCorrector 怎么会知道刚刚发生了自动滚动?

【问题讨论】:

问题是什么?什么是跳跃? (我可能是愚蠢的???) 我的意思是当浏览器滚动到文档中具有与 URL 的哈希部分匹配的 id 的特定元素时。 好的我想我现在明白了,但是你的问题很不清楚,你可能想更清楚地说明你的问题。没有内置功能可以执行您想要的操作,您可以执行类似***.com/questions/487073/… 的操作,检查滚动时锚点是否可见。然后,当它“跳跃”时,您可以触发自定义事件。你的意思是这样的吗? 关于锚点只触发一个事件,您可以直接将事件绑定到锚点。但不幸的是,当用户在地址栏中按下回车键而不进行任何更改时,您将很难触发任何类型的事件。 我同意,但我不知道如何才能更清楚地描述问题。稍后我可能会重写它,但现在是凌晨 2 点,我没喝咖啡了。 :) 无论如何,谢谢你的帮助。我知道如何检查元素是否在视口中。我不知道什么时候应该检查它。我的意思是我知道何时检查它,但不知道事件是否发生。可以检测到对链接的点击。可以检测到哈希变化。但是无法检测到导致浏览器再次滚动到指定元素的“哈希更改为相同的哈希”。至少我还没弄明白。 【参考方案1】:

滚动事件将触发,所以你可以,

检查您的实际location.hash,如果为空我们不在乎 debounce 事件以确保它不是触发它的鼠标滚轮 获取实际的document.querySelector(location.hash).getBoundingClientRect().top,如果是===0,请致电您的scrollCorrector

var AnchorScroller = function() 
  // a variable to keep track of last scroll event
  var last = -100, // we set it to -100 for the first call (on page load) be understood as an anchor call
    // a variable to keep our debounce timeout so that we can cancel it further
    timeout;

  this.debounce = function(e) 
    // first check if we've got a hash set, then if the last call to scroll was made more than 100ms ago
    if (location.hash !== '' && performance.now() - last > 100)
    // if so, set a timeout to be sure there is no other scroll coming
      timeout = setTimeout(shouldFire, 100);
    // that's not an anchor scroll, stop it right now !	
    else clearTimeout(timeout);
    // set the new timestamp
    last = performance.now();
  

  function shouldFire() 
    // a pointer to our anchored element
    var el = document.querySelector(window.location.hash);
    // if non-null (an other usage of the location.hash) and that it is at top of our viewport
    if (el && el.getBoundingClientRect().top === 0) 
      // it is an anchor scroll
      window.scrollTo(0, window.pageYOffset - 64);
    
  
;
window.onscroll = new AnchorScroller().debounce;
body 
  margin: 64px 0 0 0;

nav 
  background: blue;
  opacity: 0.7;
  position: fixed;
  top: 0;
  width: 100%;

a 
  color: #fff;
  float: left;
  font-size: 30px;
  height: 64px;
  line-height: 64px;
  text-align: center;
  text-decoration: none;
  width: 50%;

div 
  background: grey;
  border-top: 5px #0f0 dashed;
  font-size: 30px;
  padding: 25vh 0;
  text-align: center;

#intro,#contact   background: red;
<nav>
  <a href="#intro">Intro</a>
  <a href="#contact">Contact</a>
</nav>
<div id="intro">  Intro </div>
<div> Lorem </div>
<div id="contact">  Contact </div>
<div> Ipsum </div>

注意事项: - 它在滚动事件和更正之间引入了 100 毫秒的超时,这是可见的。 - 它不是 100% 防弹的,用户只能触发一个事件(通过鼠标滚轮或键盘)并恰好落在正确的位置,因此会产生误报。但发生这种情况的机会非常小,以至于这种行为可能是可以接受的。

【讨论】:

没关系,但是去抖动器应该调用什么类型的函数来判断它是否是自动滚动?一旦我知道了这一点,就很容易实现您在第 2 点和第 3 点中提到的scrollCorrector 逻辑。但只要我无法区分用户滚动和自动滚动,就不能这样做。所以我还在寻找另一种方法,因为这似乎是不可能的。 ***.com/a/7210072/1293492 好吧,我指的是 David Walsh 的文章,因为他确实使用了巧妙的超时来避免多次调用发生在短时间(大约几毫秒)。您可以调整此函数以检测是否在间隔中触发了其他滚动事件。如果是这样,它来自用户操作,您应该返回该函数。否则,请进行其他检查。它可能不是 100% 防弹的,但是很难通过鼠标滚轮只产生一个滚动事件并恰好落在哈希链接元素的 scrollHeight 上。也许这个误差范围是可以接受的? Ps : 有空的时候我可能会把整个函数写给你 现在很清楚了。只要浏览器不平滑这种类型的滚动,而是直接“跳转”到锚定元素,这是一个好主意。我检查了从 android 浏览器到 Edge 再到 Chrome 的所有内容,幸运的是它们都没有使用平滑滚动,所以这个想法显然是赢家,谢谢。这是简化的实现:jsfiddle.net/36yw1x5z/1【参考方案2】:

我看过了,是的,我可以看到你提到的使用 window.onhashchange 的限制。

我明白你想要什么,但我不认为这样的事情存在。

这是我想出的最好的(完全放弃 hashchange):

<html>
<head>
<script>
"use strict";
(function () 
    window.myFunc = function(href) 
        window.alert("Link clicked, hash is: " + href);
    ;
    window.alert("Page just reloaded, hash is: " + window.location.hash);
)();
</script>
</head>
<body>
<a href="#a" onclick="myFunc(this.hash)">a</a><br />
<a href="#b" onclick="myFunc(this.hash)">b</a>
<h1 id="a">a</a>
<h1 id="b">b</a>
</body>
</html>

【讨论】:

谢谢,但这只会在重新加载和链接点击时发出警报。尝试在 Mozilla Firefox 或 Microsoft Edge 中打开它,以便看到 example.com/index.html#b。无需单击或重新加载即可滚动到#a。现在点击地址栏,不要改变任何东西,直接回车。您会看到浏览器再次滚动到#b,没有任何警报。那就是问题所在。我试过onloadonhashchangeonpopstate,但这些都没有帮助。我会尝试以某种方式使用onscroll... 你是对的。我在 chrome 中对其进行了测试,在 chrome 中它可以满足您的要求。

以上是关于如何检测自动滚动到锚点?的主要内容,如果未能解决你的问题,请参考以下文章

js实现进入页面后自动跳转到锚点

滚动到锚点,同时保持散列

Vue路由器导航或滚动到锚点(#锚点)

无法滚动到锚点

Angular4 - 滚动到锚点

javascript 平滑滚动到锚点