javascript - 如何检测以 javascript 结束的窗口滚动?

标签 javascript html scroll

我想在滚动结束后向所选元素添加一个类。如何在 JS 中检测滚动结束?

HTML

<ul class="list" id="wrapper">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li id="element">7</li>
    <li>8</li>
    <li>9</li>
    <li>10</li>
 </ul>

JS

const element = document.getElementById('element');
const y = element.getBoundingClientRect().top + window.scrollY;


window.scroll({
  top: y,
  behavior: 'smooth'
});

JSBIN EXAMPLE

最佳答案

2023 年更新:

现在有一个 scrollend event你可以听听。当滚动结束时,无论是用户滚动还是程序化平滑滚动结束,都会触发此事件。 浏览器支持还不是很好,但应该很快就会在所有地方可用。

if (!("onscrollend" in window)) {
  console.warn("Your browser doesn't support the onscrollend event");
}

const trigger = document.getElementById( 'trigger' );
const scroll_forcer = document.getElementById( 'scroll_forcer' );

let scrolling = false; // a simple flag letting us know if we're already scrolling
trigger.onclick = (evt) => startScroll();

function startScroll() {
  setTimeout(()=> {
    scroll_forcer.classList.add( "scrolling" )
    document.scrollingElement.scrollTo( { top: 1000, behavior: "smooth" } );
    document.addEventListener( "scrollend", (evt) => {
      scroll_forcer.classList.remove( "scrolling" );
    }, { once: true });
    }, 10);
};
#scroll_forcer {
  height: 5000vh;
  background-image: linear-gradient(to bottom, red, green);
  background-size: 100% 100px;
}
#scroll_forcer.scrolling {
  filter: grayscale(70%);
}
<button id="trigger">click to scroll</button>
<div id="scroll_forcer">
</div>

所以暂时我可能还需要之前的答案:

<小时/>

本身没有事件可以告诉平滑滚动何时结束。

对于如何发生这种平滑滚动,也没有标准行为,没有定义的持续时间,没有定义的计时函数(Chrome 使用缓入输出函数,而 Firefox 使用线性函数),并且添加了一个平滑滚动可以是 canceled在中间通过对滚动算法的其他调用,甚至根本没有效果......

所以这个检测并不那么容易。

我目前发现的最好方法是启动 requestAnimationFrame如果我们的目标位于同一位置,则动力循环将检查每个绘画帧(right after滚动操作)。一旦它在同一位置超过两帧,我们就假设滚动结束。这时我们可以检查它是否成功,只需检查我们是否处于预期位置即可:

const trigger = document.getElementById( 'trigger' );
const scroll_forcer = document.getElementById( 'scroll_forcer' );

let scrolling = false; // a simple flag letting us know if we're already scrolling
trigger.onclick = (evt) => startScroll();

function startScroll() {
  setTimeout(()=> {
    scroll_forcer.classList.add( "scrolling" )
    smoothScrollTo( { top: 1000 } )
      .catch( (err) => {
        /*
          here you can handle when the smooth-scroll
          gets disabled by an other scrolling
        */
        console.error( 'failed to scroll to target' );
      } )
      // all done, lower the flag
      .then( () => scroll_forcer.classList.remove( "scrolling" ) );
    }, 10);
};


/* 
 *
 * Promised based window.scrollTo( { behavior: 'smooth' } )
 * @param { Element } elem
 **  ::An Element on which we'll call scrollIntoView
 * @param { object } [options]
 **  ::An optional scrollToOptions dictionary
 * @return { Promise } (void)
 **  ::Resolves when the scrolling ends
 *
 */
function smoothScrollTo( options ) {
  return new Promise( (resolve, reject) => {
    const elem = document.scrollingElement;
    let same = 0; // a counter

    // last known scroll positions
    let lastPos_top = elem.scrollTop;
    let lastPos_left = elem.scrollLeft;

    // pass the user defined options along with our default
    const scrollOptions = Object.assign( {
        behavior: 'smooth',
        top: lastPos_top,
        left: lastPos_left
      }, options );

    // expected final position
    const maxScroll_top = elem.scrollHeight - elem.clientHeight;
    const maxScroll_left = elem.scrollWidth - elem.clientWidth;
    const targetPos_top = Math.max( 0, Math.min(  maxScroll_top, scrollOptions.top ) );
    const targetPos_left = Math.max( 0, Math.min( maxScroll_left, scrollOptions.left ) );

    // let's begin
    window.scrollTo( scrollOptions );
    requestAnimationFrame( check );
    
    // this function will be called every painting frame
    // for the duration of the smooth scroll operation
    function check() {
      // check our current position
      const newPos_top = elem.scrollTop;
      const newPos_left = elem.scrollLeft;
      // we add a 1px margin to be safe
      // (can happen with floating values + when reaching one end)
      const at_destination = Math.abs( newPos_top - targetPos_top) <= 1 &&
        Math.abs( newPos_left - targetPos_left ) <= 1;
      // same as previous
      if( newPos_top === lastPos_top &&
        newPos_left === lastPos_left ) {
        if( same ++ > 2 ) { // if it's more than two frames
          if( at_destination ) {
            return resolve();
          }
          return reject();
        }
      }
      else {
        same = 0; // reset our counter
        // remember our current position
        lastPos_top = newPos_top;
        lastPos_left = newPos_left;
      }
      // check again next painting frame
      requestAnimationFrame( check );
    }
  });
}
#scroll_forcer {
  height: 5000vh;
  background-image: linear-gradient(to bottom, red, green);
  background-size: 100% 100px;
}
#scroll_forcer.scrolling {
  filter: grayscale(70%);
}
.as-console-wrapper {
  max-height: calc( 50vh - 30px ) !important;
}
<button id="trigger">click to scroll</button>
<div id="scroll_forcer">
</div>

关于javascript - 如何检测以 javascript 结束的窗口滚动?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60000332/

相关文章:

javascript - 如何创建链接以预选表单选项

javascript - 确认对话框无法正常工作

javascript - 在 contenteditable div 中切换自定义元素

c# - 在asp.net中选中复选框时弹出窗口

javascript - 创建一些圆弧超过 180 度的饼图

ios - 表格 View 在其上方的 View 下滚动而不是与它一起滚动

ios - Ionic 3 不正确的滚动行为

javascript - 更改 ScrollReveal.js 视口(viewport)

javascript - 使用 Jquery animate 进行简单的 css 更改

javascript - 使用 angularjs 将单选按钮绑定(bind)到 ng-model