判断一个snap-scroll元素的snap滚动事件是否完成

问题描述 投票:0回答:2

摘要

我正在使用可滚动元素创建图像库。我正在使用 CSS 的

scroll-snap
功能,它允许我捕捉到滚动条中的元素(图像)。

通过绑定到元素的

scroll
事件,我在用户滚动元素时应用各种操作(例如预加载、隐藏界面元素等)。其中之一取决于滚动事件,并且需要在“确切时刻”滚动完成时停止。但是滚动捕捉给我带来了一种无法预见但尚未处理的情况;

我无法

准确判断快速滚动动作是否完成。 我可以在每个滚动上设置一个

setTimeout

,它会自行取消并重新设置 - 有效地消除抖动 - 如果不重置,最后会被调用。但是设置此设置时使用的超时可能意味着您在确定滚动完成时“为时已晚”。

底线:如何检查滚动是否完成,因为:

用户已停止滚动,或者;
  1. 滚动条已到达捕捉点(
  2. scroll-snap-type
  3. 已设置)
    
        
javascript css animation easing scroll-snap
2个回答
25
投票
offsetWidth

更改为

offsetHeight
,将
scrollLeft
更改为
scrollTop
,以适应垂直滚动条。)
function scrollHandler(e) {
    var atSnappingPoint = e.target.scrollLeft % e.target.offsetWidth === 0;
    var timeOut         = atSnappingPoint ? 0 : 150; //see notes

    clearTimeout(e.target.scrollTimeout); //clear previous timeout

    e.target.scrollTimeout = setTimeout(function() {
        console.log('Scrolling stopped!');
    }, timeOut);
}

myElement.addEventListener('scroll', scrollHandler);

故障

通过使用滚动元素自己的

width

(或垂直滚动情况下的高度),我们可以通过将元素的滚动位置(在我的例子中为scrollLeft)除以其宽度(

offsetWidth
)来计算它是否已到达其捕捉点,如果产生
一个圆形整数
(意思是:宽度“适合”滚动位置恰好x次),则它已到达捕捉点。我们通过使用 余数运算符 : 来做到这一点 var atSnappingPoint = e.target.scrollLeft % e.target.offsetWidth === 0;

然后,如果到达捕捉点,则将 
timeOut

(在滚动完成时应触发的

setTimeout
中使用)设置为 0。否则,使用常规值(在上面的示例中为 150,请参阅注释) .
这是有效的,因为当元素实际到达其捕捉点时,最后一个 

scroll

事件被触发,并且我们的处理程序(再次)被触发。将

timeOut
调整为 0 将
立即
参见 mdn)调用我们的超时函数;因此,当滚动条“击中”捕捉点时,我们会立即知道。 演示

下面是一个工作示例:

function scrollHandler(e) { var atSnappingPoint = e.target.scrollLeft % e.target.offsetWidth === 0; var timeOut = atSnappingPoint ? 0 : 150; //see notes clearTimeout(e.target.scrollTimeout); //clear previous timeout e.target.scrollTimeout = setTimeout(function() { //using the timeOut to evaluate scrolling state if (!timeOut) { console.log('Scroller snapped!'); } else { console.log('User stopped scrolling.'); } }, timeOut); } myElement = document.getElementById('scroller'); myElement.addEventListener('scroll', scrollHandler);
.scroller {
  display: block;
  width: 400px;
  height: 100px;
  
  overflow-scrolling: touch;
  -webkit-overflow-scrolling: touch;
  overflow-anchor: none;
  overflow-x: scroll;
  overflow-y: hidden;

  scroll-snap-type: x mandatory;
  scroll-snap-stop: normal;
  scroll-behavior: auto;
 }
 .scroller-canvas {
  position: relative;
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
 }
 .scroller-canvas > * {
  position: relative;
  display: inline-flex;
  flex-flow: column;
  flex-basis: 100%;
  flex-shrink: 0;
  
  width: 400px;
  height: 100px;
  
  scroll-snap-align: start;
  scroll-snap-stop: normal;
 }
 .scroller-canvas > *:nth-child(even) {
  background-color: #666;
  color: #FFF;
 }

/* stackoverflow code wrapper fix */
.as-console-wrapper { max-height: 50px !important; }
<div class="scroller" id="scroller">
  <div class="scroller-canvas">
    <div class="slide" id="0">Slide 1</div>
    <div class="slide" id="1">Slide 2</div>
    <div class="slide" id="2">Slide 3</div>
    <div class="slide" id="3">Slide 4</div>
    <div class="slide" id="4">Slide 5</div>
  </div>
</div>

注释

在捕捉点上滚动

虽然可以通过滚动过去来“击中”捕捉点,但可以通过在设置timeOut时(计算和)考虑滚动速度来缓解这种情况,例如当速度不接近零时,将其保持在 150。

像素比

:如果计算混乱(1 px 的余数等,因此该函数无法正确解析),则可能存在一些缩放/盒模型问题,这些问题会扰乱滚动位置和offsetWidth 计算。 这很可能是由设备的像素比引起的,因此您可以尝试通过将这些值乘以像素比来“纠正”这些值(向左滚动和宽度)。 重要提示:事实证明,像素比不仅表明用户是否拥有高 dpi 屏幕,而且还会在用户缩放页面时发生变化

timeout

当滚动尚未达到捕捉点时使用的任意 timeOut 为 150。这个时间足够长,可以防止在 Safari @ iOS 完成滚动之前触发它(它使用贝塞尔曲线进行滚动捕捉,这会产生一个非常长的“最后一帧”,大约 120-130 毫秒),并且足够短,可以在用户在捕捉点之间暂停滚动时产生可接受的结果。

scroll-padding

如果您在滚动元素上设置了scroll-padding,则在确定捕捉点时需要考虑到这一点。

剩余像素

:您甚至可以进一步分解,以计算到达捕捉点之前剩余的像素: var pxRemain = e.target.scrollLeft % e.target.offsetWidth; var atSnappingPoint = pxRemain === 0;

但请注意,您需要从元素的宽度中减去该宽度,具体取决于您滚动的方式。这需要您计算滚动的距离并检查它是负数还是正数。那么它就会变成:

var distance = e.target.scrollLeft - (e.target.prevScrollLeft ? e.target.prevScrollLeft : 0); var pxRemain = e.target.scrollLeft % e.target.offsetWidth; pxRemain = (pxRemain === 0) ? 0 : ((distance > 0) ? pxRemain : elementWidth - pxRemain); var atSnappingPoint = pxRemain === 0; //store scroll position for next iteration, to calculate distance e.target.prevScrollLeft = e.target.scrollLeft;

仅限拍照

编写此脚本时考虑了两种情况:

该元素已捕捉到其捕捉点,或者;
  1. 用户已暂停滚动(或捕捉检测以某种方式出错)
  2. 如果只需要前者,则不需要超时,直接写:

function scrollHandler(e) { if (e.target.scrollLeft % e.target.offsetWidth === 0) { console.log('Scrolling is done!'); } } myElement.addEventListener('scroll', scrollHandler);



2
投票
scrollend

活动就足够了。

document.querySelector('#cont').addEventListener('scrollend', () => console.log('snap ended'))
#cont {
  width: 100%;
  height: 100px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

div > div {
  scroll-snap-align: start;
display:flex;
justify-content:center;
align-items:center;
border:solid red 2px;
box-sizing:border-box;
}
<div id='cont'>
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>

© www.soinside.com 2019 - 2024. All rights reserved.