Инструменты jQuery с возможностью прокрутки с помощью колесика мыши - прокрутка ОДНОЙ позиции и остановка - PullRequest
7 голосов
/ 04 января 2012

Я использую bind / unbind для прокрутки колесика мыши, основываясь на этом ответе SO:

Jquery, отменяет привязку события mousewheel, затем повторно связывает его после завершения действий?

Я туннелирую дерево событий из дельты, чтобы указывать только значения X мышиных колес. Все работает хорошо. Проблема, которую я пытаюсь преодолеть: я хочу просто прокрутить ОДНУ панель вперед, затем остановить прокрутку. В настоящее время я отменяю привязку события mousewheel сразу после перемещения, и это фактически останавливает прокрутку ... но отмена привязки события mousewheel также портит страницу. Что мне нужно, так это уметь вычислить самое первое значение deltaX для направления, затем сделать шаг и прекратить прослушивание. Нужно ли искать автопрокрутку для ответов? Привязка / развязка чувствуется клугой, но я не могу понять, как выжить после одного хода, и в то же время иметь возможность прокручивать после того, как этот ход завершен. Вот моя функция колесика мыши:

function mouseHandler(event, delta) {
$('.homeScrollable').bind('mousewheel', mouseHandler);

var deltaX = event.originalEvent.wheelDeltaX;
//console.log(event.originalEvent.wheelDeltaX);

if(deltaX <= 0) {
    //move forward 1 screen and stop
    scrollapi.move(1);
    $('.homeScrollable').unbind('mousewheel', mouseHandler);
} else if(deltaX > 0) {
    //move backward 1 screen and stop
    scrollapi.move(-1);
    $('.homeScrollable').unbind('mousewheel', mouseHandler);
}   
event.preventDefault();

// Rebind mousewheel event:
$('.homeScrollable').bind('mousewheel', mouseHandler);     };

Я также смотрел на настройку таймера, а-ля:

плагин jquery mousewheel: как запускать только одну функцию за каждый свиток

, который казался невероятно многообещающим, но бездействующим. Вот страница плагина для этого парня: http://brandonaaron.net/code/mousewheel/docs

Спасибо за проверку.

Ответы [ 4 ]

19 голосов
/ 27 января 2012

Поскольку DOM не предлагает никакого способа различения между первым запущенным событием прокрутки и последующими, которые оказываются частью одного и того же прокручиваемого движения, мы вынуждены думать о косвенных методах различения между ними. *

Если вы быстро прокрутите любой конкретный элемент, событие прокрутки будет запускаться много раз подряд. Используя следующий код, мы можем получить представление о том, как часто это происходит:

$('#exampleDiv').bind('mousewheel', function () {
    console.log(new Date().getTime());
});

Когда вы прокрутите этот div, вы получите вывод консоли, который выглядит следующим образом:

// Using mouse wheelbar
251327626600149
251327626600215
251327626600265
251327626600282
251327626600332
251327626600365

// Using touchpad
251327626626207
251327626626225
251327626626261
251327626626276
251327626626312
251327626626345

Глядя на этот вывод, кажется, что события mousescroll обычно запускаются в пределах от 20 до 60 мс друг от друга. Чтобы быть в безопасности, мы возьмем верхний предел за 100 мс. Это очень информативно, потому что мы можем использовать его, чтобы различать прокручивающиеся события, которые являются частью одного и того же действия, и события, которые вероятно различны и преднамеренно инициированы пользователем.

То, что вы могли бы сделать здесь, - это создать глобально доступную переменную timestamp, обновляя ее каждый раз, когда происходит событие mousescroll, независимо от того, успешно оно или нет. Примерно так:

var timeStamp = new Date().getTime();

$('#exampleDiv').bind('mousewheel', function (event) {
    var timeNow = new Date().getTime();

    // Need to prevent the default scrolling behavior
    event.preventDefault();

    // If the last mousescroll happened less that 100 ms ago, update
    // timeStamp and do nothing
    if (timeNow - timeStamp < 100) {
        timeStamp = timeNow;
        return;
    } else {
        timeStamp = timeNow;
        scrollToSomeOtherDiv();
    }
});

Это фактически игнорирует все mousescroll события, которые запускаются после начального события, которое предшествовало им всем, но начинает работать снова после того, как пользователь сделал паузу в течение 100 мс.

Это решит вашу проблему, кроме случаев, когда ваша scrollToSomeOtherDiv() функция задействует какую-то трудоемкую анимацию. Конечно, вы можете сделать глобальное логическое значение isAnimating и проверить, истинно ли оно каждый раз, когда происходит событие mousescroll (убедитесь, что оно было установлено в обратный вызов после завершения анимации).

Это бы сработало, за исключением того, что оно могло бы дать пользователю возможность испытать резкий звук. Тот, кто хочет быстро прокрутить две панели, скорее всего, не будет делать паузу между прокрутками даже после начала анимации. Приведенный выше код будет видеть все их mousescroll события как часть одного и того же прокручиваемого движения и будет продолжать их игнорировать!

В этом случае вы можете просто использовать время анимации в качестве порога. Вы устанавливаете timeStamp после начала анимации, а затем игнорируете все события mousescroll в течение этого периода времени. Я написал пример здесь: http://jsfiddle.net/Sg8JQ/

