Normalizing Mousewheel Speed Across Browsers

Normalizing mousewheel speed across browsers

Edit September 2014

Given that:

  • Different versions of the same browser on OS X have yielded different values in the past, and may do so in the future, and that
  • Using the trackpad on OS X yields very similar effects to using a mouse wheel, yet gives very different event values, and yet the device difference cannot be detected by JS

…I can only recommend using this simple, sign-based-counting code:

var handleScroll = function(evt){
if (!evt) evt = event;
var direction = (evt.detail<0 || evt.wheelDelta>0) ? 1 : -1;
// Use the value as you will
};
someEl.addEventListener('DOMMouseScroll',handleScroll,false); // for Firefox
someEl.addEventListener('mousewheel', handleScroll,false); // for everyone else

Original attempt to be correct follows.

Here is my first attempt at a script to normalize the values. It has two flaws on OS X: Firefox on OS X will produce values 1/3 what they should be, and Chrome on OS X will produce values 1/40 what they should be.

// Returns +1 for a single wheel roll 'up', -1 for a single roll 'down'
var wheelDistance = function(evt){
if (!evt) evt = event;
var w=evt.wheelDelta, d=evt.detail;
if (d){
if (w) return w/d/40*d>0?1:-1; // Opera
else return -d/3; // Firefox; TODO: do not /3 for OS X
} else return w/120; // IE/Safari/Chrome TODO: /3 for Chrome OS X
};

You can test out this code on your own browser here: http://phrogz.net/JS/wheeldelta.html

Suggestions for detecting and improving the behavior on Firefox and Chrome on OS X are welcome.

Edit: One suggestion from @Tom is to simply count each event call as a single move, using the sign of the distance to adjust it. This will not give great results under smooth/accelerated scrolling on OS X, nor handle perfectly cases when the mouse wheel is moved very fast (e.g. wheelDelta is 240), but these happen infrequently. This code is now the recommended technique shown at the top of this answer, for the reasons described there.

Javascript scrollbar class and mousewheel speed in different browsers

The mouse wheel event is very dodgy in javascript, main issue being usually Safari as they used to adjust the ratio on every point release, and even then the values reported by the event are not the same in all major browsers.

There has been some discussion about this on the MooTools tracker some time ago (link) and comparing different solutions I concluded that there's no standard way to normalize the event.

The last message on that issue shows a possible solution for normalizing (link), but it breaks wheel acceleration in Safari (and probably any other acceleration that other Browser/OS/Mouse Driver combination offers) so it is a tradeoff that you will have to evaluate if it fits to the requirements of your usage scenario.

Normalize wheelEvent in Firefox

For Firefox you need to use e.detail, something like this:

if (!!event.detail) {

// Similar to your wheelDeltaRate, detailRate will change the speed
var detailRate = 1.20;
// let's add some magic for scrolling
event.wheelX = event.axis == event.HORIZONTAL_AXIS ? (-1 * event.detail) * detailRate : 0;
event.wheelY = event.axis == event.VERTICAL_AXIS ? (-1 * event.detail) * detailRate : 0;
}

and don't forget to use:

target.addEventListener('DOMMouseScroll',normalizeWheelEvent, useCapture);

let me know if it works for you

Mousewheel event in modern browsers

Clean and simple:

window.addEventListener("wheel", event => console.info(event.deltaY));

