javascript - 如何使同步两个 Divs 滚动位置更平滑

标签 javascript iscroll

我正在尝试同步两个可滚动的 DIVS 滚动位置。

遵循的方法:

Method - 1 : on-scroll事件设置其他DIV的scrollTop。 问题:滚动事件最后执行,UI 在 iOS Safari 中缓慢。

Method - 2 :使用 setInterval 来同步两个滚动位置。 问题:iOS 在滚动期间不执行定时器功能, 所以滚动位置在最后同步。同样,这更加缓慢。 尝试过,许多博客中提到的计时器已修复,但仍然没有优雅。

Method -3 :尝试自定义滚动条,所以 iScroll 并尝试在 scroll 事件上同步两者, 问题:这看起来好多了,但在 iOS 中仍然很慢!!!

Method -4 :尝试自定义滚动条,所以 iScroll 并尝试在 scroll 事件上同步两者, 问题:使用 iScroll 但使用定时器而不是依赖于 onScroll 事件, 但是在touchmove期间,iOS忙于提供动画 而是执行所需的计时器直到触摸结束。 下面的代码引用了这个方法。它也很迟钝。

var active = .., other = ...
// active : active Scrolling element
// other : Element to be in sync with active
window.setInterval(function () {
    var y;
    if (active) {
        y = active.y;
    } else {
        return;
    }
    var percentage = -y / (active.scrollerHeight - active.wrapperHeight);
    var oscrollTop = percentage * (other.scrollerHeight - other.wrapperHeight);
    if (-other.maxScrollY >= toInt(oscrollTop)) {
        other.scrollTo(0, -toInt(oscrollTop));
    }
}, 20);

如何使两个可滚动 DIVS 的同步滚动位置更顺畅。请给我一些建议,这让我很恼火。

最佳答案

依靠滚动事件(OPs 方法 1) 适合桌面实现。滚动事件在屏幕更新之前触发。在移动设备上,尤其是 iOS,情况并非如此。由于资源有限,滚动事件仅在用户完成(抬起手指)滚动操作后触发。

实现手动滚动

当用户在 iOS 上滚动时产生滚动事件需要手动实现滚动。

  1. 注册 touchstart 事件。并获得第一次接触:

    var element1 = document.getElementById('content1');
    var element2 = document.getElementById('content2');
    
    var activeTouch = null;
    var touchStartY = 0;
    var element1StartScrollTop = 0;
    var element2scrollSyncFactor = 0;
    
    document.addEventListener('touchstart', function(event) {
        event.preventDefault();
    
        var touch = event.changedTouches[0];
    
        if ( activeTouch == null ) {
            // implement check if touch started on an element you want to be scrollable
            // save a reference to the scrolling element for the other functions
            activeTouch = touch;
            touchStartY = touch.screenY;
            // if scroll content does not change do this calculation only once to safe compute and dom access time while animating
            calcSyncFactor();
        }
    });
    
    function calcSyncFactor()
    {
        // calculate a factor for scroll areas with different height
        element2scrollSyncFactor = (element2.scrollHeight - element2.clientHeight) / (element1.scrollHeight - element1.clientHeight);    
    }
    
  2. 根据手指移动更新滚动位置:

    document.addEventListener('touchmove', function() {
        for ( var i = 0; i < event.changedTouches.length; i++ ) {
            var touch = event.changedTouches[i];
    
            if ( touch === activeTouch ) {
                var yOffset = touch.screenY - touchStartY;
                element1.scrollTop = element1StartScrollTop + (0 - yOffset);
                syncScroll();
                break;
            }
        }    
    });
    
    function syncScroll()
    {
        element2.scrollTop = Math.round(element1.scrollTop * element2scrollSyncFactor);
    }
    

    可以添加一个仅在用户将手指移动了一些像素后才开始滚动的检查。这样,如果用户单击某个元素,文档将不会滚动一些像素。

  3. 用户抬起手指后的清理:

    document.addEventListener('touchend', touchEnd);
    document.addEventListener('touchcancel', touchEnd);
    
    function touchEnd(event)
    {
        for ( var i = 0; i < event.changedTouches.length; i++ ) {
            var touch = event.changedTouches[i];
            if ( touch === activeTouch ) {
                // calculate inertia and apply animation
                activeTouch = null;
                break;
            }
        }    
    }
    

    要使滚动感觉更自然,请应用 iOS 橡皮筋效果和惯性。通过将最后一个 touchMove yOffset 与之前的比较来计算滚动速度。从这个速度应用一个动画(例如 css 过渡)来慢慢停止滚动