Соответствующий код здесь:

var lastAnimation = 0;
var animationTime = 1000; // in ms
var quietPeriod = 500; // in ms, time after animation to ignore mousescroll

function scrollThis(event, delta, deltaX, deltaY) {
    var timeNow = new Date().getTime();

    // change this to deltaX/deltaY depending on which
    // scrolling dir you want to capture
    deltaOfInterest = deltaY;

    if (deltaOfInterest == 0) {
        // Uncomment if you want to use deltaX
        // event.preventDefault();
        return;
    }

    // Cancel scroll if currently animating or within quiet period
    if(timeNow - lastAnimation < quietPeriod + animationTime) {
        event.preventDefault();
        return;
    }

    if (deltaOfInterest < 0) {
        if ($('.active').next('div').length) {
            lastAnimation = timeNow;
            $('.active').removeClass('active').next('div').addClass('active');
            $('html,body').animate( {
                scrollTop: $('.active').offset().top }, animationTime);
        }
    } else {
        if ($('.active').prev('div').length) {
            lastAnimation = timeNow;
            $('.active').removeClass('active').prev('div').addClass('active');
            $('html,body').animate( {
                scrollTop: $('.active').offset().top }, animationTime);
        }
    }
}

// Note: mousewheel() is defined in the mousewheel plugin by Brandon Aaron
// You could do without it, but you'd need to adjust for firefox and webkit
// separately.
//
// You couldn't use $(document).scroll() because it doesn't allow you to
// preventDefault(), which I use here.
$(document).mousewheel(scrollThis);

Я также включил quietPeriod, который превышает время анимации, в течение которого вы хотите продолжать игнорировать mousescroll события. Вы можете установить его на 0, если хотите, чтобы прокрутка была «отзывчивой», как только анимация будет завершена.

1 голос
/ 29 апреля 2013

Я думаю, что решение, предоставленное "pikappa" - это чистая сексуальность.

Для меня это отлично работало на Firefox, но я испытывал "мерцание" при прокрутке в Chrome 26.0.x

Простой грязный «патч» к его удивительному коду добавляет « return false; » в строке 57 и 64 из его JSfiddle

0 голосов
/ 07 января 2012

Итак, это то, что я делаю. Вроде работает, но все равно глючит.

i=0;
$("#test").mousewheel(function(event, delta) {
    event.preventDefault();
    clearTimeout($.data(this, 'timer'));        

    // check if the mouse moved on right/up
    if( delta > 0 ) {               
        i++;                

        // hack to execute function just once
        if( i == 3 ) {                  
            $('#child').fadeIn().html("<h1>next</h1>").fadeOut();  // do something once when i=4, but happening multiple times
        }           

    } else { // check if mouse moved left/down

        i--;                

        if(i==-3) {     
            $('#child').fadeIn().html("<h1>previous</h1>").fadeOut();  // do something once when i=-4, but happening multiple times
        }               

    } // end if delta

    // only execute another mousewheel scroll after some delay
    $.data(this, 'timer', setTimeout(function() {
        i = 0;
    }, 100));

});

Итак, хотя увеличенное значение i распечатывается очень хорошо, если я раскомментирую строку печати, если i = 4, она не будет работать должным образом (возможно, вам придется сильно прокручивать, если вы используете мышь, я пытаюсь в трекпаде ). Хотя я понимаю, что по мере того, как вы получаете дельта-значение, я непрерывно увеличивается, но вы выполняете что-то, только когда я достигаю 4, и сбрасываете его значение только в том случае, если прокрутка остановлена ​​на 250 мс, этот код, по-видимому, сбрасывает i в 0 как минимум дважды или трижды, поэтому == 4 на самом деле выполняется дважды или трижды. Возможно, вы сможете найти, что может происходить. Я почти там, просто нужно исправить эту ошибку.

Попробуйте добавить что-то, что меняется, например, fadeIn, эффект fadeOut, в оператор if (i == 4) и сделать его более заметным.

EDIT:

Вот новая отредактированная версия . Ссылка на колесико мыши была старой, поэтому, возможно, теперь это лучше видно. Если вы двигаетесь быстро, вы видите, что оно мерцает ...

0 голосов
/ 04 января 2012

Взгляните на это и посмотрите, что вы думаете.Для начала я бы не привязывался к колесу мыши, потому что Firefox использует DOMMouseScroll.При этом также используется другой дельта-обработчик, обратный тому, который используется во всех других браузерах.Вместо этого свяжите с событием прокрутки jQuery, которое нормализует поведение.Вы можете отслеживать последнюю позицию прокрутки, чтобы определить, прокручивал ли пользователь вверх или вниз.

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

http://jsfiddle.net/Gj3Qy/

var lastScrollTop = 0;
var isDoingStuff = false;

$(document).scroll(function(event) {
    //abandon 
    if(isDoingStuff) { return; }

    if ($(this).scrollTop() > lastScrollTop) {
        //console.log('down');
        $('.active').removeClass('active').next('div').addClass('active');
        isDoingStuff = true;
        $('html,body').animate( {scrollTop: $('.active').offset().top }, 1000, function() {
            setTimeout(function() {isDoingStuff = false;}, 100);
            console.log('off');
        });
    } else {
        //console.log('up');
    }
    lastScrollTop = $(this).scrollTop();
})
...