Browsers may return different values for the delta (for instance, Chrome returns +120 (scroll up) or -120 (scroll down). A nice trick to normalize it is to extract its sign, effectively converting it to +1/-1:

window.addEventListener("wheel", event => {
const delta = Math.sign(event.deltaY);
console.info(delta);
});

Reference: MDN.

Smooth vertical scrolling on mouse wheel in vanilla javascript?

How about this:

function init(){ new SmoothScroll(document,120,12)}
function SmoothScroll(target, speed, smooth) { if (target === document) target = (document.scrollingElement || document.documentElement || document.body.parentNode || document.body) // cross browser support for document scrolling var moving = false var pos = target.scrollTop var frame = target === document.body && document.documentElement ? document.documentElement : target // safari is the new IE target.addEventListener('mousewheel', scrolled, { passive: false }) target.addEventListener('DOMMouseScroll', scrolled, { passive: false })
function scrolled(e) { e.preventDefault(); // disable default scrolling
var delta = normalizeWheelDelta(e)
pos += -delta * speed pos = Math.max(0, Math.min(pos, target.scrollHeight - frame.clientHeight)) // limit scrolling
if (!moving) update() }
function normalizeWheelDelta(e){ if(e.detail){ if(e.wheelDelta) return e.wheelDelta/e.detail/40 * (e.detail>0 ? 1 : -1) // Opera else return -e.detail/3 // Firefox }else return e.wheelDelta/120 // IE,Safari,Chrome }
function update() { moving = true var delta = (pos - target.scrollTop) / smooth target.scrollTop += delta if (Math.abs(delta) > 0.5) requestFrame(update) else moving = false }
var requestFrame = function() { // requestAnimationFrame cross browser return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(func) { window.setTimeout(func, 1000 / 50); } ); }()}
p{  font-size: 16pt;  margin-bottom: 30%;}
<body onload="init()">  <h1>Lorem Ipsum</h1>    <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>    <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>    <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p></body>

Detecting type of mouse scroll wheel (Smooth vs Notched) with javascript

See UPDATE at the end!


My original answer:

I don't have a mac nor a 'smooth' mouse, but I've tested your snippet both on Chrome and Firefox both on Windows and Linux boxes.

Works great on Chrome both on Windows and Linux but...

looks like the coefficient isn't the right one for Firefox... it works better (not as good as in Chrome) with 200.

One more thing:

Have you tested the mac fancy mouse on windows and vice-versa? Could it be a mac related problem?

UPDATE:

Other answers are great but I got puzzled by your question and learned a lot with the code and with what other answers pointed out, but something kept in my mind like a bug.

Searching for this topic I found this question very informative. It included a possible mouse scroll calibration script in this answer and a function getScrollLineHeight for Detecting the line-height used by DOM_DELTA_LINE triggered scroll events.

I've copied this function in the snippet for completeness, but at the end it's not needed for what I've thought. I've commented out the line that calls getScrollLineHeight because it does not work in this site for security reasons, but works in this fiddle.

My confusion was to think of scrolling as I normally do, in terms of pixels on a page. But your code really doesn't care about that. I mean, does not care about mouse scroll wheel event.deltaY magnitude. Only if it's positive or negative and consider that one step forward or backwards in a video timeline.

So this does not resolve the problem of "touch sensitive scroll mice", but it does resolve easily Firefox/Chrome and any Pixel/Line/Page deltaMode also. Now it runs smoothly both in Chrome and Firefox. I can't test on other browser because of WEBM video format, and I haven't been able to create a video in any format that works (look at my P.D. at the end).

So, every call is just one step: -1 or 1. Though it seems that only Firefox returns anything than "pixels" for deltaMode. I used this fiddle to test... Now you can focus on that smooth scrolling mouse and see how fast it sends every call, that is what really matters in this particular case (note that many macs have smooth scrolling software or inverted scrolling).

I've commented every line of your code and my modifications for my self but may be useful for others.

// detect if browser firefox as it appears to be the only//  browser that return deltaModes different than DOM_DELTA_PIXEL//  Ref: https://stackoverflow.com/a/37474225/4146962var FF = !(window.mozInnerScreenX == null);
// Function grabbed from the reference above// It tries to read current line-height of document (for 'lines' deltaMode)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;}
// html5 elementsvar vid = document.getElementById("v"); // HTML5 video elementvar canvas = document.getElementById("c"); // HTML5 canvas elementvar context = canvas.getContext('2d'); // Canvas contextvar momentum = document.getElementById('m'); // Current momentum displayvar delta = document.getElementById('d'); // Current deltaMode displayvar lineheight = document.getElementById('l'); // Current deltaMode display
// global variablesvar ch = 120; // canvas with (could be window.innerHeight)var cw = Math.round(ch * (16 / 9)); // 16/9 proportion widthvar targetOffset = 0; // Video offset target position when scrolling
// deltaY to FPS coefficients (for fine tuning)// Possible mouse scroll wheel 'event.deltaMode'// modes are: 0:'pixels', 1:'lines', 2:'page'var pc = 1000; // 'pixels' deltaY coefficientvar lh = "disabled"; //getScrollLineHeight(); // get line-height of deltaMode 'lines'lineheight.value = lh; // display current document line heightcoefficient = 30;var deltaModes = ['pixels', 'lines', 'page']; // For deltaMode display
// Sets canvas dimensionscanvas.width = cw;canvas.height = ch;
// Pauses video (this also starts to load the video)vid.pause();
// Listens video changes time positionvid.addEventListener('seeked', function() { // Updates canvas with current video frame context.drawImage(vid, 0, 0, cw, ch);});
// Listens mouse scroll wheelwindow.addEventListener('wheel', function(e) {
// Don't do what scroll wheel normally does e.preventDefault();
// You don't need an amount, just positive or negative value: -1, 1 var deltabs = 1; if (e.deltaY<0) deltabs = -1;
// Disable page scrolling, modes[e.deltaMode]=='page' if (e.deltaMode>1) return false;
delta.value = deltaModes[e.deltaMode]; // Normally scrolling this should be a subtraction // not a sum but "I like it like this!" // targetOffset = targetOffset + (e.deltaY / coefficient); // e.deltaY is the thing!! targetOffset = targetOffset + (deltabs/coefficient);
// Shows current momentum momentum.value = targetOffset;
return false;});
// Updates canvas on a loop (both for play or pause state)var renderLoop = function() { requestAnimationFrame(function() {
// This parts updates canvas when video is paused // Needs 'seeked' listener above if (vid.paused || vid.ended) {
// Reduce target offset gradually targetOffset = targetOffset * 0.9; // Show current momentum momentum.value = Math.round(targetOffset * 100) / 100;
// this part joins start and end of video when scrolling // forward & backwards var vct = vid.currentTime - targetOffset; if (vct < 0) { vct = vid.duration + vct; } else if (vct > vid.duration) { vct = vct - vid.duration; } vid.currentTime = vct;
// This parts updates canvas when video is playing } else { // update canvas with current video frame context.drawImage(vid, 0, 0, cw, ch); }
renderLoop(); // Recursive call to loop });};renderLoop(); // Initial call to loop
input {  width: 50px;}
.column { float: left; width: 50%;}
/* Clear floats after the columns */.row:after { content: ""; display: table; clear: both;}
<h3>  mouse scroll video</h3><div class="row">  <div class="column">    <div>      Video element:    </div>    <video controls height="120" id="v" tabindex="-1" autobuffer="auto" preload="auto">      <source type="video/webm" src="https://www.html5rocks.com/tutorials/video/basics/Chrome_ImF.webm"/>    </video>  </div>  <div class="column">    <div>      Canvas element:    </div>    <canvas id="c"></canvas>    <div>      Momentum: <input type=text id="m">    </div>    <div>      deltaMode: <input type=text id="d">    </div>    <div>      lineHeight: <input type=text id="l">    </div>  </div></div>


Related Topics



Leave a reply



Submit