49

Вопрос по jquery, events – Определите, щелкает ли пользователь по полосе прокрутки или по содержимому (щелчок по собственной полосе прокрутки)

Я пытаюсь создать пользовательские события в 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;
}

Are all scrollbar sizes uniform? E.g in Firefox and IE it's 18px.
Предполагая, что нет настраиваемых полос прокрутки, есть ли какие-либо дополнительные отступы или размеры в некоторых браузерах?

Все эти функции работают как задумано (из того, что я могу различить).

Создание пользовательских событий было немного сложнее, но я заставил его работать несколько. Единственная проблема заключается в том, что если к элементу, к которому щелкнули, прикреплено событие 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.");
});

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

Пока я спрашиваю, пожалуйста, не стесняйтесь максимально оптимизировать код.

Вот JSFiddle, с которым можно возиться.

Причина, по которой я это делаю, заключается в том, что я разрабатываю плагин большего размера. У него есть пользовательское контекстное меню, которое появляется, когда я щелкаю правой кнопкой мыши по одному из скроллеров. Я этого не хочу. Поэтому я подумал, что должен создать событие, которое проверяет щелчки при прокрутке (mouseup / downs), а затем предотвращает отображение контекстного меню. Однако, чтобы сделать это, мне нужно, чтобы щелчок прокрутки предшествовал обычному щелчку, а также, если возможно, не давал срабатывать обычным щелчкам.

I'm just thinking out loud here but может быть, есть способ получить все функции, которые привязаны к элементу, и затем изменить порядок, в котором они были добавлены? Я знаю, что функции выполняются в том порядке, в котором они были добавлены (1-й добавлен, 1-й вызван), поэтому, если бы я мог подключиться к этому процессу, возможно, весь процесс "регистрации" события в JQuery можно было просто вставить до события click.


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

  • Можно ли нанять вас, чтобы построить мне что-то, что использует это? Мне нужна такая функция, чтобы отменить действие ajax и функцию scrolltop, пока нажата полоса прокрутки, а затем возобновить обе функции, когда щелчок отпущен. это немного выше моего уровня квалификации, хотя.

    от
  • @ r3wt Нанимаете меня? Нет необходимости в этом друге-о. Единственный способ научиться - это изучать код и писать его самостоятельно! Наконец-то у меня появилось свободное время иmade this, Вы должны найти основные концепции, которые вам нужны, в примере.

    от ShadowScripter
  • Решение с помощью jedierikb (прокрутка вниз) намного лучше, потому что оно не полагается на вычисления или хаки, а на то, какой элемент вызвал событие. И JQuery на самом деле не требуется.

    от
  • Спасибо за слова, приятель. Я проверю это завтра. Я ценю головы и добрые слова.

    от
  • @ www139 Спасибо ;-)

    от
  • Не работает на MacOS, когда полоса прокрутки появляется только при прокрутке

    от
  • Спасибо Александр, этоworked perfectly, Извините за поздний прием, вы действительно должны получить больше голосов за это, это замечательно! 25 очков на вашем пути;)

    от ShadowScripter
  • Идеальное решение, только работает. :-) СПАСИБО

    от
  • Выполнение измерений для определения ширины / высоты прокрутки подвержено ошибкам - при добавлении и удалении мыши с ноутбука с сенсорной панелью на Mac происходит переполнение: полосы прокрутки динамически появляются и исчезают.

    от
  • @ ShadowScripter, я рад, что это было полезно;)

    от
  • Хорошее решение Вы также можете сделать это с одним событием на.container через условие как:if($(e.target).is($(this))) //scrollbar-click. jsfiddle.net/3tzj0bps

    от
  • хорошее решение: использованиеclientWidth а такжеoffsetWidth вместе это хороший способ узнать, где произошел щелчок.

    от
  • Простое и очевидное решение, спасибо

    от
  • Удивительный код! Хорошая работа.

    от
  • @ThiefMaster Я уже знаю об этом, но этоshould not влияет на поведение по умолчанию прокрутки браузеров. Может быть, вы неправильно поняли, что я пытаюсь сделать? Смысл в том, чтобы добавить дополнительные события в JQuery, чтобы я мог заставить другие мои плагины делать что-то, когда на него нажимают. Больше функциональности - & gt; больше вариантов. В моем примере я создал пользовательское контекстное меню, но я не хочу, чтобы контекстное меню вызывалось, если пользователь щелкает правой кнопкой мыши по полосе прокрутки (имитирует то же поведение, что и меню браузера). Чтобы решить эту проблему, я подумал, что могу добавить событие, которое не позволяет всплыть контекстному меню.

    от ShadowScripter
  • @mrtsherman Да, но они запускаютсяmousedown а такжеmouseup, Я называю их "нажмите" События в моем посте, а затем также объяснить это в самом низу.

    от ShadowScripter
  • Пожалуйста, не меняйте стандартное поведение<select multiple> управления. Это очень раздражает, если они не ведут себя так, как ожидают пользователи. Например, нажмите Shift, чтобы выбрать диапазон, Ctrl-клик, чтобы выбрать конкретные элементы. В некоторых приложениях он есть, поэтому каждый щелчок по элементу переключает его состояние - и, конечно, это не то, что ожидается. Итак: оставьте поведение по умолчаниюor использовать полностью настраиваемый виджет, который не выглядит как обычный элемент управления, как это происходит, например в родном приложении

    от ThiefMaster♦
  • Ах понятно. Спасибо за разъяснение этого для меня. +1 и в избранное. Надеюсь увидеть ответ (я уверен, что вы тоже!)

    от mrtsherman
  • Размеры полосы прокрутки не являются одинаковыми. В WebKit вы можете настроить их довольно сильно.css-tricks.com/examples/WebKitScrollbars

    от mrtsherman