参见 FIDDLE .见result on iOS . fiddle 只实现了触摸设备的解决方案。对于桌面设备,使用 OP 的方法 1. 实现一个条件,检查根据设备使用哪种方法。

如何通过 css 转换应用惯性

可以使用 requestAnimationFrame 在 javascript 中制作动画。在移动设备上可能更高效的方法是使用 css 转换或 css 动画。虽然元素滚动位置不能用 css 动画。

  1. 将 html 的结构更改为。

    • div:溢出:隐藏

      的容器
      • div:position: absolute

        的内容

        根据内容大小,在内容 div 上使用 css 属性 -webkit-transform: translateZ(0)。这将创建一个具有自己的背衬表面的新层,该层将在 gpu 上合成。

  2. 实现上述函数,以便它们为内容的 top 位置而不是 scrollTop 设置动画

    var element1 = document.getElementById('content1');
    var element2 = document.getElementById('content2');
    
    var activeTouch = null;
    var touchStartY = 0;
    var element1StartScrollTop = 0;
    var element2scrollSyncFactor = 0;
    var offsetY = 0;
    var lastOffsetY = 0;
    
    document.addEventListener('touchstart', function(event) {
        event.preventDefault();
    
        var touch = event.changedTouches[0];
    
        if ( activeTouch == null ) {
            activeTouch = touch;
            touchStartY = touch.screenY;
            // use offsetTop instead of scrollTop
            element1StartScrollTop = element1.offsetTop;
            // if scroll content does not change do this calculation only once to safe compute time while animating
            calcSyncFactor();
    
            // cancel inertia animations
            element1.style.webkitTransition = 'none';
            element2.style.webkitTransition = 'none';
        }
    });
    
    function calcSyncFactor()
    {
        // calculate a factor for scroll areas with different height   
        // use the div's sizes instead of scrollTop
        element2scrollSyncFactor = (element2.clientHeight - element2.parentNode.clientHeight) / (element1.clientHeight - element1.parentNode.clientHeight);    
    }
    
    document.addEventListener('touchmove', function() {
        for ( var i = 0; i < event.changedTouches.length; i++ ) {
            var touch = event.changedTouches[i];
    
            if ( touch === activeTouch ) {
                lastOffsetY = offsetY;
                offsetY = touch.screenY - touchStartY;
                // use offsetTop instead of scrollTop
                element1.style.top = (element1StartScrollTop + offsetY) + 'px';
                syncScroll();
                break;
            }
        }    
    });
    
    function syncScroll()
    {
        element2.style.top = Math.round(element1.offsetTop * element2scrollSyncFactor) + 'px';
    }
    
    document.addEventListener('touchend', touchEnd);
    document.addEventListener('touchcancel', touchEnd);
    
    function touchEnd(event)
    {
        for ( var i = 0; i < event.changedTouches.length; i++ ) {
            var touch = event.changedTouches[i];
            if ( touch === activeTouch ) {
                applyInertia();
                activeTouch = null;
                break;
            }
        }    
    }
    
  3. 当用户完成滚动并抬起手指时应用惯性

    function applyInertia()
    {
        var velocity = offsetY - lastOffsetY;
        var time = Math.abs(velocity) / 150;
        var element1EndPosition = element1.offsetTop + velocity;
    
        element1.style.webkitTransition = 'top ' + time + 's ease-out';
        element1.style.top = element1EndPosition + 'px';
    
        element2.style.webkitTransition = 'top ' + time + 's ease-out';
        element2.style.top = Math.round(element1EndPosition * element2scrollSyncFactor) + 'px';
    }
    

    惯性是根据用户抬起手指时的速度计算得出的。 摆弄这些值以获得所需的结果。橡皮筋效果也可以在此函数中实现。没有 javascript 涉及应用 css 动画可能是诀窍。另一种方法是为转换完成时注册事件。如果过渡完成并且滚动位置在容器外部,则应用一个新的过渡来动画内容。

参见 FIDDLE .见result on iOS .

关于javascript - 如何使同步两个 Divs 滚动位置更平滑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25477651/

相关文章:

javascript - Iscroll mousewheel 滚动在 firefox 中比 Google Chrome 慢

javascript - iScroll 隐藏/显示滚动条

javascript - 桌面浏览器上的水平 iScroll 问题

javascript - 无法在移动设备上与溢出 :auto element underneath scrollable section using fullpage. js 进行交互,scrollOverflow:true

jquery-mobile - iScroll回到顶部

JavaScript 从函数返回值

javascript - jQuery Sortable 没有获取 ui.draggable id

php - "Make this your Default Search engine"按钮?

javascript - 随 secret 码生成器 JavaScript

javascript - 在匿名内部使用 'this',IDE : potentially invalid usage