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

时间:2014-08-25 00:38:52

标签: javascript iscroll

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

遵循的方法:

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

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的同步滚动位置更加平滑。请给我一些建议,这让我感到恼火。

1 个答案:

答案 0 :(得分: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和之前的yOffset来计算滚动的速度。从此速度应用动画(例如css过渡),慢慢停止滚动

  4. 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. 实现上述功能,以便为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动画可能是诀窍。另一种方法是在转换完成时注册事件。如果转换完成且滚动位置在容器外部,则应用一个新的转换,将内容设置为动画。

    4. FIDDLE。见result on iOS