Is there a way to disable or detect that w开发者_开发知识库heel events are from the "inertia" setting on a Mac?
I'd like to be able to tell the difference between real events and the others...or disable that kind of scrolling for a particular page.
I found a solution that works really well for this. Below is some pasted code from my project. It basically comes down to this logic:
A scroll event is from a human when ANY ONE of these conditions are true:
- The direction is the other way around than the last one
- More than 50 milliseconds passed since the last scroll event (picked 100ms to be sure)
- The delta value is at least as high as the previous one
Since Mac spams scroll events with descreasing delta's to the browser every 20ms when inertial scrolling is enabled, this is a pretty failsafe way. I've never had it fail on me at least. Just checking the time since the last scroll won't work because a user won't be able to scroll again if the "virtual freewheel" is still running even though they haven't scrolled for 3 seconds.
this.minScrollWheelInterval = 100; // minimum milliseconds between scrolls
this.animSpeed = 300;
this.lastScrollWheelTimestamp = 0;
this.lastScrollWheelDelta = 0;
this.animating = false;
document.addEventListener('wheel',
(e) => {
const now = Date.now();
const rapidSuccession = now - this.lastScrollWheelTimestamp < this.minScrollWheelInterval;
const otherDirection = (this.lastScrollWheelDelta > 0) !== (e.deltaY > 0);
const speedDecrease = Math.abs(e.deltaY) < Math.abs(this.lastScrollWheelDelta);
const isHuman = otherDirection || !rapidSuccession || !speedDecrease;
if (isHuman && !this.animating) {
this.animating = true; // current animation starting: future animations blocked
$('.something').stop().animate( // perform some animation like moving to the next/previous page
{property: value},
this.animSpeed,
() => {this.animating = false} // animation finished: ready for next animation
)
}
this.lastScrollWheelTimestamp = now;
this.lastScrollWheelDelta = e.deltaY;
},
{passive: true}
);
There's one caveat by the way: Mac also has acceleration on the scrolling, i.e.: at first, the delta value is higher for each successive event. It seems like this does not last more than 100ms or so though. So if whatever action/animation you are firing as a result of the scroll event lasts at least 100ms and blocks all other actions/animations in the meantime, this is never a problem.
Yes and no.
You can use touchdown/up, and scroll as events to look for the page moving about but those won't trigger if the OS is doing an inertial scroll. Fun, right?
One thing that you can continually detect, however, is window.pageYOffset. That value will keep changing while an inertial scroll is happening but won't throw an event. So you can come up with a set of timers to keep checking for an inertial scroll and keep running itself until the page has stopped moving.
Tricky stuff, but it should work.
Oh how is this issue killing me :/
I'm in the process of creating "endless" scrolling large file viewer.
To make situation worse, this editor is embedded in page that has its own scroll bar, because its bigger than one screen.
U use overflow-x scroll for horizontal scroll, but for vertical scroll i need current line highlighter (as seen in most modern IDEs) so i'm using jquery mousewheel plugin, and scrolling moving content for line height up or down.
It works perfectly on ubuntu/chrome but on MacOS Lion/Chrome sometimes, and just sometimes, when you scroll, it doesn't prevent default scroll on the editor element, and event propagates "up" and page it self starts to scroll.
I cant even describe how much annoying that is.
As for inertial scroll it self, i successfully reduced it with two timers
var self = this;
// mouse wheel events
$('#editorWrapper').mousewheel(function (event, delta, deltax, deltay) {
self._thisScroll = new Date().getTime();
//
//this is entirely because of Inertial scrolling feature on mac os :(
//
if((self._thisScroll - self._lastScroll) > 5){
//
//
// NOW i do actual moving of content
//
//
self._lastScroll = new Date().getTime();
}
5ms is value i found to have most natural feel on my MacBook Pro, and you have to scroll mouse wheel really fast to catch one of those..
Even still, sometimes on Mac listener on mac wrapper doesn't prevent default, and page scrolls down.
Well, (I might be wrong), I think that the "inertia" settings on the Mac are all computed by the system itself, the browser, or any program for that matter would just think that the user is scrolling quickly, rather than slowing down.
I'm not sure about other browsers, but the following event fires during inertial scroll on my Chrome, FF, Safari (mac):
var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel";
function scrollEE (e) {
console.log(e);
}
window.addEventListener(mousewheelevt, scrollEE, false);
I had a big problem with an object animating based on scroll position after the scroll had completed, and the inertial scroll was really messing me around. I ended up calculating the velocity to determine how long the inertial scroll would last and used that to wait before animating.
var currentY = 0;
var previousY = 0;
var lastKnownVelocity = 0;
var velocityRating = 1;
function calculateVelocity() {
previousY = currentY;
currentY = $('#content').scrollTop();
lastKnownVelocity = previousY - currentY;
if (lastKnownVelocity > 20 || lastKnownVelocity < -20) {
velocityRating = 5;
} else {
velocityRating = 1;
}
}
$('#content').scroll(function () {
// get velocity while scrolling...
calculateVelocity();
// wait until finished scrolling...
clearTimeout($.data(this, 'scrollTimer'));
$.data(this, 'scrollTimer', setTimeout(function() {
// do your animation
$('#content').animate({scrollTop: snapPoint}, 300);
}, 300*velocityRating)); // short or long duration...
});
There's a library that solves this problem.
https://github.com/d4nyll/lethargy
After installing it, use it like this:
var lethargy = new Lethargy();
$(window).bind('mousewheel DOMMouseScroll wheel MozMousePixelScroll', function(e){
if(lethargy.check(e) === false) {
console.log('stopping zoom event from inertia')
e.preventDefault()
e.stopPropagation();
}
console.log('Continue with zoom event from intent')
});
Following your instructions with my type of code, I had to set timeout of 270ms on each action activated by scroll to get it all smooth, so if anyone is using something similar to me here is my example if not ignore it, hope it will help you.
//Event action
function scrollOnClick(height) {
$('html').animate({
scrollTop: height
}, 'fast');
return false;
};
// Scroll on PC
let timer = false;
$(window).on('mousewheel', function (event) {
if(timer != true) {
var heightWindow = $(window).height();
var heightCurrent = $(window).scrollTop();
if (event.originalEvent.wheelDelta >= 1) {
if (heightWindow >= heightCurrent) {
timer= true;
scrollOnClick(0)
setTimeout(function (){
timer = false;
},270);
}
} else {
if (heightCurrent < heightWindow) {
timer= true;
scrollOnClick(heightWindow)
setTimeout(function (){
timer = false;
},270);
}
}
}
});
精彩评论