Determine whether user clicking scrollbar or conte

2019-01-03 17:40发布

I'm trying to create custom events in JQuery that are supposed to detect when a scrollbar is clicked1.
I know there's lots of text, but all my questions are boldfaced and there's a JSFiddle example you can work on straight away.

Because I haven't found any built in functionality for this,
I had to create a hasScroll function, checking if the element has a scrollbar,

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

and an inScrollRange function, checking if the click performed was within the scroll range.

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

Are all scrollbar sizes uniform? E.g in Firefox and IE it's 18px.
Assuming there are no customized scrollbars, is there any extra padding or sizes in some browsers?

These functions all perform as intended (from what I can discern).

Making custom events was a bit trickier, but I got it to work somewhat. The only problem is that if the element clicked has a mousedown/up event attached to it, that will be triggered as well.

I can't seem to stop the other events from triggering while simultaneously triggering, what I call, the mousedownScroll/mouseupScroll events.

$.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.");
});

How do I stop the other "click" events from triggering?

While I'm asking, please feel free to optimize the code as much as possible.

Here's a JSFiddle to mess around with.

The reason I'm making this is because of a bigger plugin I'm developing. It's got a custom context menu that is showing up when I right click one of the scrollers. I don't want that. So I thought I should make an event that checks for scroll clicks (mouseup/downs) and then prevent the context menu from being displayed. In order to do that though, I need the scroll click to come before the normal click, and also, if possible, stop the normal clicks from firing.

I'm just thinking out loud here but maybe there's a way to get all the functions that are bound to the element and then switch the order in which they were added? I know that functions are executed in the order they were added (1st added 1st called), so, if I could tap into that process, perhaps the whole "registering" of the event to JQuery could just be inserted before the click events.


1 can only use mousedown/mouseup because click doesn't trigger when clicking on a scrollbar. If this is false, please provide a working example/code

10条回答
【Aperson】
2楼-- · 2019-01-03 17:45

I needed to detect scrollbar on mousedown but not on window but on div, and I've had element that fill the content, that I was using to detect size without 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;
}

the code for detect was similar to @DariuszSikorski answer but including offset and using the node that was inside 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
   }
});
查看更多
SAY GOODBYE
3楼-- · 2019-01-03 17:45

The only solution that works for me (only tested against IE11):

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

});

查看更多
Luminary・发光体
4楼-- · 2019-01-03 17:47

You may probably use this hack.

You could try hijacking the mousedown and mouseup events and avoiding them when click on a scrollbar with your custom powered function.

$.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" );
};

And the inverse for mousedownScroll and mouseupScroll events.

$.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" );
};

By the way, I think the scrollbar width is an OS setting.

查看更多
Melony?
5楼-- · 2019-01-03 17:50

Use following solution to detect if user clicked mouse over element's scrollbar. Didn't test how it works with window's scrollbar. I guess Pete's solution works better with window scrolls.

window.addEventListener("mousedown", onMouseDown);

function onMouseDown(e) {
  if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) 
  {
      // mouse down over scroll element
   }
}
查看更多
Juvenile、少年°
6楼-- · 2019-01-03 17:53

It should be pointed out that on Mac OSX 10.7+, there are not persistant scroll bars. Scroll bars appear when you scroll, and disappear when your done. They are also much smaller then 18px (they are 7px).

Screenshot: http://i.stack.imgur.com/zdrUS.png

查看更多
时光不老,我们不散
7楼-- · 2019-01-03 17:56

I had the same problem in a previous project, and i recommend this solution. It's not very clean but it works and i doubt we can do much better with html. Here are the two steps of my solution:

1. Measure the width of the scrollbar on your Desktop environment.

In order to achieve this, at application startup, you perform the following things:

Add the following element to the body:

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

Measure the with of the inner div of the previously added element with jQUery's .width(), and store the width of the scrollbar somewhere (the width of the scollbar is 50 - inner div's with)

Remove the extra element used to measure scrollbar (now that you have the result, remove the element that you added to the body).

All these steps should not be visible by the user and you have the width of the scrollbar on your OS

For example, you can use this snippet:

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. Check if a click is on the scrollbar.

Now that you have the width of the scrollbar, you can with the coordinates of the event compute the coordinates of the event relative to the scrollbar's location rectangle and perfom awesome things...

If you want to filter the clicks, you can return false in the handler to prevent their propagation.

查看更多
登录 后发表回答