web-dev-qa-db-fra.com

Déterminer si l'utilisateur clique sur la barre de défilement ou sur le contenu (cliquez sur pour la barre de défilement native)

J'essaie de créer des événements personnalisés dans JQuery censés détecter le clic sur une barre de défilement.1.
Je sais qu'il y a beaucoup de texte, mais toutes mes questions sont en caractères gras et il y a un exemple - JSFiddle vous pouvez travailler tout de suite.

Parce que je n'ai trouvé aucune fonctionnalité intégrée pour cela,
Je devais créer une fonction hasScroll, vérifiant si l’élément avait une barre de défilement, 

$.fn.hasScroll = function(axis){
    var overflow = this.css("overflow"),
        overflowAxis;

    if(typeof axis == "undefined" || axis == "y") overflowAxis = this.css("overflow-y");
    else overflowAxis = this.css("overflow-x");

    var bShouldScroll = this.get(0).scrollHeight > this.innerHeight();

    var bAllowedScroll = (overflow == "auto" || overflow == "visible") ||
                         (overflowAxis == "auto" || overflowAxis == "visible");

    var bOverrideScroll = overflow == "scroll" || overflowAxis == "scroll";

    return (bShouldScroll && bAllowedScroll) || bOverrideScroll;
};

et une fonction inScrollRange, vérifiant si le clic effectué était dans la plage de défilement.

var scrollSize = 18;

function inScrollRange(event){
    var x = event.pageX,
        y = event.pageY,
        e = $(event.target),
        hasY = e.hasScroll(),
        hasX = e.hasScroll("x"),
        rX = null,
        rY = null,
        bInX = false,
        bInY = false

    if(hasY){
        rY = new RECT();
        rY.top = e.offset().top;
        rY.right = e.offset().left + e.width();
        rY.bottom = rY.top +e.height();
        rY.left = rY.right - scrollSize;

        //if(hasX) rY.bottom -= scrollSize;
        bInY = inRect(rY, x, y);
    }

    if(hasX){
        rX = new RECT();
        rX.bottom = e.offset().top + e.height();
        rX.left = e.offset().left;
        rX.top = rX.bottom - scrollSize;
        rX.right = rX.left + e.width();

        //if(hasY) rX.right -= scrollSize;
        bInX = inRect(rX, x, y);
    }

    return bInX || bInY;
}

Est-ce que toutes les tailles de barres de défilement sont uniformes? E.g dans Firefox et IE c'est 18px.
En supposant qu’il n’existe pas de barres de défilement personnalisées, existe-t-il des marges ou des tailles supplémentaires dans certains navigateurs?

