阻止用户一次滚动超过 1 个元素
Stop user from scrolling more than 1 element at once
问题
基本上我有一个有一些视频的移动网站。每个视频占用 100% 宽度和 100% 高度。我正在尝试制作一个滚动捕捉类型的提要,用户一次只能滚动一个视频,如果他们在滚动时到达页面上的某个点,它将捕捉到下一个视频。这有点像 tiktok 的视频提要或 instagram 卷轴作为一个想法。
我正在使用 scroll-snap 包,它有点让我达到了我想要实现的目标。缓慢滚动时它会卡住,但是如果我在移动设备上,我可以非常快速地滚动并允许滚动势头跳过视频。我正在努力做到这一点,以便用户无论滚动多用力,一次只能滚动 1 个视频。
这是正在发生的事情:https://streamable.com/f98slq。如您所见,我一次可以滚动超过 1 个视频。
我试过的
我试图在滚动提要时获取滚动速度。如果它高于某个值,我会应用一种停止页面滚动 10 毫秒的样式。这并没有真正起作用,因为即使滚动速度不高,滚动动量也能够滚动页面。不过我不太喜欢这个解决方案。
我不太确定解决此问题的最佳方法。如有任何反馈,我们将不胜感激。
这是我的代码:
// Scroll snap
useEffect(() => {
const { unbind } = createScrollSnap(MobileSnapContainer.current!, {
snapDestinationX: '0%',
snapDestinationY: '100%',
timeout: 0,
duration: 100,
threshold: 0.1,
snapStop: true,
}, () => {})
return () => unbind()
}, [])
return (
<>
<InfiniteScroll
dataLength={mobileVideos.length}
next={loadMore}
hasMore={hasMore}
loader={null}
scrollableTarget="container"
>
<div
className="overflow-auto select-none hide-scrollbar"
id="container"
ref={MobileSnapContainer}
>
{mobileVideos.map((data: video, index: number) => (
<SingleMobileVideo
index={index}
/>
))}
</div>
</InfiniteScroll>
</>
一种策略是检查某些元素的位置通过 运行ge 触发 滚动冻结 .
因此在滚动时,如果滚动的像素数量或多或少接近这些元素之一的位置,您可以将其冻结一段时间。
在开始我的演示之前,这里有四件事:
- 如何冻结卷轴?
- 为什么 运行ge?
- 什么时候解冻它?
- 一些注意事项...
如何冻结卷轴?
很简单...您可以在每次滚动事件中将 scrollTop
设置为固定值。
let freezed = true;
let noScrollElement = document.querySelector(".outer");
noScrollElement.scrollTop = 2920;
noScrollElement.addEventListener("scroll", function(e) {
if (freezed) {
e.target.scrollTop = 2920;
}
})
document.querySelector("button").addEventListener("click", function(e) {
freezed = false;
})
.outer {
height: 300px;
overflow: scroll;
}
.space {
height: 3000px;
}
<div class="outer">
<div class="space"></div>
<div>
No! You just can't scroll me! <button>Unfreeze</button>
</div>
<div class="space"></div>
</div>
为什么是 运行ge?
因为 scrollTop
值不会完全匹配元素的位置。所以你必须评估它周围的一定数量的像素。它估计 +/- 16px
即使鼠标滚轮 正常快速 旋转也很好。这使得 运行ge 为 32px。由你来调整它。 异常快速旋转几乎没有任何关系。
什么时候解冻?
你必须! .o(lol) 它可以在一定的延迟之后(就像我在我的演示中所做的那样)或者在视频播放一定时间之后。您必须查看视频 currentTime
。
一些注意事项...
虽然编写代码可能很有趣...您的用户很可能会非常不喜欢。所以不要滥用冻结时间。它应该是微妙的。
它最有趣的用例是关于超快速滚动...就像在移动设备上做一个快速向上滑动的手势,它可以在一秒钟内滚动 10 公里的页面。但遗憾的是,这种解决方案在这种情况下并不可靠。
下面是我演示的相关部分
可以在 replit
上 运行
// Some globals
let snapPrecision = 16; // pixels
let unsnapTime = 5000; // milliseconds
let freezed = false; // boolean to know if the scrolling is freezed
let freezedAt = 0; // Pixels where it is frozen
let lastSnaped = 0; // index of the sanppable element
// Have all position of the divs with className snap in state
const [snapPos, snapPosSetter] = React.useState([])
// If the page updates, like new elements appear,
// Update the positions array in the state
React.useEffect(() => {
// Get the position of the snap elements
let snapPos = [];
Array.from(document.querySelectorAll(".snap")).forEach(s => snapPos.push(s.getBoundingClientRect().top));
// Update the state
snapPosSetter(snapPos)
}, [])
// The scroll handler
function scrollHandler(e){
// console.log(e.target.scrollTop)
// Scrolled position
let scrolled = parseInt(e.target.scrollTop);
// If not freezed, check if there is a match between
// the scrolled position and all the snap items in state
if (!freezed) {
// If the scrolled position is withing +/- snapPrecision
// and not the last snapped
for (let i = 0; i < snapPos.length; i++) {
if (
scrolled + snapPrecision > snapPos[i] &&
scrolled - snapPrecision < snapPos[i] &&
lastSnaped != i
) {
freezed = true;
freezedAt = snapPos[i];
lastSnaped = i;
// Unsnap it in unsnapTime (milliseconds)
setTimeout(function () {
console.log("unfreezed");
freezed = false;
}, unsnapTime);
break;
}
}
}
// This is the part that really freezes the scroll
if (freezed) {
console.clear();
console.log("FREEZED at ", freezedAt);
// Force the scroll at a specific position!
e.target.scrollTop = freezedAt;
}
}
问题
基本上我有一个有一些视频的移动网站。每个视频占用 100% 宽度和 100% 高度。我正在尝试制作一个滚动捕捉类型的提要,用户一次只能滚动一个视频,如果他们在滚动时到达页面上的某个点,它将捕捉到下一个视频。这有点像 tiktok 的视频提要或 instagram 卷轴作为一个想法。
我正在使用 scroll-snap 包,它有点让我达到了我想要实现的目标。缓慢滚动时它会卡住,但是如果我在移动设备上,我可以非常快速地滚动并允许滚动势头跳过视频。我正在努力做到这一点,以便用户无论滚动多用力,一次只能滚动 1 个视频。
这是正在发生的事情:https://streamable.com/f98slq。如您所见,我一次可以滚动超过 1 个视频。
我试过的
我试图在滚动提要时获取滚动速度。如果它高于某个值,我会应用一种停止页面滚动 10 毫秒的样式。这并没有真正起作用,因为即使滚动速度不高,滚动动量也能够滚动页面。不过我不太喜欢这个解决方案。
我不太确定解决此问题的最佳方法。如有任何反馈,我们将不胜感激。
这是我的代码:
// Scroll snap
useEffect(() => {
const { unbind } = createScrollSnap(MobileSnapContainer.current!, {
snapDestinationX: '0%',
snapDestinationY: '100%',
timeout: 0,
duration: 100,
threshold: 0.1,
snapStop: true,
}, () => {})
return () => unbind()
}, [])
return (
<>
<InfiniteScroll
dataLength={mobileVideos.length}
next={loadMore}
hasMore={hasMore}
loader={null}
scrollableTarget="container"
>
<div
className="overflow-auto select-none hide-scrollbar"
id="container"
ref={MobileSnapContainer}
>
{mobileVideos.map((data: video, index: number) => (
<SingleMobileVideo
index={index}
/>
))}
</div>
</InfiniteScroll>
</>
一种策略是检查某些元素的位置通过 运行ge 触发 滚动冻结 .
因此在滚动时,如果滚动的像素数量或多或少接近这些元素之一的位置,您可以将其冻结一段时间。
在开始我的演示之前,这里有四件事:
- 如何冻结卷轴?
- 为什么 运行ge?
- 什么时候解冻它?
- 一些注意事项...
如何冻结卷轴?
很简单...您可以在每次滚动事件中将 scrollTop
设置为固定值。
let freezed = true;
let noScrollElement = document.querySelector(".outer");
noScrollElement.scrollTop = 2920;
noScrollElement.addEventListener("scroll", function(e) {
if (freezed) {
e.target.scrollTop = 2920;
}
})
document.querySelector("button").addEventListener("click", function(e) {
freezed = false;
})
.outer {
height: 300px;
overflow: scroll;
}
.space {
height: 3000px;
}
<div class="outer">
<div class="space"></div>
<div>
No! You just can't scroll me! <button>Unfreeze</button>
</div>
<div class="space"></div>
</div>
为什么是 运行ge?
因为 scrollTop
值不会完全匹配元素的位置。所以你必须评估它周围的一定数量的像素。它估计 +/- 16px
即使鼠标滚轮 正常快速 旋转也很好。这使得 运行ge 为 32px。由你来调整它。 异常快速旋转几乎没有任何关系。
什么时候解冻?
你必须! .o(lol) 它可以在一定的延迟之后(就像我在我的演示中所做的那样)或者在视频播放一定时间之后。您必须查看视频 currentTime
。
一些注意事项...
虽然编写代码可能很有趣...您的用户很可能会非常不喜欢。所以不要滥用冻结时间。它应该是微妙的。
它最有趣的用例是关于超快速滚动...就像在移动设备上做一个快速向上滑动的手势,它可以在一秒钟内滚动 10 公里的页面。但遗憾的是,这种解决方案在这种情况下并不可靠。
下面是我演示的相关部分
可以在 replit
上 运行// Some globals
let snapPrecision = 16; // pixels
let unsnapTime = 5000; // milliseconds
let freezed = false; // boolean to know if the scrolling is freezed
let freezedAt = 0; // Pixels where it is frozen
let lastSnaped = 0; // index of the sanppable element
// Have all position of the divs with className snap in state
const [snapPos, snapPosSetter] = React.useState([])
// If the page updates, like new elements appear,
// Update the positions array in the state
React.useEffect(() => {
// Get the position of the snap elements
let snapPos = [];
Array.from(document.querySelectorAll(".snap")).forEach(s => snapPos.push(s.getBoundingClientRect().top));
// Update the state
snapPosSetter(snapPos)
}, [])
// The scroll handler
function scrollHandler(e){
// console.log(e.target.scrollTop)
// Scrolled position
let scrolled = parseInt(e.target.scrollTop);
// If not freezed, check if there is a match between
// the scrolled position and all the snap items in state
if (!freezed) {
// If the scrolled position is withing +/- snapPrecision
// and not the last snapped
for (let i = 0; i < snapPos.length; i++) {
if (
scrolled + snapPrecision > snapPos[i] &&
scrolled - snapPrecision < snapPos[i] &&
lastSnaped != i
) {
freezed = true;
freezedAt = snapPos[i];
lastSnaped = i;
// Unsnap it in unsnapTime (milliseconds)
setTimeout(function () {
console.log("unfreezed");
freezed = false;
}, unsnapTime);
break;
}
}
}
// This is the part that really freezes the scroll
if (freezed) {
console.clear();
console.log("FREEZED at ", freezedAt);
// Force the scroll at a specific position!
e.target.scrollTop = freezedAt;
}
}