使用jQuery同步滚动?

Posted

技术标签:

【中文标题】使用jQuery同步滚动?【英文标题】:Synchronized scrolling using jQuery? 【发布时间】:2013-09-27 22:59:45 【问题描述】:

我正在尝试使用以下代码实现两个DIV 的同步滚动。

DEMO

$(document).ready(function() 
    $("#div1").scroll(function ()  
        $("#div2").scrollTop($("#div1").scrollTop());
    );
    $("#div2").scroll(function ()  
        $("#div1").scrollTop($("#div2").scrollTop());
    );
);

#div1#div2 的内容完全相同,但大小不同,比如说

#div1 
 height : 800px;
 width: 600px;

#div1 
 height : 400px;
 width: 200px;

使用这段代码,我面临两个问题。

1) 滚动没有很好地同步,因为 div 的大小不同。我知道,这是因为,我直接设置了scrollTop 值。我需要找到滚动内容的百分比并计算其他div 对应的scrollTop 值。我不确定,如何找到实际高度和当前滚动位置。

2) 此问题仅在 firefox 中发现。在 Firefox 中,滚动不像在其他浏览器中那样平滑。我认为这是因为上面的代码正在创建滚动事件的无限循环。 我不确定,为什么这只发生在 Firefox 上。有没有办法找到滚动事件的来源,以便我解决这个问题。

任何帮助将不胜感激。

【问题讨论】:

有点类似的问题和我的回答:***.com/a/15344759/2000060 【参考方案1】:

您可以使用element.scrollTop / (element.scrollHeight - element.offsetHeight) 获取百分比(它将是01 之间的一个值)。因此,您可以将其他元素的 (.scrollHeight - .offsetHeight) 乘以该值以进行比例滚动。

为避免在循环中触发侦听器,您可以暂时取消绑定侦听器,设置 scrollTop 并再次重新绑定。

var $divs = $('#div1, #div2');
var sync = function(e)
    var $other = $divs.not(this).off('scroll'), other = $other.get(0);
    var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
    other.scrollTop = percentage * (other.scrollHeight - other.offsetHeight);
    // Firefox workaround. Rebinding without delay isn't enough.
    setTimeout( function() $other.on('scroll', sync ); ,10);

$divs.on( 'scroll', sync);

http://jsfiddle.net/b75KZ/5/

【讨论】:

你也可以使用jQuery.throttle插件:github.com/cowboy/jquery-throttle-debounce 是否可以让div2水平滚动? @RafaSashi 当然,只需将 *Height 更改为 *Width*Top 更改为 *Left jsfiddle.net/b75KZ/102 (编辑:对不起,我以为你的意思是水平的,但它应该是一样的简单,只需根据scrollTop 更改scrollLeft,反之亦然) @pawel 谢谢我更多地考虑类似jsfiddle.net/PvVdq 的事情,换句话说就是调整你对这个问题的回答***.com/questions/18333085/… 我创建了一个版本,它解决了这个错误并增加了在多个容器之间滚动的能力,而不仅仅是两个:***.com/a/34017855/63467【参考方案2】:

像发条一样运行(见DEMO)

$(document).ready(function()

  var master = "div1"; // this is id div
  var slave = "div2"; // this is other id div
  var master_tmp;
  var slave_tmp;
  var timer;

  var sync = function ()
  
    if($(this).attr('id') == slave)
    
      master_tmp = master;
      slave_tmp = slave;
      master = slave;
      slave = master_tmp;
    

    $("#" + slave).unbind("scroll");

    var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);

    var x = percentage * ($("#" + slave).get(0).scrollHeight - $("#" + slave).get(0).offsetHeight);

    $("#" + slave).scrollTop(x);

    if(typeof(timer) !== 'undefind')
      clearTimeout(timer);

    timer = setTimeout(function() $("#" + slave).scroll(sync) , 200)
  

  $('#' + master + ', #' + slave).scroll(sync);

);

【讨论】:

效果很好,即使使用鼠标滚轮。【参考方案3】:

这就是我正在使用的。只需使用要同步的两个元素调用 syncScroll(...) 函数。我发现 pawel 的解决方案存在在鼠标或触控板实际完成操作后继续缓慢滚动的问题。

查看工作示例here。

// Sync up our elements.
syncScroll($('.scroll-elem-1'), $('.scroll-elem-2'));


/***
*   Synchronize Scroll
*   Synchronizes the vertical scrolling of two elements.
*   The elements can have different content heights.
*
*   @param $el1 Object
*       Native DOM element or jQuery selector.
*       First element to sync.
*   @param $el2 Object
*       Native DOM element or jQuery selector.
*       Second element to sync.
*/
function syncScroll(el1, el2) 
  var $el1 = $(el1);
  var $el2 = $(el2);

  // Lets us know when a scroll is organic
  // or forced from the synced element.
  var forcedScroll = false;

  // Catch our elements' scroll events and
  // syncronize the related element.
  $el1.scroll(function()  performScroll($el1, $el2); );
  $el2.scroll(function()  performScroll($el2, $el1); );

  // Perform the scroll of the synced element
  // based on the scrolled element.
  function performScroll($scrolled, $toScroll) 
    if (forcedScroll) return (forcedScroll = false);
    var percent = ($scrolled.scrollTop() / 
      ($scrolled[0].scrollHeight - $scrolled.outerHeight())) * 100;
    setScrollTopFromPercent($toScroll, percent);
  

  // Scroll to a position in the given
  // element based on a percent.
  function setScrollTopFromPercent($el, percent) 
    var scrollTopPos = (percent / 100) *
      ($el[0].scrollHeight - $el.outerHeight());
    forcedScroll = true;
    $el.scrollTop(scrollTopPos);
  

