简体   繁体   English

阻止用户一次滚动超过 1 个元素

[英]Stop user from scrolling more than 1 element at once

The problem问题

Basically I have a mobile website that has some videos.基本上我有一个有一些视频的移动网站。 Each video takes 100% width and 100% height.每个视频占用 100% 的宽度和 100% 的高度。 I'm trying to make a scroll snap type feed, where the user can scroll only 1 video at a time and if they reach a certain point on the page while scrolling, it will snap to the next video.我正在尝试制作滚动捕捉类型的提要,用户一次只能滚动一个视频,如果他们在滚动时到达页面上的某个点,它将捕捉到下一个视频。 It's kinda like tiktok's video feed or instagram reels as an idea.这有点像 tiktok 的视频提要或 instagram 卷轴作为一个想法。

I'm using the scroll-snap package which kinda gets my half way to what I'm trying to achieve.我正在使用滚动快照 package 这有点让我达到了我想要实现的目标。 It snaps when scrolling slowly, however if I was on mobile, I can just scroll really fast and allow the scroll momentum to skip videos.缓慢滚动时它会卡住,但是如果我在移动设备上,我可以非常快速地滚动并让滚动势头跳过视频。 I'm trying to make it so the user is only able to scroll 1 video at a time no matter how hard they scroll.我正在努力做到这一点,以便用户一次只能滚动 1 个视频,无论他们滚动多么努力。

Here is what's happening: https://streamable.com/f98slq .这是发生了什么: https://streamable.com/f98slq As you can see, I'm able to scroll past more than 1 video at a time.如您所见,我一次可以滚动超过 1 个视频。

What I've tried我试过的

I tried to get the scroll velocity when scrolling the feed.我试图在滚动提要时获得滚动速度。 If it's higher than a certain value, I would apply a style that stops the page scroll for 10ms.如果它高于某个值,我会应用一种停止页面滚动 10 毫秒的样式。 This didn't really work though as the scroll momentum was able to scroll the page even if the scroll velocity wasn't high.这并没有真正起作用,因为即使滚动速度不高,滚动动量也能够滚动页面。 I don't really like this solution though.我不是很喜欢这个解决方案。

I'm not really sure the best way to approach this problem.我不确定解决这个问题的最佳方法。 Any feedback would be appreciated.对于任何反馈,我们都表示感谢。

This is my code:这是我的代码:

    // 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>
      </>

A strategy would be to check the position of certain elements passes through a range that triggers a scroll freeze .一种策略是检查某些元素的 position 是否通过触发滚动冻结范围

So on scroll, if the scrolled pixels amount is more or less close to the position of one of those elements, you can freeze it for a certain time.所以在滚动时,如果滚动的像素量或多或少接近其中一个元素的 position,您可以将其冻结一段时间。

Four things here, before we go to my demo:这里有四件事,在我们 go 到我的演示之前:

  1. How to freeze the scroll?如何冻结卷轴?
  2. Why a range ?为什么是范围
  3. When to unfreeze it?什么时候解冻
  4. Some considerations...一些考虑...

How to freeze the scroll?如何冻结卷轴?

Quite simple... You can set the scrollTop to a fixed value at each scroll event.很简单...您可以在每个滚动事件中将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>

Why a range?为什么是范围?

Because the scrollTop value will not match exactly the position of the elements.因为scrollTop值不会完全匹配元素的 position。 So you have to evaluate a certain amount of pixels around it.所以你必须评估它周围的一定数量的像素。 It estimated +/- 16px to be quite good even with normal quick spins of the mouse wheel.即使鼠标滚轮正常快速旋转,它估计 +/- 16px也相当不错。 That makes a range of 32px.这使得范围为 32px。 Up to you to adjust it.由你来调整。 There is about nothing to do with abnormals quick spins.异常快速旋转无关。

When to unfreeze it?什么时候解冻?

You have to!你必须! .o(lol) It can be after a certain delay (like I've done in my demo) or after the video played a certain time. .o(lol)它可以在一定的延迟之后(就像我在我的演示中所做的那样)或在视频播放了一定时间之后。 You would have to check the video currentTime .您必须检查视频currentTime

Some considerations...一些考虑...

While it may be fun to code it... Your users most probably will dislike like hell.虽然编写代码可能很有趣......您的用户很可能会不喜欢地狱。 So do not abuse on the freezed time.所以不要滥用冻结时间。 It should be subtile.它应该是微妙的。

The most interesting use case for it would be the about the super fast scroll... Like doing a fast swipe up gesture on a mobile which scrolls a 10km page in a second.最有趣的用例是关于超快速滚动......就像在移动设备上快速向上滑动手势,每秒滚动 10 公里页面。 But sadly, this solution won't be reliable for that case.但遗憾的是,这种解决方案对于这种情况并不可靠。

Now the relevant parts of my demo现在是我演示的相关部分

Which can be ran on replit哪个可以在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;
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM