我终于,明确地解决了这个脑筋急转弯。解决起来比我最初想象的要简单得多。(注意:在我的情况下,它适用于水平滚动条;在示例中更改offsetWidth
为offsetHeight
和scrollLeft
toscrollTop
以适应垂直滚动条。)
function scrollHandler(e) {
var atSnappingPoint = e.target.scrollLeft % e.target.offsetWidth === 0;
var timeOut = atSnappingPoint ? 0 : 150; //see notes
clearTimeout(e.target.scrollTimeout);
e.target.scrollTimeout = setTimeout(function() {
console.log('Scrolling is done!');
}, timeOut);
}
myElement.addEventListener('scroll', scrollHandler);
分解
通过使用滚动元素自己的宽度(或垂直滚动时的高度),我们可以通过将元素的滚动位置(在我的例子中)除scrollLeft
以其宽度(含义:宽度“适合”滚动位置恰好 x 次)它已到达捕捉点。我们通过使用余数运算符来做到这一点:offsetWidth
var atSnappingPoint = e.target.scrollLeft % e.target.offsetWidth === 0;
然后,如果达到捕捉点,则将timeOut
(在setTimeout
滚动完成时应该触发的那个中使用)设置为 0。否则,使用常规值(在上面的示例中为 150,请参阅注释)。
这是因为当元素实际到达其捕捉点时,scroll
会触发最后一个事件,并且(再次)触发我们的处理程序。将调整timeOut
为 0 将立即(参见 mdn)调用我们的超时函数;因此,当滚动条“击中”捕捉点时,我们会立即知道这一点。
笔记
滚动捕捉点我还没有实现/考虑到你可以通过滚动来“命中”捕捉点这一事实。不过,这种情况极为罕见。当我找到解决方案时,我会更新答案。
像素比:如果你搞砸了计算(剩余的 1 px 等,所以函数不能正确解析),你可能有一些缩放/框模型问题,这些问题会搞乱滚动位置和 offsetWidth 计算的计算. 这是由设备的像素比率引起的,因此您可以尝试通过将这些值乘以像素比率来“纠正”这些值(向左滚动和宽度)。重要提示:事实证明,像素比率不仅表明用户是否拥有高 dpi 屏幕,而且在用户缩放页面时也会发生变化。
timeouttimeOut
滚动尚未达到捕捉点时使用的任意值是 150。这足以防止在 Safari @ iOS 完成滚动之前触发它(它使用贝塞尔曲线进行滚动捕捉,这会产生非常长的“最后一帧” ' 大约 120-130 毫秒)并且足够短以在用户在捕捉点之间暂停滚动时产生可接受的结果。
如果您在滚动元素上设置了滚动填充scroll-padding
,则在确定捕捉点时需要考虑到这一点。
剩余像素:您甚至可以进一步分解,以计算到达捕捉点之前剩余的像素:
var pxRemain = e.target.scrollLeft % e.target.offsetWidth;
var atSnappingPoint = pxRemain === 0;
但请注意,您需要从元素的宽度中减去它,具体取决于您滚动的方式。这需要您计算滚动的距离,并检查它是负数还是正数。那么它会变成:
var pxRemain = e.target.scrollLeft % e.target.offsetWidth;
pxRemain = (pxRemain === 0) ? 0 : ((distance > 0) ? pxRemain : elementWidth - pxRemain);
var atSnappingPoint = pxRemain === 0;
仅捕捉
编写此脚本是为了考虑两种情况:
- 该元素已捕捉到其捕捉点,或者;
- 用户已暂停滚动(或捕捉检测不知何故出错)
如果你只需要前者,你不需要超时,你可以写:
function scrollHandler(e) {
if (e.target.scrollLeft % e.target.offsetWidth === 0) {
console.log('Scrolling is done!');
}
}
myElement.addEventListener('scroll', scrollHandler);