JavaScript滚动事件监听器

60

6
如果(允许jQuery),请访问http://api.jquery.com/scroll/。该网址是jQuery的滚动方法文档,提供了在网页中滚动元素的指南。 - Whymarrh
6个回答

135

对于那些希望找到不涉及jQuery的答案的人,你可以使用普通事件监听来钩入window的“scroll”事件。假设我们想要将滚动监听添加到多个可通过CSS选择器选择的元素中:

// what should we do when scrolling occurs
function runOnScroll(element) {
  // not the most exciting thing, but a thing nonetheless
  console.log(element);
};

// grab elements as array, rather than as NodeList
const elements = document.querySelectorAll(`...`);

// and then make each element do something on scroll
elements.forEach(element => {
  window.addEventListener(
    "scroll",
    () => runOnScroll(element),
    { passive: true }
  );
});
或者,您可以绑定一个单一的滚动监听器,将evt => runOnScroll(evt)作为处理程序,然后在runOnScroll函数中确定对elements中的所有内容要执行什么操作。

请注意,我们使用了passive属性来告诉浏览器,该事件不会干扰滚动本身。如果我们想要平滑的滚动行为(并且这也意味着我们不应该在滚动期间执行重新流程触发的DOM更新),这很重要。

作为额外加分项,您可以给滚动处理程序添加锁定机制,以便在我们已经滚动时它不会运行:

// global lock, so put this code in a closure of some sort so you're not polluting.
let locked = false;
let lastCall = false;

function runOnScroll(element) {
  if(locked) return;

  if (lastCall) clearTimeout(lastCall);
  lastCall = setTimeout(() => {
    runOnScroll(element);
    // you do this because you want to handle the last
    // scroll event, even if it occurred while another
    // event was being processed.
  }, 200);

  // ...your code goes here...

  locked = false;
};

只是好奇 - 不确定是否重要 - 但是对于滚动事件监听器,useCapture应该返回false还是true? - beefchimi
1
不设置 useCapture 是一种确保处理程序在任何处理程序由于冒泡而获取事件之前触发的方法,或者是“我不关心它被调用的顺序”。然而,对于这个目的来说并不太有用,因为当使用多个具有 useCapture=true 的侦听器时,没有调用顺序保证。 - Mike 'Pomax' Kamermans
2
非常干净,但是这样做会不会降低滚动性能,因为我们正在添加一个监听器来选择多少个元素?我认为我们应该集中onScroll逻辑,以便我们可以管理执行的内容。你有什么发现吗? - gdbj
非常正确,因此为了提高性能,您可以将其转换为标准的“事件监听器仅设置标志”(其中“标志”只是滚动目标值),并使用独立调度的“处理滚动数据”函数在超时时调用它(如果它没有运行,“滚动”事件会启动它,如果它已经运行,则会取消其运行,如果它看不到要处理的新滚动数据)。 - Mike 'Pomax' Kamermans
帮我理解一下。你有一个function(element),它接受element作为参数,但是这个变量从来没有被使用过?你总是将事件监听器添加到window上。 - oliversisson
显示剩余2条评论

6
我一直在寻找使用旧版JS(无需JQuery)的固定菜单的解决方案。因此,我建立了一个小测试来尝试它。我认为这对那些正在寻找JS解决方案的人很有帮助。
需要改进的是将菜单粘回去,并使其更加平滑。同时,我发现了一个不错的JQuery解决方案,它复制原始div而不是使用position fixed,这样更好,因为修复后无需替换页面的其他元素。 有谁知道如何用JS实现这个功能呢?
请注意,纠正和改进。
<!DOCTYPE html>
<html>

<head>
<script>

// addEvent function by John Resig:
// http://ejohn.org/projects/flexible-javascript-events/

function addEvent( obj, type, fn ) {

    if ( obj.attachEvent ) {

        obj['e'+type+fn] = fn;
        obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
        obj.attachEvent( 'on'+type, obj[type+fn] );
    } else {
        obj.addEventListener( type, fn, false );
    }
}
function getScrollY() {
    var  scrOfY = 0;
    if( typeof( window.pageYOffset ) == 'number' ) {
        //Netscape compliant
        scrOfY = window.pageYOffset;

    } else if( document.body && document.body.scrollTop )  {
        //DOM compliant
        scrOfY = document.body.scrollTop;
    } 
    return scrOfY;
}
</script>
<style>
#mydiv {
    height:100px;
    width:100%;
}
#fdiv {
    height:100px;
    width:100%;
}
</style>
</head>

<body>

<!-- HTML for example event goes here -->

<div id="fdiv" style="background-color:red;position:fix">
</div>
<div id="mydiv" style="background-color:yellow">
</div>
<div id="fdiv" style="background-color:green">
</div>

<script>

// Script for example event goes here

addEvent(window, 'scroll', function(event) {

    var x = document.getElementById("mydiv");

    var y = getScrollY();      
    if (y >= 100) {
        x.style.position = "fixed"; 
        x.style.top= "0";
    } 
});

</script>
</body>
</html>

4
以下基本方法不足以满足您的要求吗?
具有
的HTML代码。
<div id="mydiv" onscroll='myMethod();'>


JS将有以下代码

function myMethod(){ alert(1); }

5
翻译:从严格意义上来说,这是一个“函数”,而不是一个“方法” :-) - Julian F. Weinert
3
这取决于myMethod是否是全局的—— window.myMethod ;)。 - Maxime Dupré

3
如果滚动事件由于某些原因无法工作,则尝试使用滚轮事件:
document.addEventListener('wheel', (event) => {console.log('scrolled')});

4
如果您隐藏主体级别的溢出并将其替换为连接到div的滚动条,则“scroll”事件似乎无法正常工作。“wheel”事件在此情况下有效。 - JkAlombro

0
有没有一个JS监听器可以用于特定文本框中用户滚动时的事件?
DOM L3 UI Events规范给出了初始定义,但已被认为过时。
要添加单个处理程序,您可以执行以下操作:
  let isTicking;
  const debounce = (callback, evt) => {
    if (isTicking) return;
    requestAnimationFrame(() => {
      callback(evt);
      isTicking = false;
    });
    isTicking = true;
  };
  const handleScroll = evt => console.log(evt, window.scrollX, window.scrollY);
  document.defaultView.onscroll = evt => debounce(handleScroll, evt);

如果有多个处理程序或者出于样式原因更喜欢的话,可以使用 addEventListener 而不是像上面所示那样将处理程序分配给 onscroll

如果使用类似于 lodash 的 \_.debounce,你可能可以轻松搞定:

const handleScroll = evt => console.log(evt, window.scrollX, window.scrollY);
document.defaultView.onscroll = evt => _.debounce(() => handleScroll(evt));

请查看浏览器兼容性,并在完成前务必在一些实际设备上进行测试。


-1
let lastKnownScrollPosition = 0;
let ticking = false;

function doSomething(scrollPos) {
  // Do something with the scroll position
}

document.addEventListener('scroll', function(e) {
  lastKnownScrollPosition = window.scrollY;

  if (!ticking) {
    window.requestAnimationFrame(function() {
      doSomething(lastKnownScrollPosition);
      ticking = false;
    });

    ticking = true;
  }
});

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