web-dev-qa-db-fra.com

Détecter lorsque la barre de défilement verticale de la fenêtre apparaît

Existe-t-il une solution simple et fiable pour détecter l'apparition/la disparition de la barre de défilement verticale de la fenêtre?

window.onresize n'est pas déclenché lorsque la page de manipulation DOM JavaScript devient suffisamment élevée pour faire apparaître la barre de défilement.

Dans cet article très similaire Détecter si une page a une barre de défilement verticale décrit la solution pour détecter si la barre de défilement est présente ou non, mais j'ai besoin de savoir quand elle apparaît exactement.

36
Roman

Window.onresize ne fonctionnera pas, car la fenêtre ne se redimensionne pas.

body.onresize ne fonctionnera pas, car resize n'est implémenté que pour les fenêtres et les cadres.

Cette question traite du même problème. Le meilleur répondeur a des idées intéressantes, bien qu'elles ne soient pas simples ou multi-navigateurs.

Je pense que cette approche basée sur Jquery par Rick Strahl est la meilleure que vous puissiez obtenir: Surveillance des modifications CSS des éléments HTML en JavaScript il utilise les fonctions "montre" du navigateur si disponibles, ou une minuterie dans le cas contraire. Ce dernier n'est pas très convivial en termes de ressources, mais il semble qu'il n'y ait aucun moyen de le contourner.

Une bonne façon de dire à la barre de défilement une fois que la chose resize est résolue est dans cette question par le façon, juste au cas où vous ne faites pas référence à celui-là dans votre question.

18
Pekka

Désolé de ramener cela d'entre les morts, mais je viens de rencontrer cette limitation et j'ai trouvé ma propre solution. C'est un peu hacky mais restez avec moi ...

L'idée est d'ajouter une iframe invisible à 100% de largeur à la page et d'écouter les événements de redimensionnement sur sa fenêtre interne. Ces événements récupèrent les modifications non seulement de la taille de la fenêtre extérieure, mais également lorsque des barres de défilement sont ajoutées ou supprimées de la fenêtre extérieure.

Il déclenche un événement de redimensionnement de fenêtre normal, il ne nécessite donc pas de code supplémentaire si vous écoutez déjà le redimensionnement de la fenêtre.

Testé dans IE9 et Chrome/Firefox les plus récents - pourrait peut-être fonctionner avec des IE plus anciens, mais mon projet ne les prend pas en charge, donc je n'ai pas essayé.

https://Gist.github.com/OrganicPanda/8222636

38
OrganicPanda

Basé sur la réponse d'OrganicPanda, est venu avec cette chose jquery

$('<iframe id="scrollbar-listener"/>').css({
    'position'      : 'fixed',
'width'         : '100%',
'height'        : 0, 
'bottom'        : 0,
'border'        : 0,
'background-color'  : 'transparent'
}).on('load',function() {
    var vsb     = (document.body.scrollHeight > document.body.clientHeight);
    var timer   = null;
    this.contentWindow.addEventListener('resize', function() {
        clearTimeout(timer);
        timer = setTimeout(function() {
            var vsbnew = (document.body.scrollHeight > document.body.clientHeight);
            if (vsbnew) {
                if (!vsb) {
                    $(top.window).trigger('scrollbar',[true]);
                    vsb=true;
                }
            } else {
                if (vsb) {
                    $(top.window).trigger('scrollbar',[false]);
                    vsb=false;
                }
            }
        }, 100);
    });
}).appendTo('body');

Cela déclenchera des événements de "barre de défilement" sur la fenêtre, s'ils apparaissent/disparaissent

Fonctionne sur chrome/mac, au moins. maintenant, quelqu'un étend cela pour détecter les barres de défilement horizontales :-)

8
commonpike

Si vous utilisez AngularJS, vous pouvez utiliser une directive pour détecter quand la largeur change (en supposant que la barre de défilement apparaissant/disparaissant est verticale):

app.directive('verticalScroll', function($rootScope){
    return {
        restrict: 'A',
        link: function (scope, element) {
            scope.$watch(
                function() {
                    return element[0].clientWidth;
                },
                function() {
                    $rootScope.$emit('resize');
                }
            );
        }
    }
});

Cela déclenche un événement sur la portée racine que d'autres directives ou contrôleurs peuvent écouter.

La montre est déclenchée par la boucle de résumé angular, donc cela dépend de Angular ayant chargé/supprimé le contenu supplémentaire qui a fait apparaître/disparaître votre barre de défilement).

4
Dan King

Le scoop

Il est possible de détecter les modifications de la visibilité des barres de défilement en utilisant ResizeObserver pour vérifier les modifications de la taille de l'élément pouvant prendre des barres de défilement et les modifications de la taille de son contenu.

Raisonnement

J'ai commencé à implémenter une solution avec le <iframe> mais a rapidement constaté que la mise en œuvre complète nécessitait de rompre la séparation des préoccupations entre les vues de ma candidature. J'ai une vue parent qui doit savoir quand une vue enfant acquiert une barre de défilement verticale. (Je ne me soucie pas de la barre de défilement horizontale.) J'ai deux situations qui peuvent affecter la visibilité de la barre de défilement verticale:

  1. La vue parent est redimensionnée. Ceci est sous le contrôle direct de l'utilisateur.

  2. Le contenu de la vue enfant devient plus grand ou plus petit. Ceci est sous le contrôle indirect de l'utilisateur. La vue enfant affiche les résultats d'une recherche. La quantité et le type de résultats déterminent la taille de la vue enfant.

