Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mouse scroll delta is not scaled properly #6283

Open
ghost opened this issue Feb 26, 2018 · 26 comments
Open

Mouse scroll delta is not scaled properly #6283

ghost opened this issue Feb 26, 2018 · 26 comments

Comments

@ghost
Copy link

ghost commented Feb 26, 2018

I've done some digging and I think this is true for all scrolling in all libraries.

Using GLFW to listen to scroll events (glfwSetScrollCallback) gives me two doubles: Y and X delta. These are majorly differing between Firefox and Chrome (because as I have found, you should scale the delta according to if you get PIXELS or LINES).

In library_glfw.js, glfwInit function you listen to 'wheel' event (and others), handler is GLFW.onMouseWheel. There you call Browser.getMouseWheelDelta which for the 'wheel' event simply returns deltaY. Then this delta is just passed up to GLFW handlers.

What needs to be done is to properly handle the WheelEvent.deltaMode which can be DOM_DELTA_PIXEL or DOM_DELTA_LINE or even DOM_DELTA_PAGE.

When Firefox gives delta in LINES and Chrome gives delta in PIXELS there's a major inconsistency for apps run in those two browsers. Browser.getMouseWheelDelta is used by all libraries it seems to this issue should affect all scrolling.

@ghost
Copy link
Author

ghost commented Feb 26, 2018

I made this HTML to display and test the behavior:

<html>

<head>
<script>

// I found this somewhere
function getScrollLineHeight() {
    var r;
    var iframe = document.createElement('iframe');
    iframe.src = '#';
    document.body.appendChild(iframe);
    var iwin = iframe.contentWindow;
    var idoc = iwin.document;
    idoc.open();
    idoc.write('<!DOCTYPE html><html><head></head><body><span>a</span></body></html>');
    idoc.close();
    var span = idoc.body.firstElementChild;
    r = span.offsetHeight;
    document.body.removeChild(iframe);
    return r;
}

function handler(e) {
// pixels or lines?
if (e.deltaMode == 0) {
console.log(e.deltaY);
} else if (e.deltaMode == 1) {
console.log(e.deltaY * getScrollLineHeight());
}

}

function main() {
var canvas = document.getElementById('canvas');

canvas.addEventListener("wheel", handler, false);
}
</script>
</head>

<body onload="main();">

<canvas id="canvas" style="width: 600px; height 600px; background-color: red;"></canvas>

</body>

</html>

@centurn
Copy link

centurn commented Dec 11, 2018

I've encountered the same problem while using SDL API: Y delta is different about ~30 times between Firefox and Chrome. The Firefox values are similar to what I get when running native SDL on Linux.

I've also came across this 4-year old issue. Looks like it was closed without normalizing the values between browsers.

@feliwir
Copy link

feliwir commented Dec 19, 2018

Having this issue aswell

@flostellbrink
Copy link
Contributor

Looks like the specific issue is in line 568 of library_browser.js:

getMouseWheelDelta: function(event) {
var delta = 0;
  switch (event.type) {
    case 'DOMMouseScroll':
      delta = event.detail;
      break;
    case 'mousewheel':
      delta = event.wheelDelta;
      break;
    case 'wheel':
      delta = event['deltaY'];
      break;
    default:
      throw 'unrecognized mouse wheel event: ' + event.type;
  }
  return delta;
},

I just found this solution from facebook. They essentially convert all values into pixels.

I would suggest that we also return those estimated pixels by default. Specific libraries like library_glfw.js could then attempt to recreate the behaviour of their originals.

@malytomas
Copy link

Does the fix apply to SDL2 too? I have this same issue now with 1.39.3.

@flostellbrink
Copy link
Contributor

@malytomas Sorry to disappoint. This was really just a legacy patch. SDL1 and GLFW2 should work, but neither GLFW3 nor SDL2 are affected by this. This also only supports vertical scrolling.

I think the html5 api would be the place to fix issues with SDL2.

