"Wheel" event triggers more than once despite using throttle - javascript

i try to do something like this:
function throttle(fn, wait) {
var time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
fn();
time = Date.now();
}
}
}
function callback() {
//something
}
something.addEventListener("wheel", throttle(callback, 500));
When I use mousewheel it seems to work nice and triggers only once. The issue is when I use Macbook's touchpad this event triggers (depending on swipe's length) 1, 2 or 3 times at once. What's a problem?

Your code is fine, the problem is that when you "wheel" with a touchpad, you trigger the wheel event a lot, especially a lot of very small values.
For example, if you try to scroll this page with a touchpad, you will notice the smoothness of the scroll. That's because many events are fired with a degressive value.
A throttle is a good start but not enough. An upgrade would be to dismiss wheel events with a very small delta value, like this:
function throttle(fn, wait) {
var time = Date.now();
return function(event) {
// we dismiss every wheel event with deltaY less than 4
if (Math.abs(event.deltaY) < 4) return
if ((time + wait - Date.now()) < 0) {
fn(event);
time = Date.now();
}
}
}
function callback(event) {
// something
}
something.addEventListener("wheel", throttle(callback, 500));
It won't be "perfect" but close.
If you want a perfect result, some advanced maths is necessary. And when I mean advanced, I mean I would myself need one week or two full-time to implement it cleanly across all devices.
If you want to control the wheel to scroll from a screen A to a screen B, you should check out the css scroll snapping property.

Related

Prevent javascript throttle function repeating if user pauses on trigger

I want to resize a header when the user scrolls, by adding/subtracting a class. But, I'm missing something. The script below fires repeatedly, re-adding the class, If a user slowly scrolls down the page to the offset point that triggers the function. It doesn't matter what time I set, the user has still scrolled to that location and stopped - triggering the script repeatedly. I tried with a basic debouncer, and that didn't work - I got similar issues.
Here's my script:
let throttlePause;
const throttle = (callback, time) => {
if (throttlePause) return;
throttlePause = true;
setTimeout(() => {
callback();
throttlePause = false;
}, time);
};
function scrollShrink() {
document.getElementById('wrapper').classList.toggle('page-scrolled', window.pageYOffset >= 20);
}
// run through throttler
window.addEventListener("scroll", () => {
throttle(scrollShrink, 200);
});

Listening to the scroll direction only ONCE on devices with continuous scroll

We're trying to make a pseudo scrolling effect on the homepage of our website but we're running into an issue with the scrolling mechanics touchpads and other digital scrolling mice have (which have a continuing scrolling effect with an easing out timing function).
We want to be able to listen to the direction of the scroll (currently using .on('scroll mousewheel') ) to determine the direction of the scroll (therefore deciding if we should show the previous or next slide) but not listen to every scroll event as this would lead to a series of flashing, seizure-inducing flurry of changes (these being hidden/shown using javascript depending on the scroll value).
We currently have a setTimeout() function that waits 50ms per scroll event before executing the code that actually makes the changes, but this can lead to a longer wait than expected on the aforementioned devices due to their ability to continuously scroll on a swipe, thus continuously refreshing the 50ms wait. This also doesn't take into consideration the easing out scrolling that digital scroll wheels have that allow it to still fire more than once towards the end of the scroll.
So, in essence, we seem to be looking for one of the following:
Listening to the direction of the mousewheel direction without
firing the function every instance of that scroll.
Another workaround we haven't thought about yet.
Here's the current section of javascript related to this:
var timer;
$('html').on ('scroll mousewheel', function (e) {
if(timer) {
window.clearTimeout(timer);
}
timer = window.setTimeout(function() {
var delta = e.originalEvent.wheelDelta;
if((window.innerHeight + window.pageYOffset ) >= document.body.offsetHeight && delta < 0){
$(allSections[scrollNumber]).hide();
$(".cc-nav-circle").removeClass("active");
if(scrollNumber >= allSections.length - 1){
scrollNumber = 0;
} else {
scrollNumber++;
}
} else if($(window).scrollTop() === 0 && delta > 0){
$(allSections[scrollNumber]).hide();
$(".cc-nav-circle").removeClass("active");
if(scrollNumber <= 0){
scrollNumber = allSections.length - 1;
} else {
scrollNumber--;
}
}
$(allSections[scrollNumber]).show();
$(allCircles[scrollNumber]).addClass("active");
}, 50);
});
Here's the current in-progess version of this website: https://unink-marketing.squarespace.com/
Instead of delaying your scroll function until the user is done scrolling, you can scroll right away and just ignore further scroll events until the 50ms timer completes. See the updated code below
var timer;
var justScrolled = false;
$('html').on ('scroll mousewheel', function (e) {
if(timer) {
window.clearTimeout(timer);
}
timer = window.setTimeout(function() {
justScrolled = false;
}, 50);
if(justScrolled) {
return;
}
justScrolled = true;
// Do scroll stuff
});

