Reduce mousemove events - javascript

I've this code:
$("body").mousemove(function (event) {
var lowestspanelm = findNearestSpan(gg_phone_spans, event);
});
During mouse move this is generating too many events and increasing CPU load which is taking around 1 minute or so to become normal.
To reduce this load what I want is to act only when mouse has stopped for at least 1/2 second.
What is the way to do it?

Though your question points in a direction that you need a debounce function, my advise is that you try a throttle function instead.
A debounced function will only fire when there are no more incoming events
A throttled function will fire only every so often
$("body").mousemove(throttle(function (event) {
var lowestspanelm = findNearestSpan(gg_phone_spans,event);
}, 500));
This will fire only every .5 seconds as long as there are incoming events
You won't need jQuery for this.
document.addEventListener('mousemove', throttle(function (event) {
var lowestspanelm = findNearestSpan(gg_phone_spans,event);
}, 500));
Here is a demonstration of the differences.
If you hover over the NORMAL element the background color will change with every pixel moved.
If you move inside the THROTTLE element, the color will change only every half of a second as long as you are moving.
If you move inside the DEBOUNCE element, the color will change only .5 seconds after you stopped moving (or moved out of the element).
Here are debounce and throttle next to each other
function debounce(callback, wait) {
var timeout;
return function(e) {
clearTimeout(timeout);
timeout = setTimeout(() => {
callback(e);
}, wait);
}
}
function throttle(callback, wait) {
var timeout
return function(e) {
if (timeout) return;
timeout = setTimeout(() => (callback(e), timeout=undefined), wait)
}
}
function onMove(e) {
e.target.classList.toggle('gold')
}
document.querySelector('.normal').addEventListener('mousemove', onMove)
document.querySelector('.throttle').addEventListener('mousemove', throttle(onMove, 500))
document.querySelector('.debounce').addEventListener('mousemove', debounce(onMove, 500))
.normal,
.throttle,
.debounce {
width: 100px;
height: 100px;
margin: 25px;
background: silver;
border: 1px solid gold;
}
.normal.gold,
.throttle.gold,
.debounce.gold {
background: gold;
}
body {
display: flex;
}
<div class="normal">NORMAL</div>
<div class="throttle">THROTTLE</div>
<div class="debounce">DEBOUNCE</div>

One possible way is to use timeouts:
var mouseTimeout;
$("body").mousemove(function (event) {
clearTimeout(mouseTimeout);
mouseTimeout = setTimeout(function() {
var lowestspanelm = findNearestSpan(gg_phone_spans,event);
}, 500);
});
You can adjust the 500 to have it trigger more or less often.