10 ответов
  • 1

    Единственное решение

    которое работает для меня (проверено только на IE11):

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

    });

  • 0

    Следует отметить

    что в Mac OSX 10.7+ нет постоянных полос прокрутки. Полосы прокрутки появляются при прокрутке и исчезают, когда вы закончите. Они также намного меньше 18px (они 7px).

    Скриншот: http://i.stack.imgur.com/zdrUS.png

  • 15

    Вы, вероятно, можете использовать этот хак.

    Вы можете попробовать угнатьmousedown а такжеmouseup события и избегая их, когда вы нажимаете на полосу прокрутки с вашей собственной активной функцией.

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

    И обратное дляmousedownScroll а также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" );
    };
    

    Кстати, я думаю, что ширина полосы прокрутки является настройкой ОС.

  • 2

    Используйте следующее решение

    чтобы определить, щелкнул ли пользователь мышью по полосе прокрутки элемента. Не проверял, как это работает с полосой прокрутки окна. Полагаю, решение Пита лучше работает с прокручиванием окон.

    window.addEventListener("mousedown", onMouseDown);
    
    function onMouseDown(e) {
      if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) 
      {
          // mouse down over scroll element
       }
    }
    

  • 23

    Самое короткое обнаружение щелчков полосы прокрутки

    Solved:

    которое я смог придумать, протестировано на 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/

  • 7

    Убедитесь

    что содержимое вашей scollarea полностью [пере] заполняет родительский 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

  • 0

    Просто определить, если

    element.offsetLeft + element.scrollWidth < event.clientX подelement.onmousedown, ЭлементscrollWidth не включает ширину полосы прокрутки. Это включает только внутреннее содержание. Если вы также хотите обнаруживать с помощью горизонтальной полосы прокрутки, то же самое можно применить к этому масштабу, используя высоту и позицию Y вместо ширины и позиции X.

  • 1

    Я отправлю свой ответ и принимаю

    Александр ответ, потому что он сделал это работает отлично, и upvoteОтвет Самуилапотому что он правильно рассчитывает ширину полосы прокрутки, что мне и нужно.

    При этом я решил сделать два независимых события вместо того, чтобы пытаться перезаписать / переопределить JQuery.mousedown событие.

    Это дало мне необходимую гибкость, не мешая собственным событиям JQuery, и было довольно легко сделать.

    mousedownScroll mousedownContent

    Ниже приведены две реализации, использующие Александер и мою собственную. Оба работают так, как я хотел, но первый, вероятно, лучший.

    Here's a JSFiddle that implements Alexander's answer + Samuel's answer.

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

    Here's a JSFiddle of what I decided to do instead.

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

    Мне нужно было обнаружить полосу прокрутки на mousedown

    но не на окне, а на div, и у меня есть элемент, который заполняет содержимое, которое я использовал для определения размера без полосы прокрутки:

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

  • 9

    У меня была такая же проблема в предыдущем проекте

    и я рекомендую это решение. Он не очень чистый, но он работает, и я сомневаюсь, что мы сможем добиться большего с HTML. Вот два шага моего решения:

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

    Для этого при запуске приложения выполняются следующие действия:

    Добавьте следующий элемент в тело:

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

    С помощью jQUery's .width () измерьте внутреннее значение div для ранее добавленного элемента и сохраните где-нибудь ширину полосы прокрутки (ширина полосы прокрутки равна 50 - с внутренним делителем)

    Удалите дополнительный элемент, используемый для измерения полосы прокрутки (теперь, когда у вас есть результат, удалите элемент, который вы добавили в тело).

    Все эти шаги не должны быть видны пользователю, и у вас есть ширина полосы прокрутки в вашей ОС

    Например, вы можете использовать этот фрагмент:

    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.

    Теперь, когда у вас есть ширина полосы прокрутки, вы можете с помощью координат события вычислить координаты события относительно прямоугольника расположения полосы прокрутки и выполнить удивительные вещи ...

    Если вы хотите отфильтровать щелчки, вы можете вернуть false в обработчике, чтобы предотвратить их распространение.