【讨论】:

【参考方案4】:

如果 div 大小相同,则下面的代码是同步滚动它们的简单方法:

scroll_all_blocks: function(e) 
        var scrollLeft = $(e.target)[0].scrollLeft;

        var len = $('.scroll_class').length;
        for (var i = 0; i < len; i++)
        
            $('.scroll_class')[i].scrollLeft = scrollLeft;
        

    

这里我使用水平滚动,但你可以在这里使用 scrollTop。该函数在 div 上的 scroll 事件上调用,因此 e 将有权访问事件对象。 其次,您可以简单地计算适用于这一行的 div 的相应大小的比率$('.scroll_class')[i].scrollLeft = scrollLeft;

【讨论】:

【参考方案5】:

我通过将滚动百分比设置为定点表示法解决了同步滚动循环问题:percent.toFixed(0),参数为 0。这可以防止两个同步元素之间不匹配的分数滚动高度,这些元素不断地试图“赶上”彼此。这段代码最多可以让他们在一个额外的步骤之后赶上(即,第二个元素可能会在用户停止滚动后继续滚动一个额外的像素)。不是完美的解决方案或最复杂的解决方案,但肯定是我能找到的最简单的解决方案。

var left = document.getElementById('left');
var right = document.getElementById('right');
var el2;
var percentage = function(el)  return (el.scrollTop / (el.scrollHeight - el.offsetHeight)) ;

function syncScroll(el1) 
  el1.getAttribute('id') === 'left' ? el2 = right : el2 = left;
  el2.scrollTo( 0, (percentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop


document.getElementById('left').addEventListener('scroll',function() 
  syncScroll(this);
);
document.getElementById('right').addEventListener('scroll',function() 
  syncScroll(this);
);

【讨论】:

【参考方案6】:

我喜欢 pawel 的干净解决方案,但它缺少我需要的东西,并且有一个奇怪的滚动错误,它会继续滚动,我的插件可以在多个容器上工作,而不仅仅是两个。

http://www.xtf.dk/2015/12/jquery-plugin-synchronize-scroll.html

示例和演示:http://trunk.xtf.dk/Project/ScrollSync/

插件:http://trunk.xtf.dk/Project/ScrollSync/jquery.scrollSync.js

$('.scrollable').scrollSync();

【讨论】:

@Frost 有非 jquery 版本吗?【参考方案7】:

如果您不想按比例滚动,而是希望在每个字段上滚动等量的像素,您可以将 change 的值添加到您将滚动事件绑定到的字段的当前值。

假设#left 是小字段,#right 是大字段。

var oldRst = 0;

$('#right').on('scroll', function () 
    l = $('#left');
    var lst = l.scrollTop();
    var rst = $(this).scrollTop();

    l.scrollTop(lst+(rst-oldRst)); // <-- like this

    oldRst = rst;
);

https://jsfiddle.net/vuvgc0a8/1/

通过添加 change 的值,而不仅仅是将其设置为等于 #rightscrollTop(),您可以在小字段中向上或向下滚动,而不管其 scrollTop() 是否小于较大的字段。 Facebook 上的用户页面就是一个例子。

这是我来这里时需要的,所以我想我会分享。

【讨论】:

【参考方案8】:

来自 pawel 解决方案(第一个答案)。

对于使用 jQuery 的 水平同步滚动,这是解决方案:

var $divs = $('#div1, #div2'); //only 2 divs
var sync = function(e)
    var $other = $divs.not(this).off('scroll');
    var other = $other.get(0);
    var percentage = this.scrollLeft / (this.scrollWidth - this.offsetWidth);
    other.scrollLeft = percentage * (other.scrollWidth - other.offsetWidth);
    setTimeout( function() $other.on('scroll', sync ); ,10);


$divs.on('scroll', sync);

JSFiddle

多个水平同步 div 的另一种解决方案是这样,但它适用于具有相同宽度的 div。

var $divs = $('#div1, #div2, #div3'); //multiple divs
var sync = function (e) 
    var me = $(this);
    var $other = $divs.not(me).off('scroll');
    $divs.not(me).each(function (index) 
        $(this).scrollLeft(me.scrollLeft());
    );
    setTimeout(function () 
        $other.on('scroll', sync);
    , 10);

$divs.on('scroll', sync);

注意:仅适用于宽度相同的 div

JSFiddle

【讨论】:

以上是关于使用jQuery同步滚动?的主要内容,如果未能解决你的问题,请参考以下文章

标题/数据列未与 jQuery dataTables、bootstrap 2.3.2 对齐并启用水平滚动

同步两个handsontables的水平滚动

解决iscoll.js 与jquery的AJAX配合使用时会造成回弹的问题

Vue 中使用消息总线让两个滚动条同步滚动

Vue 中使用消息总线让两个滚动条同步滚动

使用 jQuery 确认和 scrollTop 导致页面向上滚动然后向下滚动