我正在使用可滚动元素创建图像库。我正在使用 CSS 的
scroll-snap
功能,它允许我捕捉到滚动条中的元素(图像)。
通过绑定到元素的
scroll
事件,我在用户滚动元素时应用各种操作(例如预加载、隐藏界面元素等)。其中之一取决于滚动事件,并且需要在“确切时刻”滚动完成时停止。但是滚动捕捉给我带来了一种无法预见但尚未处理的情况;
我无法准确判断快速滚动动作是否完成。 我可以在每个滚动上设置一个
setTimeout
,它会自行取消并重新设置 - 有效地消除抖动 - 如果不重置,最后会被调用。但是设置此设置时使用的超时可能意味着您在确定滚动完成时“为时已晚”。
底线:如何检查滚动是否完成,因为:用户已停止滚动,或者;
scroll-snap-type
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);
故障
(或垂直滚动情况下的高度),我们可以通过将元素的滚动位置(在我的例子中为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
,则在确定捕捉点时需要考虑到这一点。
:您甚至可以进一步分解,以计算到达捕捉点之前剩余的像素:
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;
仅限拍照
该元素已捕捉到其捕捉点,或者;
function scrollHandler(e) {
if (e.target.scrollLeft % e.target.offsetWidth === 0) {
console.log('Scrolling is done!');
}
}
myElement.addEventListener('scroll', scrollHandler);
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>