【问题标题】:How to make Syncing two Divs scroll positions smoother如何使同步两个 Divs 滚动位置更平滑
【发布时间】:2014-09-04 05:14:15
【问题描述】:

我正在尝试同步两个可滚动的 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忙于提供动画 而是执行所需的计时器直到 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);

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

【问题讨论】:

  • 它们必须是 2 个 DIV 吗?可以分享一点 HTML 吗?
  • @JNF 它们不共享相同的 HTMl,因此可能有 0-100px 的高度差异。这就是我计算滚动百分比和相应滚动的原因。
  • 你能做一个简单的演示给我们编辑吗?
  • @ZachSaucier 更新了最后两种方法的小提琴,也将发布给其他人。
  • 所以方法 3 只是 iOS 问题?你需要香草还是 jQuery?

标签: javascript iscroll


【解决方案1】:

依靠滚动事件(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。小提琴只实现了触摸设备的解决方案。对于桌面设备,请使用 OP 的方法 1。实现一个条件,根据设备检查使用哪种方法。

如何通过 css 过渡应用惯性

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

  1. 改变html的结构为。

    • div:带有overflow: hidden的容器

      • div:内容为position: absolute

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

  2. 实现上述功能,以便它们为scrollTop 的内容的top 位置设置动画

    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';
    }
    

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

FIDDLE。见result on iOS

【讨论】:

  • @感谢您的回答。但是快速移动手指并尝试设置scrollTop时会出现问题吗?而且它们不会有相同的滚动高度,因此需要计算滚动百分比。
  • 什么意思:“快速移动手指并尝试设置 scrollTop”。你检查了result on iOS。您可以随心所欲地移动手指。必须禁用所有浏览器滚动。
  • 我的意思是弹性滚动。我遇到了快速移动手指时反应迟缓的问题,即 touchmove 的速度。
  • 用 css 动画惯性。这将得到最平滑的结果:通过比较最后一个 touchMove yOffset 和之前的一个来计算滚动的速度。从此速度应用缓慢停止滚动的动画(例如 css 过渡)
  • 这是我见过的最好的解释之一。
猜你喜欢
  • 2012-03-03
  • 1970-01-01
  • 2010-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-15
相关资源
最近更新 更多