Prevent javascript throttle function repeating if user pauses on trigger - javascript

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);
});

Related

Simplify two functions with same functionalities?

I have two functions that do the same thing, but are used differently.
I currently have a popup that is triggered on actions based on device. On desktop, if the mouse leaves the page, it triggers the popup (function called exitUserMessage). On mobile, if the mouse is inactive for 10 seconds+ it triggers the popup (function called inactiveUserMessage). See below:
//display popup for inactive users
const inactiveUserMessage = () => {
//other code was here but deleted bc not relevant
return dismissModal();
};
//display popup for users leaving the page
function exitUserMessage() {
//other code was here but deleted bc not relevant
return dismissModal();
}
Here is where the functions are used, both separately for desktop and mobile:
if (width <= 480) {
// Start the timer on page load
window.addEventListener("load", () => {
timer = setTimeout(inactiveUserMessage, 10000);
});
document.addEventListener("mousemove", debounce(inactiveUserMessage, 10000), timer);
} else {
// Trigger popup for desktop
document.addEventListener("mouseleave", (e) => {
if (Evergage.cashDom("#abandon-cart-message").length === 0) {
return exitUserMessage();
}
});
}
inactiveUserMessage is used in the debounce function that delays it by 10 seconds, then triggers the popup to show.
exitUserMessage is returned when the mouse leaves the page for desktop. My question is both of these functions are doing the same thing above ^.
Any ways to simplify? I am fairly new to JavaScript so looking to learn best practice!
Something worth noting: these are tracked as separate actions in our dataLayer.
Thank you!
You can call the inactiveUserMessage variable like functions. If there are different parts in those functions, add a parameter like isDesktop to inactiveUserMessage and debounce functions . If true, run the relevant desktop code in if statement.
const inactiveUserMessage = (isDesktop) => {
if (isDestop) {
//Desktop
} else {
//Mobile
}
return dismissModal();
};
if (width <= 480) {
// Start the timer on page load
window.addEventListener("load", () => {
timer = setTimeout(inactiveUserMessage, 10000);
});
document.addEventListener("mousemove", debounce(inactiveUserMessage, false, 10000), timer);
} else {
// Trigger popup for desktop
document.addEventListener("mouseleave", (e) => {
if (Evergage.cashDom("#abandon-cart-message").length === 0) {
return inactiveUserMessage(true);
}
});
}

"Wheel" event triggers more than once despite using throttle

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.

When does a click become a hold?

I'm making a web app where a button's behavior is different if the user clicks vs holds the button. I have been experimenting with different timings and it got me wondering if there is any established standard for this kind of thing.
For clarification: I am wondering if there is an exact timing that is standard. Below is the code I am using with 150ms being the threshold for a hold.
function onMouseDown()
{
var holdTimeout = setTimeout(function()
{
//Hold code (also cancels click event)
}, 150);
var cancelHold = function()
{
clearTimeout(holdTimeout);
};
window.onmouseup = cancelHold;
}
function onClick()
{
//Click code
}
Answering exactly your question, hold becomes click. You could set the click event (it's release in fact), inside the mousedown event. Run the code below and try holding and release the mouse button.
document.getElementById("click").addEventListener('mousedown', (e) => {
var i = 0;
var int = setInterval(() => {
console.log("hold " + i++);//<-- actions when we hold the button
}, 200)
document.getElementById("click").addEventListener("click", () => {
clearInterval(int);
console.log("release")//<-- actions when we release the button
})
});
<div id="click">click</div>
In this case, if we hold the button less that 200 milliseconds, just the click (release) event is fired.

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
});

setTimeout function if user not active

I can do something such as the following every 30 seconds to reload the page, and the backend logic will determine which session have been invalidated:
setInterval(function () {
location.reload()
}, 30000);
However, how would I only run this 30s location.reload() if the user is not active? For example, how banks will have a user-timeout if the user has not been active on the page (which only starts counting after the user is 'inactive'). How would this be done?
One way is to track mousemoves. If the user has taken focus away from the page, or lost interest, there will usually be no mouse activity:
(function() {
var lastMove = Date.now();
document.onmousemove = function() {
lastMove = Date.now();
}
setInterval(function() {
var diff = Date.now() - lastMove;
if (diff > 1000) {
console.log('Inactive for ' + diff + ' ms');
}
}, 1000);
}());
First define what "active" means. "Active" means probably, sending a mouse click and a keystroke.
Then, design your own handler for these situations, something like this:
// Reseting the reload timer
MyActivityWatchdog.prototype.resetReloadTimer = function(event) {
var reloadTimeInterval = 30000;
var timerId = null;
...
if (timerId) {
window.clearInterval(timerId);
}
timerId = window.setInterval( reload... , reloadTimeInterval);
...
};
Then, make sure the necessary event handler will call resetReloadTimer(). For that, you have to look what your software already does. Are there key press handlers? Are there mouse movement handlers? Without knowing your code, registering keypress or mousemove on document or window and could be a good start:
window.onmousemove = function() {
...
activityWatchdog.resetReloadTimer();
...
};
But like this, be prepared that child elements like buttons etc. won't fire the event, and that there are already different event handlers. The compromise will be finding a good set of elements with registered handlers that makes sure "active" will be recognized. E.g. if you have a big rich text editor in your application, it may be enough to register only there. So maybe you can just add the call to resetReloadTimer() to the code there.
To solve the problem, use window blur and focus, if the person is not there for 30 seconds ,it will go in the else condition otherwise it will reload the page .
setTimeout(function(){
$(window).on("blur focus", function(e) {
var prevType = $(this).data("prevType");
if (prevType != e.type) { // reduce double fire issues
switch (e.type) {
case "blur":
$('div').text("user is not active on page ");
break;
case "focus":
location.reload()
break;
}
}
$(this).data("prevType", e.type);
})},30000);
DEMO : http://jsfiddle.net/rpawdg6w/2/
You can check user Session in a background , for example send AJAX call every 30 - 60 seconds. And if AJAX's response will be insufficient (e.g. Session expired) then you can reload the page.
var timer;
function checkSession() {
$.ajax({
url : 'checksession.php',
success: function(response) {
if (response == false) {
location.reload();
}
}
});
clearTimeout(timer);
timer = setTimeout(checkSession,30 * 1000);
}
checkSession();

Categories