As commented this is generally called a debounce function, and is available in many general purpose libraries. It is also fairly easy to write (a simple version) yourself.
$(document).on("mousemove",debounce(function(event) {
console.log("This will only happen after you stop moving the mouse for 1/2 second")
},500));
function debounce(fn, time){
let timer = null;
return (evt) => {
clearTimeout(timer);
timer = setTimeout( () => fn(evt), time);
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Related

Is there a way to fire a function only if an event is sustained for a while

I'm trying to call a function only if the user hovered over the target for a certain amount of seconds.
So it doesn't fire instantly and also doesn't just fire after a time frame but only fires if the user hovered for the entire duration of the timeframe.
If this could be done in JavaScript it would be fantastic. Thanks in advance
Just set a timeout when the mouse hovers the div, and clear it when the mouse leaves the div. In this way the callback will be fired only and only if the mouse remained within the div for n seconds
const el = document.querySelector('.hover-me');
let timeoutHandler = null;
el.addEventListener('mouseenter', () => {
timeoutHandler = setTimeout(() => alert('Hovered for 2 seconds'), 2000);
});
el.addEventListener('mouseleave', () => {
if (timeoutHandler) clearTimeout(timeoutHandler);
timeoutHandler = null;
});
div.hover-me {
width: 200px;
height: 200px;
background-color: lightblue;
}
<div class="hover-me">Hover me</div>

How to correctly use the scroll eventListener in vanilla javascript?

How can I improve the code below?
I wish to use only one function to execute my code with the scroll event. I have something like this:
function scrollMenu() {
window.addEventListener("scroll", () => {
// do stuff
});
}
function scrollhabilities() {
let cont = 0;
window.addEventListener("scroll", () => {
// do stuff
};
});
}
This works, but I tried to improve the code to be less repetative, however it doesn't work:
const scrollmenu = () =>{}
const scrollhabilities = () =>{}
function scrollThings() {
window.addEventListener("scroll", () => {
scrollmenu();
scrollhabilities():
});
}
Maybe this helps you to get a starting point. (you can obviously remove the event parameter if you never need it inside the function)
var scrollmenu = function(scrollEvent) {
console.log('scrollmenu', window.scrollY)
};
var scrollhabilities = function(scrollEvent){
console.log('scrollhabilities', window.scrollY)
};
var scrollThings = function () {
window.addEventListener("scroll", event => {
scrollmenu(event);
scrollhabilities(event);
});
}();
div.scroll {
background-color: orange;
height: 1000px;
width: 400px;
}
<div class="scroll">move your cursor here and scroll up and down</div>
You might need a debounce function. Debouncing allows your function to fire only once in the specified period of time (500ms in the below example).
Without this function, scrolling the window will fire thousands of times during a continuous scroll. With this function, it will fire only once. As soon as the user pauses for the specified period of time (eg. 500ms), the function will fire. (I set the timeout to 500ms to ensure the effect is seen - usually you would not specify such a large number - perhaps 100ms or less might be better.)
Note that there is a companion function called "throttle" that is slightly different.
var cnt = 0;
const msg = document.getElementById('msg');
window.addEventListener("scroll", () => {
handleScroll();
});
const handleScroll = debounce(() => {
cnt++;
msg.innerText = cnt;
},500);
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
body{height:3000px;}
#msg{
position:fixed;
top:20px;
right:2px;
padding:2vh 5vw;
font-size:5rem;
background:wheat;
border:1px solid orange;
}
<div id="msg"></div>
References:
https://davidwalsh.name/javascript-debounce-function
this comment explains throttle
https://css-tricks.com/debouncing-throttling-explained-examples/

The event .click fires multiple times

On the page there is a link with id get-more-posts, by clicking on which articles are loaded. Initially, it is outside the screen. The task is to scroll the screen to this link by clicking on it. The code below does what you need. But the event is called many times. Only need one click when I get to this element scrolling.
p.s. sorry for my bad english
$(window).on("scroll", function() {
if((($(window).scrollTop()+$(window).height())+250)>=$(document).height()){
$('#get-more-posts').click();
}
});
Try use removeEventListener or use variable with flag, just event scroll detached more at once
You can set up throttling by checking if you are already running the callback. One way is with a setTimeout function, like below:
var throttled = null;
$(window).on("scroll", function() {
if(!throttled){
throttled = setTimeout(function(){
if((($(window).scrollTop()+$(window).height())+250)>=$(document).height()){
$('#get-more-posts').click();
throttled = null;
}
}.bind(window), 50);
}
}.bind(window));
Here's an ES6 version that might resolve the scoping issues I mentioned:
let throttled = null;
$(window).on("scroll", () => {
if(!throttled){
throttled = setTimeout(() => {
if((($(window).scrollTop()+$(window).height())+250)>=$(document).height()){
$('#get-more-posts').click();
throttled = null;
}
}, 50);
}
});
The last argument of setTimeout is the delay before running. I chose 50 arbitrarily but you can experiment to see what works best.
I don't know how true it is, but it works. After the event (click), delete the element id, and then add it again, so the click is performed once. Scroll the page to the desired item, click again, delete the id and add it again. It works. Can someone come in handy.
window.addEventListener('scroll', throttle(callback, 50));
function throttle(fn, wait) {
var time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
fn();
time = Date.now();
}
}
}
function callback() {
var target = document.getElementById('get-more-posts');
if((($(window).scrollTop()+$(window).height())+650)>=$(document).height()){
$('#get-more-posts').click();
$("#get-more-posts").removeAttr("id");
//$(".get-more-posts").attr("id='get-more-posts'");
};
}
window.removeEventListener('scroll', throttle(callback, 50));

