确定用户是点击滚动条还是内容(原生滚动条的onclick事件)

67

我正在尝试使用jQuery创建自定义事件,以便检测当滚动条被单击1时的情况。
虽然有很多文本,但是所有我的问题都是加粗的,而且还有一个JSFiddle示例可以直接操作。

因为我没有找到任何内置的功能来实现这一点,
所以我创建了一个hasScroll函数,用于检查元素是否有滚动条。

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

还需要一个inScrollRange函数,用于检查所执行的点击是否在滚动范围内。

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

滚动条大小是否都一致?例如在Firefox和IE中都是18像素。
假设没有自定义滚动条,那么某些浏览器中是否有额外的填充或大小?

这些函数看起来都按预期执行(据我所见)。

制作自定义事件有点棘手,但我成功地让它工作了一些。唯一的问题是,如果被点击的元素附加了mousedown/up事件,那么这些事件也会被触发。

我似乎无法阻止其他事件触发同时触发我所谓的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.");
});

如何停止触发其他的“单击”事件?

在此期间,请随意尽可能地优化代码。

这里有一个JSFiddle供您玩耍。

我正在制作这个项目,是因为我正在开发一个更大的插件。它有一个自定义上下文菜单,显示在我右键单击其中一个滚动条时。我不想要那个。所以我想做一个事件来检查滚动点击(mousedown/up),然后防止上下文菜单被显示出来。但是,为了做到这一点,我需要让滚动点击先于正常点击,并且在可能的情况下,停止正常点击的触发。

我只是在这里公开思考,但也许有一种方法可以获取绑定到元素的所有函数,然后切换它们添加的顺序?我知道函数按它们添加的顺序执行(先添加的先调用),因此,如果我能介入该过程,或许整个将事件注册到JQuery的过程就可以插入到单击事件之前。


1只能使用mousedown/mouseup,因为在滚动条上单击时,click不会触发。如果这是错误的,请提供一个可行的示例/代码。


2
滚动条大小不统一。在 Webkit 中,您可以对其进行相当大的自定义。http://css-tricks.com/examples/WebKitScrollbars/ - mrtsherman
1
请不要更改<select multiple>控件的标准行为。如果它们的行为与用户期望的不同,那么这将非常令人恼火。例如,使用Shift键点击以选择范围,使用Ctrl键点击以选择特定项。有些应用程序会使每次单击项目都切换其状态 - 当然这不是人们所期望的。因此:保持默认行为使用完全自定义的小部件,它看起来不像普通控件,就像在本地应用程序中发生的那样。 - ThiefMaster
2
@ThiefMaster 我已经知道了,但这不应该影响浏览器滚动的默认行为。也许你误解了我的意图?它的目的是添加额外的事件到JQuery中,以便我可以在被点击时让我的其他插件做一些事情。更多功能->更多选项。在我的例子中,我创建了一个自定义上下文菜单,但我不希望在用户右键单击滚动条时弹出上下文菜单(模仿浏览器菜单的相同行为)。为了解决这个问题,我想添加一个事件来防止上下文菜单弹出。 - ShadowScripter
1
@mrtsherman 是的,但它们会在 mousedownmouseup 上触发。我在我的帖子中将它们称为 "click" 事件,并在最底部进行了解释。 - ShadowScripter
1
啊,我明白了。谢谢你为我澄清。+1并收藏。希望能看到答案(我肯定你也是!) - mrtsherman
显示剩余6条评论
12个回答

28

解决:

以下是我能想到的最短的滚动条点击检测方法,已在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");
  }
});

工作示例: https://jsfiddle.net/s6mho19z/


@www139 谢谢你 ;-) - Dariusz Sikorski
好的解决方案:同时使用clientWidthoffsetWidth是了解点击发生位置的好方法。 - Sebas
9
当滚动条仅在滚动时出现时,在MacOS上无法正常工作。 - Eugene Fidelin
在 macOS 上,滚动条出现在页面内容的顶部,而不是页面的外部。 - undefined

24

使用以下解决方案来检测用户是否在元素的滚动条上单击了鼠标。没有测试它与窗口滚动条的配合情况。我猜Pete的解决方案在窗口滚动上运作得更好。

window.addEventListener("mousedown", onMouseDown);

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

2
这比上面两个答案更好,但它不能用于叠加滚动条或从右到左的阅读顺序。 - Adam Leggett

19

你可以尝试使用这个技巧。

你可以试着通过劫持mousedownmouseup事件,在使用自定义函数点击滚动条时避免触发它们。

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

对于 mousedownScrollmouseupScroll 事件,反之亦然。

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

顺便说一下,我认为滚动条的宽度是由操作系统设置决定的。