Ces fonctions fonctionnent toutes comme prévu (d'après ce que je peux discerner).

Faire des événements personnalisés était un peu plus compliqué, mais je l’ai fait fonctionner un peu. Le seul problème est que si l'élément cliqué est associé à un événement mousedown/up, il sera également déclenché.

Je n'arrive pas à empêcher les autres événements de se déclencher en même temps que ce que j'appelle, les événements mousedownScroll/mouseupScroll. 

$.fn.mousedownScroll = function(fn, data){
    if(typeof fn == "undefined" && typeof data == "undefined"){
        $(this).trigger("mousedownScroll");
        return;
    }

    $(this).on("mousedownScroll", data, fn);
};

$.fn.mouseupScroll = function(fn, data){
    if(typeof fn == "undefined" && typeof data == "undefined"){
        $(this).trigger("mouseupScroll");
        return;
    }

    $(this).on("mouseupScroll", data, fn);
};

$(document).on("mousedown", function(e){
    if(inScrollRange(e)){
        $(e.target).trigger("mousedownScroll");
    }
});

$(document).on("mouseup", function(e){
    if(inScrollRange(e)){
        $(e.target).trigger("mouseupScroll");
    }
});

$("selector").mousedown(function(e){
    console.log("Clicked content."); //Fired when clicking scroller as well
});

$("selector").mousedownScroll(function(e){
    console.log("Clicked scroller.");
});

Comment puis-je empêcher les autres événements "click" de se déclencher?

Pendant que je vous pose la question, n'hésitez pas à optimiser le code autant que possible.

Voici un JSFiddle avec lequel déconner.

La raison pour laquelle je le fais est à cause d'un plugin plus gros que je développe. Son menu contextuel personnalisé s’affiche lorsque je clique avec le bouton droit de la souris sur l’un des défileurs. Je ne veux pas ça. J'ai donc pensé créer un événement qui vérifie les clics de défilement (mouseup/downs), puis empêche l'affichage du menu contextuel. Pour ce faire, il me faut le clic de défilement avant le clic normal et, si possible, arrêter le déclenchement des clics normaux.

Je pense simplement à voix haute ici mais peut-être y a-t-il un moyen d'obtenir toutes les fonctions liées à l'élément et de changer l'ordre dans lequel elles ont été ajoutées? Je sais que les fonctions sont exécutées dans l'ordre dans lequel elles ont été ajoutées (1er ajouté, 1er appelé), donc, si je pouvais puiser dans ce processus, peut-être que l'enregistrement complet de l'événement sur JQuery pourrait simplement être inséré avant les événements de clic.


1 ne peut utiliser que mousedown/mouseup, car le clic ne se déclenche pas lorsque vous cliquez sur une barre de défilement. Si cela est faux, veuillez fournir un exemple/code de travail

49
ShadowScripter

Vous pouvez probablement utiliser ce hack.

Vous pouvez essayer de détourner les événements mousedown et mouseup et de les éviter en cliquant sur une barre de défilement avec votre fonction personnalisée.

$.fn.mousedown = function(data, fn) {
    if ( fn == null ) {
        fn = data;
        data = null;
    }    
    var o = fn;
    fn = function(e){
        if(!inScrollRange(e)) {
            return o.apply(this, arguments);
        }
        return;
    };
    if ( arguments.length > 0 ) {
        return this.bind( "mousedown", data, fn );
    } 
    return this.trigger( "mousedown" );
};

Et l'inverse pour les événements mousedownScroll et mouseupScroll.

$.fn.mousedownScroll = function(data, fn) {
    if ( fn == null ) {
        fn = data;
        data = null;
    }    
    var o = fn;
    fn = function(e){
        if(inScrollRange(e)) {
            e.type = "mousedownscroll";
            return o.apply(this, arguments);
        }
        return;
    };
    if ( arguments.length > 0 ) {
        return this.bind( "mousedown", data, fn );
    } 
    return this.trigger( "mousedown" );
};

En passant, je pense que la largeur de la barre de défilement est un paramètre du système d'exploitation.

15
Alexander

Résolu:

Une détection de clic de barre de défilement la plus courte que je pourrais trouver, testée sur IE, Firefox, Chrome.

var clickedOnScrollbar = function(mouseX){
  if( $(window).outerWidth() <= mouseX ){
    return true;
  }
}

$(document).mousedown(function(e){
  if( clickedOnScrollbar(e.clientX) ){
    alert("clicked on scrollbar");
  }
});

Exemple de travail: https://jsfiddle.net/s6mho19z/

22
Dariusz Sikorski

J'ai eu le même problème dans un projet précédent, et je recommande cette solution. Ce n'est pas très propre mais cela fonctionne et je doute que nous puissions faire beaucoup mieux avec le langage HTML. Voici les deux étapes de ma solution:

1. Mesurez la largeur de la barre de défilement sur votre environnement de bureau.

Pour ce faire, au démarrage de l'application, vous effectuez les opérations suivantes:

Ajoutez l'élément suivant au corps: 

<div style='width: 50px; height: 50px; overflow: scroll'><div style='height: 1px;'/></div>

Mesurez le avec de la division interne de l'élément ajouté précédemment avec .width () de jQUery, et stockez la largeur de la barre de défilement quelque part (la largeur de la barre de défilement est égale à 50 - div interne avec

Supprimez l'élément supplémentaire utilisé pour mesurer la barre de défilement (maintenant que vous avez le résultat, supprimez l'élément que vous avez ajouté au corps).

Toutes ces étapes ne doivent pas être visibles par l'utilisateur et vous avez la largeur de la barre de défilement sur votre système d'exploitation

Par exemple, vous pouvez utiliser cet extrait:

var measureScrollBarWidth = function() {
    var scrollBarMeasure = $('<div />');
    $('body').append(scrollBarMeasure);
    scrollBarMeasure.width(50).height(50)
        .css({
            overflow: 'scroll',
            visibility: 'hidden',
            position: 'absolute'
        });

    var scrollBarMeasureContent = $('<div />').height(1);
    scrollBarMeasure.append(scrollBarMeasureContent);

    var insideWidth = scrollBarMeasureContent.width();
    var outsideWitdh = scrollBarMeasure.width();
    scrollBarMeasure.remove();

    return outsideWitdh - insideWidth;
};

