如何使两个Div的同步滚动位置更平滑

red*_*edV 6 javascript iscroll

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

方法如下:

方法 - 1:滚动事件设置其他DIV的scrollTop.问题:滚动事件在最后执行,UI在iOS游戏中缓慢.

方法-2:使用setInterval同步两个滚动位置.问题:iOS在滚动期间不执行计时器功能,因此在末尾同步滚动位置.再次这更加缓慢.试过,计时器修复如许多博客中提到但仍然没有优雅.

方法-3 :尝试自定义滚动条,所以iScroll并尝试同步scroll事件,问题:这似乎更好但在iOS仍然是缓慢!

方法-4:尝试自定义滚动条,因此iScroll尝试同步scroll事件,问题:使用iScroll但使用计时器而不是取决于onScroll事件,但在touchmove期间,iOS正忙于提供动画而不是执行所需的计时器直到touchend.下面的代码指的是这种方法.它也很迟钝.

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);
Run Code Online (Sandbox Code Playgroud)

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

dre*_*lab 3

依赖滚动事件(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);    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

  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;
            }
        }    
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

参见小提琴在 iOS 上查看结果。fiddle仅实现了触摸设备的解决方案。对于桌面设备,使用 OP 的方法 1. 实现一个条件,检查根据设备使用哪种方法。

如何在 CSS 过渡中应用惯性

可以使用 javascript 制作动画requestAnimationFrame。在移动设备上可能更高效的方法是使用 css 转换或 css 动画。尽管元素的滚动位置不能使用 css 进行动画处理。

  1. 将 html 的结构更改为。

    • div:容器overflow: hidden

      • div:内容为position: absolute

        根据内容大小,-webkit-transform: translateZ(0)在内容 div 上使用 css 属性。这将创建一个具有自己的背衬表面的新层,该层将在 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;
            }
        }    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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';
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

参见小提琴在 iOS 上查看结果。

  • 这是我见过的最好的解释之一。 (2认同)