32

I was wondering why some Javascript of mine would not work until I figured that the audio events did not bubble up the DOM tree, e.g. the timeupdate-event.

Is there a rationale for not letting the events of the audio- and video-tag bubble?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
k0pernikus
  • 60,309
  • 67
  • 216
  • 347

1 Answers1

48

The reason why event bubbling exists is solve the ambiguous question of which element is the intended target of the event. So, if you click on a div, did you mean to click the div, or its parent? If the child doesn't have a click handler attached, then it checks the parent, and so on. I'm sure you know how that works.

The reason why audio events don't bubble is because they don't make sense on any other element. There's no ambiguity when you trigger a timeupdate on an audio element whether it's meant for the audio element itself or its parent div, so there's no need to bubble it.

You can read a fuller history of event bubbling here

Event delegation

Event delegation is still possible by utilizing the capturing phase of the event. Simply add true as the third argument for addEventListener which looks like this:

document.addEventListener('play', function(e){
    //e.target: audio/video element
}, true);

Note, that this event doesn't bubble, but goes down the DOM-tree and can't be stopped with stopPropagation.

In case you want to use this with the jQuery's .on/.off methods (for example to have namespacing and other jQuery event extensions). The following function, taken form the webshim library, should become usefull:

$.createEventCapturing = (function () {
    var special = $.event.special;
    return function (names) {
        if (!document.addEventListener) {
            return;
        }
        if (typeof names == 'string') {
            names = [names];
        }
        $.each(names, function (i, name) {
            var handler = function (e) {
                e = $.event.fix(e);

                return $.event.dispatch.call(this, e);
            };
            special[name] = special[name] || {};
            if (special[name].setup || special[name].teardown) {
                return;
            }
            $.extend(special[name], {
                setup: function () {
                    this.addEventListener(name, handler, true);
                },
                teardown: function () {
                    this.removeEventListener(name, handler, true);
                }
            });
        });
    };
})();

Usage:

$.createEventCapturing(['play', 'pause']);

$(document).on('play', function(e){
    $('audio, video').not(e.target).each(function(){
        this.pause();
    });
});
alexander farkas
  • 13,754
  • 4
  • 40
  • 41
saml
  • 6,702
  • 1
  • 34
  • 30
  • 25
    While it doesn't make much sense to bubble up media events to other type of elements, it does make sense to register a global event listener on the `body`, for example, which would catch the events from any media element within the document. – Sergiu Dumitriu Sep 27 '12 at 21:46
  • That's a pity, but understandable. It does make "tricks" like jQuery's delegation system unusable, unfortunately. I.e. you would attach one `play` listener to e.g. the body, and the function would get executed whenever any ` – pimvdb Sep 28 '12 at 08:33
  • It wasn't my question :) I'm usually not too quick with giving the bounty, but don't worry. – pimvdb Sep 29 '12 at 15:28
  • Thank you for this neat solution, just what I was looking long and hard for! For anyone using **rxjs' fromEvent** like me, you can add a third parameter to capture events in children elements like so: **fromEvent(document, 'timeupdate', {capture: true})** – marjon4 Aug 26 '21 at 10:11