谢谢你,Alexander。它完美地运行了。很抱歉晚才接受,你真的应该得到更多的赞,这太棒了!25分即将到达你的账户;) - ShadowScripter
4
jedierikb提供的解决方案(向下滚动)更好,因为它不依赖于计算或技巧,而是依靠触发事件的元素。而且jQuery并不是必需的。 - Ivan Castellanos

12

确保滚动区域的内容完全填充父级 div。

然后,您可以区分点击内容和点击容器。

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


2
好的解决方案。您还可以通过类似以下条件的.container上的单个事件来完成此操作:if($(e.target).is($(this))) //scrollbar-click。https://jsfiddle.net/3tzj0bps/ - malihu

9
这里有许多涉及到event.clientX, element.clientHeight等的答案,它们都是错误的,请勿使用。
  • 如上所述,有些平台中overflow:scroll滚动条会呈现为覆盖层,或者您可能已经通过overflow:overlay进行了强制设置。
  • 当Mac插入或拔出鼠标时,滚动条可能会在持久性和覆盖层之间切换。这会摧毁“启动时测量”的技术。
  • 垂直滚动条在从右到左的阅读顺序中出现在左侧。这会破坏比较客户端宽度,除非您有一堆特殊逻辑来处理从右到左的阅读顺序,但我敢打赌您可能没有始终进行RTL测试,因此这些逻辑会失效。

您需要查看event.target。如果必要,请使用一个占据滚动元素所有客户区域的内部元素,并检查event.target是否是该元素或其中一个后代元素。


7

我在之前的项目中也遇到了这个问题,我推荐使用这个解决方案。虽然不是很完美,但它可以起作用,我认为我们无法通过HTML做得更好。以下是我的解决方案的两个步骤:

1. 测量您的桌面环境中滚动条的宽度。

为了实现这一点,在应用程序启动时,您需要执行以下操作:

将以下元素添加到主体中:

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

使用jQuery的 .width() 测量先前添加的元素的内部 div 的宽度,并将滚动条的宽度存储在某个地方(滚动条的宽度是 50 - 内部 div 的宽度)

删除用于测量滚动条的额外元素(现在您已经得到了结果,请删除您添加到主体的元素)。

所有这些步骤对用户来说都不可见,您就可以得到操作系统中滚动条的宽度。

例如,您可以使用以下代码片段:

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. 检查点击是否在滚动条上。

现在你已经知道了滚动条的宽度,你可以使用事件的坐标计算出相对于滚动条位置矩形的事件坐标,并执行出色的操作...

如果你想过滤掉一些点击事件,你可以在处理程序中返回false来阻止它们的传播。


5
测量滚动条宽度/高度容易出错——在 Mac 上,将鼠标从带有触摸板的笔记本电脑中添加或移除会使 overflow:scroll 滚动条动态出现和消失。 - jedierikb

1

对我有效的唯一解决方案(仅在IE11上进行了测试):

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

0
我需要在mousedown时检测div上的滚动条,而不是window上的滚动条,并且我有一个元素填充内容,我正在使用它来检测没有滚动条的大小。
.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;
}

检测代码与@DariuszSikorski的答案类似,但包括偏移量并使用滚动内部的节点:

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

0
应该指出的是,在Mac OSX 10.7+上,没有持久的滚动条。当您滚动时,滚动条会出现,并在完成后消失。它们也比18px小得多(它们只有7px)。

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


0
我会提交我的答案并接受Alexander的答案,因为它使其完美运行,并点赞Samuel的答案,因为它正确计算了滚动条的宽度,这也是我所需要的。

话虽如此,我决定创建两个独立的事件,而不是尝试覆盖 JQuery 的 mousedown 事件。

这样做可以让我灵活地使用所需的功能,而不会干扰 JQuery 自己的事件,并且非常容易实现。

  • mousedownScroll
  • mousedownContent

以下是使用 Alexanders 和我的两种实现方式。两种方式都能按照我最初的意图正常工作,但前者可能更好。


  • 这里有一个JSFiddle,它实现了Alexander和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)

你能否接受雇佣,为我构建一个使用这个功能的东西?我需要一个类似的函数来在滚动条被点击时取消Ajax操作和scrollTop函数,然后在释放点击时恢复这两个函数。虽然这有点超出了我的技能水平。 - r3wt
@r3wt 雇用我?不需要这样做,朋友。学习的唯一方法就是研究代码并亲自编写!我终于有了一些空闲时间,制作了这个。你应该在这个例子中找到所需的核心概念。 - ShadowScripter
谢谢你的话,伙计。我明天会去看看。感谢你提醒和友善的话语。 - r3wt
请不要尝试计算滚动条的宽度。如我在上面的回答中所述,这样做会失败。 - Adam Leggett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接