0

问题

基本上我有一个有一些视频的移动网站。每个视频占用 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>
      </>
4

1 回答 1

1

一种策略是检查某些元素的位置是否通过触发滚动冻结的范围

所以在滚动时,如果滚动的像素量或多或少接近这些元素之一的位置,您可以将其冻结一段时间。

在我们进行演示之前,这里有四件事:

  1. 如何冻结卷轴?
  2. 为什么是范围
  3. 什么时候解冻
  4. 一些考虑...

如何冻结卷轴?

很简单...您可以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>

为什么是范围?

因为该scrollTop值不会与元素的位置完全匹配。所以你必须评估它周围的一定数量的像素。即使鼠标滚轮正常快速旋转,它估计 +/-16px也相当不错。这使得范围为 32px。由你来调整。与异常快速旋转无关。

什么时候解冻?

你必须!.o(lol)它可以在一定的延迟之后(就像我在我的演示中所做的那样)或在视频播放了一定时间之后。你必须检查视频currentTime

一些考虑...

虽然编写代码可能很有趣......您的用户很可能会不喜欢地狱。所以不要滥用冻结时间。它应该是微妙的。

最有趣的用例是关于超快速滚动......就像在移动设备上快速向上滑动手势,每秒滚动 10 公里页面。但遗憾的是,这种解决方案对于这种情况并不可靠。

现在是我演示的相关部分

哪个可以在repli上运行

// 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;
  }
}
于 2022-02-01T05:20:35.117 回答