javascript - 确定 snap-scroll 元素的 snap 滚动事件是否完成

标签 javascript css animation easing scroll-snap

抽象的
我正在使用可滚动元素创建一个图片库。我正在使用 CSS 的 scroll-snap功能,它允许我捕捉到滚动条中的元素(图像)。
通过绑定(bind)到元素的 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 次)它已经到达捕捉点。我们使用 remainder operator 来做到这一点。 :
    var atSnappingPoint = e.target.scrollLeft % e.target.offsetWidth === 0;
    
    然后,如果达到捕捉点,则设置 timeOut (在 setTimeout 中使用,当滚动完成时应该触发)设置为 0。否则,使用常规值(在上面的示例中为 150,请参见注释)。
    这是因为当元素实际到达其捕捉点时,最后一个 scroll事件被触发,我们的处理程序被触发(再次)。调整timeOut为 0 则 即时 ( see 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>


    笔记
    滚动捕捉点 我还没有实现/考虑到您可以通过滚动来“点击”捕捉点这一事实。不过,这种情况极为罕见。当我找到解决方案时,我会更新答案。
    像素比 :如果你搞砸了计算(剩余的 1 px 等,所以函数无法正确解析),你可能有一些缩放/框模型问题,这会弄乱滚动位置和 offsetWidth 计算的计算。
    这是由设备的像素比率引起的,因此您可以尝试通过将这些值乘以像素比率来“纠正”这些值(向左滚动和宽度)。
    重要提示:事实证明,像素比率不仅表明用户是否拥有高 dpi 屏幕,而且在用户缩放页面时也会发生变化。
    超时任意timeOut在滚动尚未达到捕捉点时使用的值为 150。这足以防止在 Safari @ iOS 完成滚动之前触发它(它使用贝塞尔曲线进行滚动捕捉,这会产生一个非常长的“最后一帧” 120-130ms)并且足够短以在用户在捕捉点之间暂停滚动时产生可接受的结果。
    滚动填充 如果你设置了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);
    

    关于javascript - 确定 snap-scroll 元素的 snap 滚动事件是否完成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65952068/

    相关文章:

    Javascript在浏览器中运行,而不是在带有cordova的phonegap应用程序中运行

    javascript - 使用 Protractor 测试页面上的所有链接

    javascript - 收到 JavaScript 错误 : "X is not function"

    css - 在子菜单中创建纯 CSS 下拉子菜单的问题

    javascript - 无法摆脱页面上的顶部和左侧空白

    css - 将 PureCSS 添加到 MVC 4.5 包

    ios - 如何在 SwiftUI 中动画/过渡文本值更改

    java - 点击ImageView后如何启动drawable动画?

    javascript - 求圆圆周上各点的坐标

    javascript - Three.js:让 child 保持在父网格之下