Disable Predictive Scrolling - Mousewheel (OnScroll) Event fires too often (Touchpad)

I am executing Javascript onScroll.
My code works great with any normal computer mouse, but when I use my notebook's touchpad, I encounter the following situation:
my mouse fires (about 1 to 8) mousewheel events while the finger is moving the wheel.
my touchpad fires a lot more (~60) mousewheel events while the two fingers are touching the pad and continues to fire after my fingers are up in the air again.
I know this behavior from mobile touch devices. The Feature is called "Predictive Touch" - The Scrolling continues if your finger movement had enough acceleration before lifting it up.
I think the touchpad drivers are setting this "smooth scrolling" behavior.
To debug this case, I have used the following code:
/* Handle Mouse-Wheel Scrolling */
var lastChange = +new Date();
$(window).bind('mousewheel', function(e){
console.log("mw");
if(+new Date() - lastChange > 1000){
console.log("mw allowed");
if(e.originalEvent.wheelDelta > 0) {/*go to previous*/}
else{ /*go to next*/}
lastChange = +new Date();
}
return false;});
This is a simple code that "allows" a mouse-scrolling-event every second.
If I make a fast touchpad-scroll, the mousewheel event is fired ~300 times. The one-second-condition is letting 3 events happen. My fingers were on the touchpad for far less than a second.
With this test, I discovered that the mousewheel events are still fired (almost continuously for 3 seconds), even when my fingers are already off the touchpad.
Is there a Javascript function or a workaround / trick / hack to avoid this behavior?
Something like a onTouchEnd event for touchpads, maybe?
To achieve this, you'd have to distinguish between mouse scroll events and touchpad events, which is not (yet) possible using JavaScript. It was already asked in question How to capture touch pad input.
Pointer Events are currently in state of Editor's Draft and not yet supported by any browser. See also touch events docs on MDN.
EDIT: This doesn't appear to work for trackpads. Once they are widely supported, this could be implemented using Touch Events, specifically the Touch End event. By tracking when the finger leaves the trackpad, you can prevent the page scrolling at that particular point.
https://jsfiddle.net/gLkkb5z0/3/
(function(){
var special = jQuery.event.special,
uid1 = 'D' + (+new Date()),
uid2 = 'D' + (+new Date() + 1);
special.scrollstart = {
setup: function() {
var timer,
handler = function(evt) {
var _self = this,
_args = arguments;
if (timer) {
clearTimeout(timer);
} else {
evt.type = 'scrollstart';
jQuery.event.handle.apply(_self, _args);
}
timer = setTimeout( function(){
timer = null;
}, special.scrollstop.latency);
};
jQuery(this).bind('scroll', handler).data(uid1, handler);
},
teardown: function(){
jQuery(this).unbind( 'scroll', jQuery(this).data(uid1) );
}
};
special.scrollstop = {
latency: 300,
setup: function() {
var timer,
handler = function(evt) {
var _self = this,
_args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout( function(){
timer = null;
evt.type = 'scrollstop';
jQuery.event.handle.apply(_self, _args);
}, special.scrollstop.latency);
};
jQuery(this).bind('scroll', handler).data(uid2, handler);
},
teardown: function() {
jQuery(this).unbind( 'scroll', jQuery(this).data(uid2) );
}
};
})();
Demo
Taken from http://james.padolsey.com/javascript/special-scroll-events-for-jquery/

jQuery/Javascript force / pressure on the mouse click