Vanilla Javascript, mouseout delay with cancelation without jQuery

i am currently having a problem. I am using css to hide and display elements depending on the mouse function. One of my elements (a navigation arrow) depends on some other things. I now need a cancleable timer function which counts for lets say 2 seconds on mouseleave and then changes the class attribute. But it should have a timer which cancels on mouseover immediately. I dont want it to disappear too early.
Below my code with which i tried so far. I have no idea how to access the current timings of that setIntervall stuff. I alreasy experimented with Date.now(). But now i hope some of the geeks is able to help me.
Thanks in advance.
function hideElementOnMouseOut(el)
{
el.addEventListener("mouseleave", function( event )
{
mySlideAction = setInterval( function()
{
}, 1000 );
}
}
You can initialize interval on mouseleave function and clear this interval on mouseover function, which would prevent executing it's function.
Check the snippet below.
function hideElementOnMouseOut(el)
{
var interval;
el.addEventListener("mouseleave", function(event)
{
el.innerHTML = 'mouse out';
interval = setInterval(function()
{
el.innerHTML = 'time out';
el.className = 'out';
}, 1000);
});
el.addEventListener("mouseover", function(event)
{
el.innerHTML = 'mouse in';
el.className = '';
if(interval) {
clearInterval(interval);
}
});
}
hideElementOnMouseOut(document.getElementById("element"));
#element {
display: block;
width: 200px;
height: 200px;
background: red;
}
#element.out {
background: blue;
}
<div id="element"></div>

Temporarily disabling javascript function from repeating after mouseleave

I have an image gallery that rotates through the rotator class divs on www.creat3dprinters.com that pauses on mouseenter and then fires again 1 second after mouseleave.
However, if a user moves the mouse in and out of the rotator class div quickly the function calls stack up and the visible changes until the 'stack' is completed.
I want the 1 second delay that has not been completed to be cancelled on the 2nd and subsequent mouseenter so that this does not happen.
I have tried using clearTimeout within the mouseenter function but it does not seem to work.
I know there is also the stop() function but that did not work either.
Any suggestions greatly appreciated.
jQuery(document).ready(function () {
var initList = setInterval('RotateIt()', 4000);
$('.rotator').mouseenter(function () {
clearInterval(initList);
}).mouseleave(function () {
timeout = setTimeout(function () {
RotateIt()
}, 1000);
initList = setInterval('RotateIt()', 4000);
})
});
function RotateIt() {
clearTimeout(timeout);
if ($('#rotator-visible').next('.rotator').length == 0) {
$('.rotator:first').attr('id', 'rotator-visible');
$('.rotator:last').removeAttr("id");
} else {
$('#rotator-visible').removeAttr("id").next('.rotator').attr("id", "rotator-visible");
}
}
If a user moves the mouse in and out of the rotator class div quickly the function calls stack up
Then clearTimeout it - and in exactly that place, not only in the delayed RotateIt. The simplest solution would be to call clearTimeout every time before setTimeout, so that you can be sure there is only one active timeout at once.
jQuery(document).ready(function($) {
var initList = setInterval(rotateIt, 4000),
delay = null;
$('.rotator').mouseenter(function(e) {
clearInterval(initList);
}).mouseleave(function(e) {
clearTimeout(delay);
delay = setTimeout(function () {
rotateIt();
initList = setInterval(rotateIt, 4000);
}, 1000);
})
});
function rotateIt() {
if ($('#rotator-visible').next('.rotator').length == 0) {
$('.rotator:first').attr('id', 'rotator-visible');
$('.rotator:last').removeAttr("id");
} else {
$('#rotator-visible').removeAttr("id").next('.rotator').attr("id", "rotator-visible");
}
}

Categories

Resources