Как фиксировать все прокручивающие события на странице без привязки обработчика onscroll к каждому контейнеру

Рассмотрим следующую веб-страницу:

 <html>
   <body onscroll="alert('body scroll event')">
     <div style='width:200px;height:200px;overflow:auto' onscroll="alert('div scroll event')">
       <div style='height:400px'>
       </div>
     </div>
   </body>
 </html>

Этот html создает div с полосой прокрутки. Если вы перемещаете полосу прокрутки, запускается событие "onscroll" в элементе div. Тем не менее, событие onscroll на теле НЕ запускается. Это ожидается, поскольку W3C утверждает, что события onscroll элемента не "пузырятся".

Тем не менее, я разрабатываю клиентскую веб-инфраструктуру, которая должна знать, когда будет прокручиваться полоса прокрутки на ЛЮБОМ элементе страницы. Это было бы легко сделать, если "onscroll" пузырился, но, к сожалению, этого не происходит. Есть ли другой способ обнаружения событий onscroll на всей странице? (Сейчас я сосредоточен в основном на Webkit, поэтому решение для Webkit будет в порядке...)

Вот некоторые вещи, которые я пробовал:  1. Захват DOMAttrModified (кажется, не срабатывает для перемещения полос прокрутки).  2. Использование наблюдателей DOM (также, похоже, не срабатывает для полос прокрутки)  3. Изменение типа события onscroll на пузырь (кажется, не возможно)

Кажется, ТОЛЬКО способ захватить события onscroll во всем мире - это привязать событие onscroll к КАЖДОМУ элементу, который может прокручиваться, что очень уродливо и может повредить производительность моей инфраструктуры.

Кто-нибудь знает лучший способ?

Спасибо заранее!

Ответ 1

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

window.addEventListener('scroll', function(){ code goes here }, true)

К сожалению, поскольку я знаю, что в более старшем браузере нет эквивалента, такого как <= IE8

Ответ 2

*... сверчки чирикают... *

ОК, я думаю, этот вопрос не собирается получать никакой любви от stackoverflow, поэтому я мог бы также ответить на мой собственный вопрос относительно наилучшего решения, которое я нашел до сих пор, если другой пользователь наткнулся на этот вопрос:

Лучшим решением, которое я придумал, является захват "onmousedown" и "onkeydown" для элемента BODY: эти пузыри событий, и поэтому, если пользователь пытается переместить полосу прокрутки на странице, эти глобальные функции будут срабатывать как побочный продукт. Затем в этих функциях просто найдите event.target и присоедините временное событие onscroll к этим объектам до тех пор, пока мышь/клавиша снова не вернется. Используя этот метод, вы можете избежать "раздувания обработчика" и по-прежнему глобально захватывать все события "onscroll". (Я думаю, что это будет работать и для прокрутки "колеса мыши", но мои исследования по этой окончательной морщине все еще ждут.)

Ответ 3

У меня была эта же проблема.

Самый простой способ - использовать jQuery.

$("*").scroll(function(e) {
    // Handle scroll event
});

В ванильном JavaScript вы можете установить useCapture boolean на true на ваш вызов addEventListener, и он будет запускаться во всех элементах, включая динамически добавленные.

document.addEventListener('scroll', function(e) {
    // Handle scroll event
}, true);

Обратите внимание, что это произойдет, прежде чем произойдет событие прокрутки. Насколько я понимаю, происходят две фазы событий. Фаза захвата происходит сначала и начинается с корня страницы (ownerDocument?) И проходит к элементу, где произошло событие. После этого начинается фаза барботирования, которая проходит от элемента до корня.

Некоторые быстрые тесты также показали, что это может быть так, как это делает jQuery (для отслеживания прокрутки по всем элементам страницы), но я не уверен на 100%.

Здесь JSFiddle показывает метод ванильного JavaScript http://jsfiddle.net/0qpq8pcf/

Ответ 4

Следующее работает отлично, если вы хотите закрыть диалоговое окно после прокрутки прокрутки:

var scrollListener = function(e) {
    // TODO: hide dialog
    document.removeEventListener('scroll', scrollListener, true);
};

document.addEventListener('scroll', scrollListener, true);