2. Vérifiez si un clic est sur la barre de défilement.

Maintenant que vous avez la largeur de la barre de défilement, vous pouvez, avec les coordonnées de l’événement, calculer les coordonnées de cet événement par rapport au rectangle de position de la barre de défilement et réaliser des choses impressionnantes ...

Si vous souhaitez filtrer les clics, vous pouvez renvoyer false dans le gestionnaire pour empêcher leur propagation.

9
Samuel Rossille

Assurez-vous que le contenu de votre scollarea remplit complètement la div du parent.

Ensuite, vous pouvez différencier les clics sur votre contenu des clics sur votre conteneur.

html:

<div class='test container'><div class='test content'></div></div>
<div id="results">please click</div>

css:

#results {
  position: absolute;
  top: 110px;
  left: 10px;
}

.test {
  position: absolute;
  left: 0;
  top: 0;
  width: 100px;
  height: 100px;
  background-color: green;
}

.container {
  overflow: scroll;
}

.content {
  background-color: red;
}

js:

function log( _l ) {
  $("#results").html( _l );
}

$('.content').on( 'mousedown', function( e ) {
  log( "content-click" );
  e.stopPropagation();
});

$('.container').on( 'mousedown', function( e ) {
  var pageX = e.pageX;
  var pageY = e.pageY;
  log( "scrollbar-click" );
});

http://codepen.io/jedierikb/pen/JqaCb

7
jedierikb

Utilisez la solution suivante pour détecter si l'utilisateur a cliqué avec la souris sur la barre de défilement de l'élément. Je n'ai pas testé son fonctionnement avec la barre de défilement de window. Je suppose que la solution de Pete fonctionne mieux avec les parchemins de fenêtre.

window.addEventListener("mousedown", onMouseDown);

function onMouseDown(e) {
  if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) 
  {
      // mouse down over scroll element
   }
}
2
Antti

Je vais soumettre ma propre réponse et accepter la réponse d'Alexander , parce que cela a parfaitement fonctionné, et le vote positif la réponse de Samuel , car elle calcule correctement la largeur de la barre de défilement, ce dont j'avais besoin également.


Cela étant dit, j'ai décidé de créer deux événements indépendants au lieu d'essayer d'écraser/de remplacer l'événement mousedown de JQuery.

Cela me donnait la souplesse dont j'avais besoin sans me mêler des événements de JQuery, et c'était assez facile à faire.

  • mousedownScroll
  • mousedownContent

