web-dev-qa-db-fra.com

Défilement synchronisé à l'aide de jQuery?

J'essaie d'implémenter le défilement synchronisé pour deux DIV avec le code suivant.

DÉMO

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

#div1 et #div2 ont le même contenu mais des tailles différentes, disons

#div1 {
 height : 800px;
 width: 600px;
}
#div1 {
 height : 400px;
 width: 200px;
}

Avec ce code, je suis confronté à deux problèmes.

1) Le défilement n'est pas bien synchronisé, car les div sont de tailles différentes. Je sais, c'est parce que je mets directement la valeur scrollTop. J'ai besoin de trouver le pourcentage de contenu défilé et de calculer la valeur scrollTop correspondante pour l'autre div. Je ne sais pas trop comment trouver la hauteur réelle et la position de défilement actuelle.

2) Ce problème ne se trouve que dans firefox. Dans Firefox, le défilement n'est pas lisse comme dans les autres navigateurs. Je pense que cela parce que le code ci-dessus crée une boucle infinie d'événements de défilement. Je ne sais pas trop pourquoi cela ne se produit que sous firefox. Est-il possible de trouver la source de l'événement de défilement afin de résoudre ce problème?.

Toute aide serait grandement appréciée.

23
Nauphal

Vous pouvez utiliser element.scrollTop / (element.scrollHeight - element.offsetHeight) pour obtenir le pourcentage (ce sera une valeur comprise entre 0 et 1). Ainsi, vous pouvez multiplier le (.scrollHeight - .offsetHeight) de l'autre élément par cette valeur pour le défilement proportionnel.

Pour éviter de déclencher les écouteurs dans une boucle, vous pouvez dissocier temporairement l'écouteur, définissez la variable scrollTop et effectuez une nouvelle liaison.

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/

42
pawel

C'est ce que j'utilise. Appelez simplement la fonction syncScroll(...) avec les deux éléments que vous souhaitez synchroniser. J'ai trouvé que la solution de pawel rencontrait des problèmes pour continuer à faire défiler lentement après que la souris ou le trackpad aient été utilisés avec l'opération.

Voir exemple de travail ici .

// 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);
  }
}
3
goozbox

Fonctionne comme sur des roulettes (voir DÉMO )

$(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);

});
2
ktretyak

Si les div sont de tailles égales, le code ci-dessous est un moyen simple de les faire défiler de manière synchrone:

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

    }

Ici, j'utilise le défilement horizontal, mais vous pouvez utiliser scrollTop ici à la place. Cette fonction appelle call l'événement scroll sur le div, de sorte que e aura accès à l'objet événement. Deuxièmement, vous pouvez simplement avoir le rapport des tailles correspondantes des div calculées à appliquer dans cette ligne $('.scroll_class')[i].scrollLeft = scrollLeft;.

2
Rahul Dole

Si vous ne voulez pas de défilement proportionnel, mais plutôt de faire défiler une quantité égale de pixels sur chaque champ, vous pouvez ajouter la valeur de change à la valeur actuelle du champ auquel vous liez l'événement scroll. 

Disons que #left est le petit champ et #right le plus grand.

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/

En ajoutant la valeur de change et en ne lui attribuant pas une valeur égale à scrollTop() de #right, vous pouvez faire défiler vers le haut ou le bas le petit champ, même si son scrollTop() est inférieur au plus grand. Un exemple de ceci est une page utilisateur sur Facebook.

C'est ce dont j'avais besoin quand je suis arrivé ici, alors j'ai pensé partager.

1
Thorpentin

J'aime la solution propre de pawel, mais il manque quelque chose dont j'ai besoin et présente un étrange bug de défilement qui continue de défiler et où mon plugin fonctionnera sur plusieurs conteneurs, pas seulement deux.

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

Exemple et démo: http://trunk.xtf.dk/Project/ScrollSync/

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

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

1
Frost

J'ai résolu le problème de la boucle de défilement sync en définissant le pourcentage de défilement sur la notation à virgule fixe: percent.toFixed(0), avec le paramètre 0. Cela évite des hauteurs de défilement fractionnaires incompatibles entre les deux éléments synchronisés, qui tentent constamment de se "rattraper" l'un par rapport à l'autre. Ce code leur permettra de se rattraper au plus tard après une seule étape supplémentaire (c'est-à-dire que le deuxième élément peut continuer à faire défiler un pixel supplémentaire après que l'utilisateur ait arrêté de faire défiler). Pas une solution parfaite ou la plus sophistiquée, mais certainement la plus simple que j'ai pu trouver.

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

0
gaspar_schott

De la solution pawel (première réponse).

Pour le défilement horizontal synchronisé avec jQuery, voici la solution:

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

Voici une autre solution pour plusieurs div synchronisés horizontalement, mais cela fonctionne pour les div de même largeur.

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);

NB: uniquement pour les div de même largeur

JSFiddle

0
jafed