I was wondering if is there any way to get the pressure level (pressure user makes on clicking on mouse key/button) on click.
Any resource or links?
Sorry for my bad english, hope my question is clear and hope is not just utopia!
I realize that I'm bumping an old thread with something that wasn't relevant then, but I believe this will be useful to others in the future.
As many others on this thread have pointed out, almost all computer mice don't measure the pressure used, so you cannot measure it. However, there are different pointing devices than mice out there, like drawing tablets or trackpads. In Gecko-based browsers like Firefox, there is the non-standard mozPressure attribute on mouse events. You would use it sort of like this:
document.addEventListener('click', function (e) {
alert(e.mozPressure);
});
mozPressure is a value between 0.0 and 1.0 depending on how hard the mouse was depressed. If you're using a normal mouse or something else that doesn't detect pressure, then this will always be 0.
Reference.
The mouse (hardware) doesn't register how hard you click it, and javascript can't track something that doesn't exist.
It's clearly not a qustion about phisical pressure but a measure of how long the user is keeping mouse button down and increasing frequency of some effect based on that information. This is my take:
someButton.addEventListener('mousedown', function(e) {
if (e.button == 0) {
someRelevantParentContainer.mouse0Pressed = true;
window.addEventListener('mouseup', function() {
someRelevantParentContainer.mouse0Pressed = false;
}, {once : true});
const func = function() {
if (someRelevantParentContainer.mouse0Pressed && !someButton.disabled) {
// do something
return true;
}
else {
return false;
}
};
recurAction(func, 200);
}
});
function recurAction(func, startingTimeout, minTimeout = 50) {
if (!func()) {
return;
}
if (startingTimeout > minTimeout) {
setTimeout(function() {
const cont = func();
if (cont) {
recurAction(func, startingTimeout / 1.3);
}
}, startingTimeout);
}
else {
const inter = setInterval(function() {
if (!func()) {
clearInterval(inter);
}
}, minTimeout);
}
}

Timed long press in Javascript within jQuery phonegap app

There is a good example for doing long press in Javascript here: Long Press in JavaScript?
But it does not provide for knowing the duration of the press.
If I want to do different things based on the length of the press I cant use the pattern in that post.
I was trying to do something similar by saving current time in a variable on('mousedown')
and then calculating the time difference on('mouseup').
this works fine within a normal Javasript page in a "normal" browser.
However within my phonegap app something happens,
looks like the mouseup event is not being called if the finger is kept on the screen for a long duration (say 5 sec..).
Is this some native mobile browser behavior? Can I override it somehow?
I am using plain jQuery not jQuery mobile.
Any ideas anyone?
You could have a look at how the taphold and vmouseup (handleTouchEnd() line 752) events are implemented in jQuery mobile source code.
Since it is already tested and implemented I'd suggest to use jquery mobile instead of jquery and modify (since it already handles all the 'quirks' related each mobile browser), and change the code as you need.
You can check the time to identify Click or Long Press [jQuery]
function AddButtonEventListener() {
try {
var mousedowntime;
var presstime;
$("button[id$='" + buttonID + "']").mousedown(function() {
var d = new Date();
mousedowntime = d.getTime();
});
$("button[id$='" + buttonID + "']").mouseup(function() {
var d = new Date();
presstime = d.getTime() - mousedowntime;
if (presstime > 999/*You can decide the time*/) {
//Do_Action_Long_Press_Event();
}
else {
//Do_Action_Click_Event();
}
});
}
catch (err) {
alert(err.message);
}
}
Note that this solution is usefull if you do not use jQuery Mobile for some reason.
I used the article Fast Touch Event Handling and just added a piece of code
$.event.special.tap = {
distanceThreshold: 10,
timeThreshold: 350,
setup: function () {
var self = this,
$self = $(self);
// Bind touch start
$self.on('touchstart', function (startEvent) {
// Save the target element of the start event
var target = startEvent.target,
touchStart = startEvent.originalEvent.touches[0],
startX = touchStart.pageX,
startY = touchStart.pageY,
threshold = $.event.special.tap.distanceThreshold,
timeout,
expired = false;
function timerFired() {
expired = true;
}
function removeTapHandler() {
clearTimeout(timeout);
$self.off('touchmove', moveHandler).off('touchend', tapHandler).off('touchcancel', removeTapHandler);
};
function tapHandler(endEvent) {
removeTapHandler();
if (target == endEvent.target) {
if (expired) {
$.event.simulate('longtap', self, endEvent);
} else {
$.event.simulate('tap', self, endEvent);
}
}
};
// Remove tap and move handlers if the touch moves too far
function moveHandler(moveEvent) {
var touchMove = moveEvent.originalEvent.touches[0],
moveX = touchMove.pageX,
moveY = touchMove.pageY;
if (Math.abs(moveX - startX) > threshold || Math.abs(moveY - startY) > threshold) {
removeTapHandler();
}
};
// Remove the tap and move handlers if the timeout expires
timeout = setTimeout(timerFired, $.event.special.tap.timeThreshold);
// When a touch starts, bind a touch end and touch move handler
$self.on('touchmove', moveHandler).on('touchend', tapHandler).on('touchcancel', removeTapHandler);
});
}
};
So, now I have a tap and a longtap events

Categories