_registerWheelEventCallback__deps: ['$JSEvents', '_fillMouseEventData', '_findEventTarget'],
_registerWheelEventCallback: function(target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) {
#if USE_PTHREADS
targetThread = JSEvents.getTargetThreadForEventCallback(targetThread);
#endif
if (!JSEvents.wheelEvent) JSEvents.wheelEvent = _malloc( {{{ C_STRUCTS.EmscriptenWheelEvent.__size__ }}} );
// The DOM Level 3 events spec event 'wheel'
var wheelHandlerFunc = function(ev) {
var e = ev || event;
#if USE_PTHREADS
var wheelEvent = targetThread ? _malloc( {{{ C_STRUCTS.EmscriptenWheelEvent.__size__ }}} ) : JSEvents.wheelEvent; // This allocated block is passed as satellite data to the proxied function call, so the call frees up the data block when done.
#else
var wheelEvent = JSEvents.wheelEvent;
#endif
__fillMouseEventData(wheelEvent, e, target);
{{{ makeSetValue('wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["deltaX"]', 'double') }}};
{{{ makeSetValue('wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, 'e["deltaY"]', 'double') }}};
{{{ makeSetValue('wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, 'e["deltaZ"]', 'double') }}};
{{{ makeSetValue('wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, 'e["deltaMode"]', 'i32') }}};
#if USE_PTHREADS
if (targetThread) JSEvents.queueEventHandlerOnThread_iiii(targetThread, callbackfunc, eventTypeId, wheelEvent, userData);
else
#endif
if ({{{ makeDynCall('iiii') }}}(callbackfunc, eventTypeId, wheelEvent, userData)) e.preventDefault();
};
// The 'mousewheel' event as implemented in Safari 6.0.5
var mouseWheelHandlerFunc = function(ev) {
var e = ev || event;
__fillMouseEventData(JSEvents.wheelEvent, e, target);
{{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["wheelDeltaX"] || 0', 'double') }}};
{{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, '-(e["wheelDeltaY"] || e["wheelDelta"]) /* 1. Invert to unify direction with the DOM Level 3 wheel event. 2. MSIE does not provide wheelDeltaY, so wheelDelta is used as a fallback. */', 'double') }}};
{{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, '0 /* Not available */', 'double') }}};
{{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, '0 /* DOM_DELTA_PIXEL */', 'i32') }}};
var shouldCancel = {{{ makeDynCall('iiii') }}}(callbackfunc, eventTypeId, JSEvents.wheelEvent, userData);
if (shouldCancel) {
e.preventDefault();
}
};
var eventHandler = {
target: target,
allowsDeferredCalls: true,
eventTypeString: eventTypeString,
callbackfunc: callbackfunc,
handlerFunc: (eventTypeString == 'wheel') ? wheelHandlerFunc : mouseWheelHandlerFunc,
useCapture: useCapture
};
JSEvents.registerOrRemoveHandler(eventHandler);
},
emscripten_set_wheel_callback_on_thread__proxy: 'sync',
emscripten_set_wheel_callback_on_thread__sig: 'iiiiii',
emscripten_set_wheel_callback_on_thread__deps: ['$JSEvents', '_registerWheelEventCallback', '_findEventTarget'],
emscripten_set_wheel_callback_on_thread: function(target, userData, useCapture, callbackfunc, targetThread) {
target = __findEventTarget(target);
if (typeof target.onwheel !== 'undefined') {
__registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WHEEL') }}}, "wheel", targetThread);
return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}};
} else if (typeof target.onmousewheel !== 'undefined') {
__registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WHEEL') }}}, "mousewheel", targetThread);
return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}};
} else {
return {{{ cDefine('EMSCRIPTEN_RESULT_NOT_SUPPORTED') }}};
}
},

@feliwir
Copy link

feliwir commented May 15, 2020

Are there any updates on a SDL2 fix? This is causing major issues for us

@kripken
Copy link
Member

kripken commented May 15, 2020

I'm not aware of someone working on it. cc @Daft-Freak who might remember more though.

If someone is interested to investigate and work on a patch for this, let me know if there is anything I can do to speed that along.

@Daft-Freak
Copy link
Collaborator

I think I remember this being mentioned at some point, but doesn't look like anything was implemented...

Looks like we need to check wheelEvent->deltaMode here: https://github.com/emscripten-ports/SDL2/blob/master/src/video/emscripten/SDL_emscriptenevents.c#L425. I don't think SDL2 defines what the units should be though.

@feliwir
Copy link

feliwir commented Jun 30, 2020

@Daft-Freak is there a library / implementation that handles that correctly?

@ziocleto
Copy link

Having the same issue, a super hacky fix, for now, it's to normalize everything [-1.0, 1.0]. Which it's pants. But at least it "works" for us somehow.

I do not understand were the root of the problem is, in the GLFW and SDL callbacks? Or in the general HTML5 wheel handling code? I won't mind having a go if you can confirm where exactly the issue arises.

@feliwir
Copy link

feliwir commented Sep 7, 2020

@ziocleto i'm not sure either why this can't be fixed. This is literally breaking our entire application on some browsers

@ziocleto
Copy link

ziocleto commented Sep 7, 2020

@feliwir Me neither, IIRC I had a go to try to understand the issue but I really gave up as it seems nobody really knows all the details of this mess. And yes, even for us, it broke everything, well it still does, so annoying... If you have any updates, let us know!

@feliwir
Copy link

feliwir commented Sep 7, 2020

@ziocleto i investigated a bit further and found out that the deltaMode is different for Chrome & Firefox (thanks @Daft-Freak ).
However i'm not certain what's the correct formula to fix this properly. This worked for me:

static EM_BOOL
Emscripten_HandleWheel(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData)
{
    SDL_WindowData *window_data = userData;
    // Default mode is in pixels
    float deltaX = wheelEvent->deltaX;
    float deltaY = wheelEvent->deltaY;
    // If delta mode is in lines
    if(wheelEvent->deltaMode == DOM_DELTA_LINE)
    {
        deltaX *= 20.0f;
        deltaY *= 20.0f;
    }
    else if(wheelEvent->deltaMode == DOM_DELTA_PAGE)
    {
        //TODO:
    }

    SDL_SendMouseWheel(window_data->window, wheelEvent->deltaMode, deltaX, -deltaY, SDL_MOUSEWHEEL_NORMAL);
    return SDL_GetEventState(SDL_MOUSEWHEEL) == SDL_ENABLE;
}

However this will probably break for Retinna etc. Can't we just pass the deltaMode to the callback aswell?

@ghost
Copy link
Author

ghost commented Sep 7, 2020

@ziocleto i investigated a bit further and found out that the deltaMode is different for Chrome & Firefox (thanks @Daft-Freak ).

I said exactly this in my very first report 2 years ago in the top of this very issue report if you care to read it...

@ghost ghost closed this as completed Sep 7, 2020
@ghost ghost reopened this Sep 7, 2020
@feliwir
Copy link

feliwir commented Sep 7, 2020

Indeed, thanks to @alexhultman aswell, but can we fix this now?

@ghost
Copy link
Author

ghost commented Sep 7, 2020

You need to know the height of a "row" and scale it accordingly. That's in the initial report. [On] Firefox [emscripten] treats the input given as "rows" as if they were pixels. That's why the scrolling is wildly incorrect. I guess you need to read the font metrics or the font-size or something like that.

@feliwir
Copy link

feliwir commented Sep 7, 2020

@alexhultman well, we display text ourselves (with WebGL), so it would be enough for us to get a notification by the SDL port that this is LINES or PIXELS, so we can handle the difference on our side.

Maybe we could add new wheel types?

@Daft-Freak
Copy link
Collaborator

I did look at this for SDL2, but the values seem to vary between platforms. (And possibly system settings?)

To get scroll events matching native on Windows, I needed /3 for LINE(Firefox) and /200 for PIXEL(Chrome). But on my Linux machine Chrome was 106...

@ziocleto
Copy link

ziocleto commented Sep 9, 2020

BTW As I see that you guys are talking about SDL, I do not think the problem is related to SDL per se, we use GLFW for example, so fixing it should be agnostic from the graphics framework.

@Daft-Freak
Copy link
Collaborator

Hmm, SDL2 should probably be an issue in the SDL2 repo. (The fix would be in SDL as it's using the low-level HTML5 API).

You could even argue that it doesn't need fixed as the docs for SDL_WheelEvent don't specify a unit and native platforms seem to only ever return +/-1...

@stale
Copy link

stale bot commented Sep 21, 2021

This issue has been automatically marked as stale because there has been no activity in the past year. It will be closed automatically if no further activity occurs in the next 30 days. Feel free to re-open at any time if this issue is still relevant.

@stale stale bot added the wontfix label Sep 21, 2021
@thomasballinger
Copy link
Contributor

I've posted PR to the SDL2 port which fixes for me: emscripten-ports/SDL2#154

@stale stale bot removed the wontfix label Sep 29, 2021
@kripken
Copy link
Member

kripken commented Sep 29, 2021

Thanks @thomasballinger !

If people can test that PR and verify it works on their codebases, and on multiple browsers, that would help move this forward.

@thomasballinger
Copy link
Contributor

thomasballinger commented Sep 29, 2021

One way to test is to make these changes to emsdk/upstream/emscripten/tools/ports/sdl2.py:

rm -rf emsdk/upstream/emscripten/cache/ports/sdl2.zip emsdk/upstream/emscripten/cache/ports/sdl2

(I've updated the hash 3x now, you can also set the hash to '')

8,9c8,9
< TAG = 'version_24'
< HASH = '5a8181acdcce29cdda7e7a4cc876602740f5b9deebd366ecec71ae15c4bbf1f352da4dd0e3c5e0ba8160709dda0270566d64a6cd3892da894463ecf8502836aa'
---
> TAG = 'scroll-speed'
> HASH = 'fc3a4aaffb57ec63ef2e2a6a0a562f99ce7b7e03e7b68762cc6105883f9766d214f50043cf0de2edd8ff1a0556a5d684719001b136d8a58101f43a9617ddcc9b'

23c23
<   ports.fetch_project('sdl2', 'https://github.com/emscripten-ports/SDL2/archive/' + TAG + '.zip', SUBDIR, sha512hash=HASH)
---
>   ports.fetch_project('sdl2', 'https://github.com/thomasballinger/SDL2/archive/refs/heads/scroll-speed.zip', SUBDIR, sha512hash=HASH)

I've tried this with https://play-endless-web.com/ (deployed version does not have the fix) in Firefox and Chrome and it feels much better to me.

@trusktr
Copy link

trusktr commented Mar 22, 2024

These are majorly differing between Firefox and Chrome (because as I have found, you should scale the delta according to if you get PIXELS or LINES).

Even in situations where we get PIXELS, the delta values still vary wildly across browser/OS combinations (even across the same browser in different OSes).

Related:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants