Vimeo iFrame Stealing Mouse Wheel Event on Firefox
Asked Answered
M

3

8

I made this example here: http://jsbin.com/pokahec/edit?html,output

// creates a global "addWheelListener" method
// example: addWheelListener( elem, function( e ) { console.log( e.deltaY ); e.preventDefault(); } );
(function(window,document) {

var prefix = "", _addEventListener, onwheel, support;

// detect event model
if ( window.addEventListener ) {
    _addEventListener = "addEventListener";
} else {
    _addEventListener = "attachEvent";
    prefix = "on";
}

// detect available wheel event
support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel"
          document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel"
          "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox

window.addWheelListener = function( elem, callback, useCapture ) {
    _addWheelListener( elem, support, callback, useCapture );

    // handle MozMousePixelScroll in older Firefox
    if( support == "DOMMouseScroll" ) {
        _addWheelListener( elem, "MozMousePixelScroll", callback, useCapture );
    }
};

function _addWheelListener( elem, eventName, callback, useCapture ) {
    elem[ _addEventListener ]( prefix + eventName, support == "wheel" ? callback : function( originalEvent ) {
        !originalEvent && ( originalEvent = window.event );

        // create a normalized event object
        var event = {
            // keep a ref to the original event object
            originalEvent: originalEvent,
            target: originalEvent.target || originalEvent.srcElement,
            type: "wheel",
            deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1,
            deltaX: 0,
            deltaZ: 0,
            preventDefault: function() {
                originalEvent.preventDefault ?
                    originalEvent.preventDefault() :
                    originalEvent.returnValue = false;
            }
        };

        // calculate deltaY (and deltaX) according to the event
        if ( support == "mousewheel" ) {
            event.deltaY = - 1/40 * originalEvent.wheelDelta;
            // Webkit also support wheelDeltaX
            originalEvent.wheelDeltaX && ( event.deltaX = - 1/40 * originalEvent.wheelDeltaX );
        } else {
            event.deltaY = originalEvent.detail;
        }

        // it's time to fire the callback
        return callback( event );

    }, useCapture || false );
}

})(window,document);

You can test in Firefox that scroll event is fired, except when over vimeo iframe ( and I guess any iFrame )

Is there any solution to fire event on iframe ?

PS - I want to use this in a custom scrollbar

Maggoty answered 14/7, 2015 at 23:9 Comment(0)
E
7

This is basically by design. Your code should be completely unaware of what the user does inside an IFRAME (especially one from a different origin like YouTube - this is a part of the web's security architecture, as mandated by the Same Origin Policy.)

Now, even in the cross-origin case browsers can choose to let scrolling affect the frame's ancestor if the frame itself doesn't scroll. This scrolling should happen without any events firing on the top document - see Chrome's behaviour if you scroll to the bottom of this IFRAME and keep scrolling: http://jsfiddle.net/8cj0dofx/1/ HTML:

<iframe src="data:text/html,<body style='background:grey;height:550px'>Hello" seamless></iframe>
<div style="height:100px">Hello</div>

JS:

document.addEventListener('DOMMouseScroll', function(e){
    document.getElementsByTagName('div')[0].firstChild.data += ' ' + e.type
});

document.addEventListener('mousewheel', function(e){
    document.getElementsByTagName('div')[0].firstChild.data += ' ' + e.type
});

What you'll see is that when you have scrolled to the end of the IFRAME, the main document will scroll but no events will fire until the mouse is above the parent document.

Expurgate answered 20/7, 2015 at 12:23 Comment(9)
so no hack possible ?Maggoty
Well, I suppose you could put a transparent DIV on top of the IFRAME - but your visitor will probably loose the ability to actually play/pause the video.Expurgate
Also, if you add a "scrolling=no" attribute on the IFRAME the browser will not scroll the IFRAME contents separately. Not sure if that helps your use case - the outer page will be scrolling when the IFRAME is hovered, but you won't get any events until the IFRAME is out of the way.Expurgate
yeah, pointer-events:none works just as well on the iframe but basically we need play function too :DMaggoty
In the meantime I've changed my mind on whether it is a bug or a feature.. so I've reopened bugzilla.mozilla.org/show_bug.cgi?id=1084121Expurgate
@Expurgate thanks for the solution! However, when I try your code I got undefined on data, because it seems there is no data attribute in HTMLElement. There is only .dataset, but it's a function... How do I modify your code? Does something changed in HTML specifications over the years?Saturate
Hi @VladislavSorokin, that code was more a demo of how things work than an attempt at solving the problem. If you have a DIV element with text inside, the firstChild will refer to the text and it will set the data property. However, that's for debugging and understanding only - :)Expurgate
So this is broken by design, to improve security. But the security improvement doesn't exist, because we can get the scroll information anyway by listening to the documents scroll events.Fertility
I have not re-tested this @FlorianKirmaier , perhaps behaviour has changed :) Security-wise it's hardly a big impact, though it is a nice and clean principle that you should not be able to detect what the user does in a different origin.Expurgate
A
4

It looks like it's a bug in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1084121

So there may not be a straightforward way to handle this. But since the action has an effect even if it's not being dispatched, there's a workaround that can be used. It may not work in every situation, but it should cover many cases.

Instead of detecting wheel event, you detect scroll, and use a switch detecting if the mouse is clicked or not. If the window scrolls and the mouse isn't clicked, then it's most likely from the mousewheel. Other cases will be if you trigger it from a script, in which case this can be handled easily also.

One case you won't handle is when the window cannot scroll anymore, then you won't get the event.

It would look like this:

var mouseDown = false;

function handle_wheel() {
    if (!mouseDown) {
        document.getElementById("debug-textarea").value = document.getElementById("debug-textarea").value + ' wheel';
    } else {
        document.getElementById("debug-textarea").value = document.getElementById("debug-textarea").value + ' scroll';
    }
}

window.onscroll = handle_wheel;
window.onmousedown = function () {
    mouseDown = true;
}
window.onmouseup = function () {
    mouseDown = false;
}

http://jsfiddle.net/wu9y6yua/4/

Antibes answered 20/7, 2015 at 1:18 Comment(2)
nice, but it does not help me - first, I can't detect wheel deltaMaggoty
second, the purpose is a custom scrollbar, the body overflow is hidden so it does not actually scroll ( thus no scroll event ) - check new exampleMaggoty
R
0

I was facing the same problem but with a zoom on scroll feature.

Since it's not possible to capture the mousewheel event without hacks, I think the best option is to place a transparent div over the iframe and add the &autoplay=1 parameter in the vimeo/youtube url on click.

Radiocarbon answered 29/10, 2020 at 8:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.