Vous trouverez ci-dessous les deux implémentations utilisant Alexanders et le mien . Les deux fonctionnent comme je le voulais à l’origine, mais le premier est probablement le meilleur.


  • Voici un JSFiddle qui met en œuvre la réponse d'Alexandre + la réponse de Samuel. 

    $.fn.hasScroll = function(axis){
        var overflow = this.css("overflow"),
            overflowAxis;
    
        if(typeof axis == "undefined" || axis == "y") overflowAxis = this.css("overflow-y");
        else overflowAxis = this.css("overflow-x");
    
        var bShouldScroll = this.get(0).scrollHeight > this.innerHeight();
    
        var bAllowedScroll = (overflow == "auto" || overflow == "visible") ||
                             (overflowAxis == "auto" || overflowAxis == "visible");
    
        var bOverrideScroll = overflow == "scroll" || overflowAxis == "scroll";
    
        return (bShouldScroll && bAllowedScroll) || bOverrideScroll;
    };
    
    $.fn.mousedown = function(data, fn) {
        if ( fn == null ) {
            fn = data;
            data = null;
        }    
        var o = fn;
        fn = function(e){
            if(!inScrollRange(e)) {
                return o.apply(this, arguments);
            }
            return;
        };
        if ( arguments.length > 0 ) {
            return this.bind( "mousedown", data, fn );
        } 
        return this.trigger( "mousedown" );
    };
    
    $.fn.mouseup = function(data, fn) {
        if ( fn == null ) {
            fn = data;
            data = null;
        }    
        var o = fn;
        fn = function(e){
            if(!inScrollRange(e)) {
                return o.apply(this, arguments);
            }
            return;
        };
        if ( arguments.length > 0 ) {
            return this.bind( "mouseup", data, fn );
        } 
        return this.trigger( "mouseup" );
    };
    
    $.fn.mousedownScroll = function(data, fn) {
        if ( fn == null ) {
            fn = data;
            data = null;
        }    
        var o = fn;
        fn = function(e){
            if(inScrollRange(e)) {
                e.type = "mousedownscroll";
                return o.apply(this, arguments);
            }
            return;
        };
        if ( arguments.length > 0 ) {
            return this.bind( "mousedown", data, fn );
        } 
        return this.trigger( "mousedown" );
    };
    
    $.fn.mouseupScroll = function(data, fn) {
        if ( fn == null ) {
            fn = data;
            data = null;
        }    
        var o = fn;
        fn = function(e){
            if(inScrollRange(e)) {
                e.type = "mouseupscroll";
                return o.apply(this, arguments);
            }
            return;
        };
        if ( arguments.length > 0 ) {
            return this.bind( "mouseup", data, fn );
        } 
        return this.trigger( "mouseup" );
    };
    
    var RECT = function(){
        this.top = 0;
        this.left = 0;
        this.bottom = 0;
        this.right = 0;
    }
    
    function inRect(rect, x, y){
        return (y >= rect.top && y <= rect.bottom) &&
               (x >= rect.left && x <= rect.right)
    }
    
    
    var scrollSize = measureScrollWidth();
    
    function inScrollRange(event){
        var x = event.pageX,
            y = event.pageY,
            e = $(event.target),
            hasY = e.hasScroll(),
            hasX = e.hasScroll("x"),
            rX = null,
            rY = null,
            bInX = false,
            bInY = false
    
        if(hasY){ 
            rY = new RECT();
            rY.top = e.offset().top;
            rY.right = e.offset().left + e.width();
            rY.bottom = rY.top +e.height();
            rY.left = rY.right - scrollSize;
    
            //if(hasX) rY.bottom -= scrollSize;
            bInY = inRect(rY, x, y);
        }
    
        if(hasX){
            rX = new RECT();
            rX.bottom = e.offset().top + e.height();
            rX.left = e.offset().left;
            rX.top = rX.bottom - scrollSize;
            rX.right = rX.left + e.width();
    
            //if(hasY) rX.right -= scrollSize;
            bInX = inRect(rX, x, y);
        }
    
        return bInX || bInY;
    }
    
    $(document).on("mousedown", function(e){
        //Determine if has scrollbar(s)
        if(inScrollRange(e)){
            $(e.target).trigger("mousedownScroll");
        }
    });
    
    $(document).on("mouseup", function(e){
        if(inScrollRange(e)){
            $(e.target).trigger("mouseupScroll");
        }
    });
    });
    
    function measureScrollWidth() {
        var scrollBarMeasure = $('<div />');
        $('body').append(scrollBarMeasure);
        scrollBarMeasure.width(50).height(50)
            .css({
                overflow: 'scroll',
                visibility: 'hidden',
                position: 'absolute'
            });
    
        var scrollBarMeasureContent = $('<div />').height(1);
        scrollBarMeasure.append(scrollBarMeasureContent);
    
        var insideWidth = scrollBarMeasureContent.width();
        var outsideWitdh = scrollBarMeasure.width();
        scrollBarMeasure.remove();
    
        return outsideWitdh - insideWidth;
    };
    
  • Voici un JSFiddle de ce que j'ai décidé de faire à la place.

    $.fn.hasScroll = function(axis){
        var overflow = this.css("overflow"),
            overflowAxis,
            bShouldScroll,
            bAllowedScroll,
            bOverrideScroll;
    
        if(typeof axis == "undefined" || axis == "y") overflowAxis = this.css("overflow-y");
        else overflowAxis = this.css("overflow-x");
    
        bShouldScroll = this.get(0).scrollHeight > this.innerHeight();
    
        bAllowedScroll = (overflow == "auto" || overflow == "visible") ||
            (overflowAxis == "auto" || overflowAxis == "visible");
    
        bOverrideScroll = overflow == "scroll" || overflowAxis == "scroll";
    
        return (bShouldScroll && bAllowedScroll) || bOverrideScroll;
    };
    
    $.fn.mousedownScroll = function(fn, data){
        var ev_mds = function(e){
            if(inScrollRange(e)) fn.call(data, e);
        }
        $(this).on("mousedown", ev_mds);
        return ev_mds;
    };
    
    $.fn.mouseupScroll = function(fn, data){
        var ev_mus = function(e){
            if(inScrollRange(e)) fn.call(data, e);
        }
        $(this).on("mouseup", ev_mus);
        return ev_mus;
    };
    
    $.fn.mousedownContent = function(fn, data){
        var ev_mdc = function(e){
            if(!inScrollRange(e)) fn.call(data, e);
        }
    
        $(this).on("mousedown", ev_mdc);
    
        return ev_mdc;
    };
    
    $.fn.mouseupContent = function(fn, data){
        var ev_muc = function(e){
            if(!inScrollRange(e)) fn.call(data, e);
        }
        $(this).on("mouseup", ev_muc);
        return ev_muc;
    };
    
    var RECT = function(){
        this.top = 0;
        this.left = 0;
        this.bottom = 0;
        this.right = 0;
    }
    
    function inRect(rect, x, y){
        return (y >= rect.top && y <= rect.bottom) &&
            (x >= rect.left && x <= rect.right)
    }
    
    var scrollSize = measureScrollWidth();
    
    function inScrollRange(event){
        var x = event.pageX,
            y = event.pageY,
            e = $(event.target),
            hasY = e.hasScroll(),
            hasX = e.hasScroll("x"),
            rX = null,
            rY = null,
            bInX = false,
            bInY = false
    
        if(hasY){ 
            rY = new RECT();
            rY.top = e.offset().top;
            rY.right = e.offset().left + e.width();
            rY.bottom = rY.top +e.height();
            rY.left = rY.right - scrollSize;
    
            //if(hasX) rY.bottom -= scrollSize;
            bInY = inRect(rY, x, y);
        }
    
        if(hasX){
            rX = new RECT();
            rX.bottom = e.offset().top + e.height();
            rX.left = e.offset().left;
            rX.top = rX.bottom - scrollSize;
            rX.right = rX.left + e.width();
    
            //if(hasY) rX.right -= scrollSize;
            bInX = inRect(rX, x, y);
        }
    
        return bInX || bInY;
    }
    
    function measureScrollWidth() {
        var scrollBarMeasure = $('<div />');
        $('body').append(scrollBarMeasure);
        scrollBarMeasure.width(50).height(50)
            .css({
                overflow: 'scroll',
                visibility: 'hidden',
                position: 'absolute'
            });
    
        var scrollBarMeasureContent = $('<div />').height(1);
        scrollBarMeasure.append(scrollBarMeasureContent);
    
        var insideWidth = scrollBarMeasureContent.width();
        var outsideWitdh = scrollBarMeasure.width();
        scrollBarMeasure.remove();
    
        return outsideWitdh - insideWidth;
    };
    