J'ai trouvé que si j'utilisais <iframe> Il faudrait que je fonde la vue de l'enfant pour subvenir aux besoins des parents. Je préfère que l'enfant ne contienne pas de code pour quelque chose qui est purement une préoccupation du parent. Avec la solution que je décris ici, seule la vue parent devait être modifiée.

Donc, en cherchant une meilleure solution, j'ai trouvé cette réponse par Daniel Herr. Il suggère d'utiliser ResizeObserver pour détecter quand les dimensions d'une div changent. ResizeObserver n'est pas encore disponible en natif sur les navigateurs mais il existe un ponyfill/polyfill robuste que j'utilise pour le support dans les cas où le support natif est indisponible. (Voici le spec pour ResizeObserver.)

Preuve de concept

J'utilise ce polyfill dans son mode ponyfill. De cette façon, l'environnement mondial reste intact. Cette implémentation repose sur window.requestAnimationFrame, et se repliera sur setTimeout pour les plates-formes qui ne prennent pas en charge window.requestAnimationFrame. En regardant le support pour requestAnimationFrame sur "Puis-je utiliser ...?", Ce que je vois là ne me dérange pas. YMMV.

J'ai un live preuve de concept . La clé est d'écouter les changements de taille sur l'élément DOM qui peut accepter les barres de défilement (l'élément avec l'ID container, en vert) et d'écouter les changements de taille sur le contenu qui peut nécessiter un défilement (l'élément avec id content). La preuve de concept utilise interact.js pour gérer un élément resizer (avec l'id resizer, en bleu) qui permet de redimensionner container. Si vous faites glisser le coin inférieur droit de resizer, il redimensionnera à la fois resizer et container. Les deux boutons permettent de simuler les changements de taille du contenu affiché par container.

J'utilise cette méthode dans du code actuellement en phase de pré-version, ce qui signifie qu'il a réussi les tests sur plusieurs navigateurs et est en cours d'évaluation par les parties prenantes, mais n'est pas encore en production.

Le HTML:

<!DOCTYPE html>
<html>

<head>
  <script data-require="interact.js@*" data-semver="1.0.26" src="//rawgit.com/taye/interact.js/v1.0.26/interact.js"></script>
  <script src="//rawgit.com/que-etc/resize-observer-polyfill/master/dist/ResizeObserver.global.js"></script>
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <div id="resizer">
    <div id="container">
      <ul id="content">
        <li>Something</li>
      </ul>
    </div>
  </div>
  <button id="add">Add to content</button>
  <button id="remove">Remove from content</button>
  <p>Scroll bar is: <span id="visibility"></span></p>
  <ul id="event-log"></ul>
  <script src="script.js"></script>
</body>

</html>

Le JavaScript:

var container = document.getElementById("container");
var resizer = document.getElementById("resizer");
interact(resizer)
  .resizable({
    restrict: {
      restriction: {
        left: 0,
        top: 0,
        right: window.innerWidth - 10,
        bottom: window.innerHeight - 10
      }
    }
  })
  .on('resizemove', function(event) {
    var target = resizer;

    var rect = target.getBoundingClientRect();

    var width = rect.width + event.dx;
    var height = rect.height + event.dy;
    target.style.width = width + 'px';
    target.style.height = height + 'px';
  });

var content = document.getElementById("content");
var add = document.getElementById("add");
add.addEventListener("click", function() {
  content.insertAdjacentHTML("beforeend", "<li>Foo</li>");
});

var remove = document.getElementById("remove");
remove.addEventListener("click", function() {
  content.removeChild(content.lastChild);
});

// Here is the code that pertains to the scrollbar visibility

var log = document.getElementById("event-log");
content.addEventListener("scrollbar", function () {
  log.insertAdjacentHTML("beforeend", "<li>Scrollbar changed!</li>");
});

var visiblity = document.getElementById("visibility");
var previouslyVisible;
function refreshVisibility() {
  var visible = container.scrollHeight > container.clientHeight;
  visibility.textContent = visible ? "visible" : "not visible";
  if (visible !== previouslyVisible) {
    content.dispatchEvent(new Event("scrollbar"));
  }
  previouslyVisible = visible;
}
// refreshVisibility();


var ro = new ResizeObserver(refreshVisibility);
ro.observe(container);
ro.observe(content);

Le CSS:

* {
  box-sizing: border-box;
}

#container {
  position: relative;
  top: 10%;
  left: 10%;
  height: 80%;
  width: 80%;
  background: green;
  overflow: auto;
}

#resizer {
  background: blue;
  height: 200px;
  width: 200px;
}
3
Louis

Il s'agit de quand vous devez déterminer la visibilité de la barre de défilement.

L'OP parle d'un temps "après manipulation DOM JavaScript". Si cette manipulation se produit dans votre code, c'est le moment de vérifier si la barre de défilement est visible. Pourquoi avez-vous besoin d'un événement en plus de cela? Comment se fait-il que vous ne sachiez pas quand cette manipulation DOM se produit?

Je me rends compte que c'est une vieille question, mais je viens de traiter cette question dans un projet javascript pur, et je n'ai aucun problème à savoir quand vérifier la visibilité de la barre de défilement. Soit un événement utilisateur se déclenche, soit un événement système se déclenche, et je sais quand la manipulation DOM se produit parce que je la cause via javascript. Je ne vois aucun cas où cette manipulation DOM javascript est en dehors de la connaissance de mon code.

Peut-être qu'un événement scrollbarVisibilityChange serait pratique, mais ce n'est certainement pas nécessaire. Cela me semble être un non-problème, 9 ans plus tard. Suis-je en train de manquer quelque chose?

0
jamess