1
ShadowScripter

La seule solution qui fonctionne pour moi (uniquement testé sur IE11):

$(document).mousedown(function(e){
    bScrollbarClicked = e.clientX > document.documentElement.clientWidth || e.clientY > document.documentElement.clientHeight;

});

1
Pete Alvin

J'avais besoin de détecter scrollbar sur mousedown mais pas sur window mais sur div, Et j'ai eu un élément qui remplit le contenu, que j'utilisais pour détecter la taille sans scrollbar:

.element {
    position: relative;
}
.element .fill-node {
    position: absolute;
    left: 0;
    top: -100%;
    width: 100%;
    height: 100%;
    margin: 1px 0 0;
    border: none;
    opacity: 0;
    pointer-events: none;
    box-sizing: border-box;
}

le code de détection était similaire à @DariuszSikorski answer mais en incluant offset et en utilisant le noeud qui était à l'intérieur, scrollable:

function scrollbar_event(e, node) {
    var left = node.offset().left;
    return node.outerWidth() <= e.clientX - left;
}

var node = self.find('.fill-node');
self.on('mousedown', function(e) {
   if (!scrollbar_event(e, node)) {
      // click on content
   }
});
1
jcubic

Il convient de noter que sur Mac OSX 10.7+, il n’existe pas de barres de défilement persistantes. Les barres de défilement apparaissent lorsque vous faites défiler et disparaissent lorsque vous avez terminé. Ils sont aussi beaucoup plus petits que 18px (ils sont 7px).

Capture d'écran: http://i.stack.imgur.com/zdrUS.png

0
Applehat

Il suffit de détecter si element.offsetLeft + element.scrollWidth < event.clientX sous element.onmousedown. La variable scrollWidth d'un élément n'inclut pas la largeur de la barre de défilement. Cela inclut uniquement celui du contenu intérieur. Si vous souhaitez également détecter avec une barre de défilement horizontale, la même chose peut être appliquée à cette échelle en utilisant la hauteur et la position Y au lieu de la largeur et la position X.

0
Grant Gryczan