If you bind "mouseover" event for each table cell and move mouse from one corner to another - each event processed.
Usually event perform some visualization. If you move mouse fast I think there are no reason to drown in event handler on each element. Only on last where mouse stop.
But I don't understand how this can be achieved...
You need something to debounce your events (that is, ignore all events that occur close to eachother except for the last one), or something to throttle your events.
You can achieve this by using timeouts in your event handlers, such that they don't proceed if the event was raised within the last 100ms, for example.
There are ready-made solutions for jQuery out there, and Mootools More has a whole chunk of psuedo-events that will help you achieve the same thing.
Use a timer to delay the action, and cancel the timer when apropriate:
var timer;
var divs = document.querySelectorAll('div');
for(var i=0; i<divs.length; i++) {
divs[i].onmouseover = function() {
var div = this;
clearTimeout(timer);
timer = setTimeout(function() {
div.style.backgroundColor = 'green';
}, 200);
}
}
http://jsfiddle.net/B7uRV/
Related
I have a spin function which makes the number rotates every 4 seconds. Now I need to start that function once when the numbers are on viewport, so when on viewport start function only once, I don't mean to rotate the numbers only once, just the function to start only once, and when numbers are not in the viewport anymore then do nothing and so on.
My problem comes when I play scrolling while numbers are visible, because that makes the function spin, to fire many times.
I kinda understand what is going on here, but just can't manage it in javascript, everytime I scroll, the scrolling listener fire multiple times so it also fires multiple times the .each() and also the spin function? If someone can explain what is the problem and how to solve it, would be nice.
If you have a fiddle to share would be nice.
Fiddle
var slots = $('.numbers').find('.slot');
if( slots.length ){
var isScrolling;
window.addEventListener('scroll', function ( event ) {
// Clear our timeout throughout the scroll
window.clearTimeout( isScrolling );
// Set a timeout to run after scrolling ends
isScrolling = setTimeout(function() {
slots.each(function(){
let slotHeight = $(this).height();
let animateTo = (-slotHeight + 21);
if( isElementInViewport( $(this)[0] ) ){
if( ! $(this).is(":animated") ){
spin($(this), animateTo);
}
else{
return false;
}
}
});
}, 200);
});
}
Thank you.
What you're seeing makes sense, because the scroll event triggers many, many times while scrolling (not just once when the scroll starts or ends or something like that).
If you'd like your behavior to be triggered less often, there are several options available to you. You could keep the scroll event listener and use a debounce or a throttle to reduce the number of triggers. Or you could listen to a different even altogether.
In your case, it sounds like you would like to trigger an action once an element comes into view, and then only once. For that, you could still run your scroll handler, but check if the element in question is visible, and then run the rest of your logic. Once that's done, you could de-register your event handler (scroll handlers are potentially expensive because they run so often).
I was trying to make a webapp with html elements that will move form div to antoher div when clicked but also I want to be able fire event when users hold that element for more than a second. So I have this code
$(document).on("click",'.card', function() {
var card=$(this).parent();
if(card.parent().attr('id')==="options"){
card.appendTo("#choice");
}
else{
card.appendTo("#options");
}
});
var timeoutId = 0;
$('.card').on('pointerdown', function() {
timeoutId = setTimeout(showModal, 1000);
}).on('pointerup mouseleave', function() {
clearTimeout(timeoutId);
});
And it is doing almost fine. The problem occures when the element is clicked. It is appended to different div as it should but the mobile pointer is still on it so when I try to fire 'hold' event on another element it is not working for the first time since 'pointerup' event from previous element is firing right after 'pointerdown' event (So you need to try to hold next element twice).
I've dealt with it by adding a simple boolean flag in click event function so it is blocking next first call of 'pointerup' event but this is a very ugly solution.
Do you have any ideas how can i improve this? Maybe there is a way to call 'pointerup' event manually after click?
Removing 'mouseleave' event and adding 'pointerleave' instead fixed the problem.
Not really sure how 'mouseleave' event was fired on mobile device though.
I have a problem when creating plugin. The problem came up when setting new value for variable width.
width variable need to calculate again, if user resizes his browser.
If i attach resize event inside loop, it will make trouble performance.
I came up with the idea to create closure function for wrapping all CODE. So, when the user resize his browser, i call this function again.
JS :
var self = this,
onScrollbarY = function() {
self.each(function() { // loop it
var el = $(this),
elLog = el.find(".scrollbarYLog"),
width = $(window).innerWidth(), // this value will change based on users browser width
onMouseOver = function() {
elLog.html(width);
};
elLog.on("mouseover", onMouseOver);
});
};
onScrollbarY(); // call it immediatly
$(window).on("resize", onScrollbarY); // if users resize its browser, call it again for getting new browser width
Is this the correct way to accompolished it? or there are other option which more efficient rather than attach all code again?
The reason why you had a performance problem in the first place is that every time you called .on('resize', ...), you registered a function that runs on that event. So after 5 resize events, you had 5 functions that were called every time, which is what caused the slow-down.
There are two ways to approach this problem:
only attach one handler to that event (what you ended up doing); or
use the .one('resize', ...) event handler to register a function that will only be triggered on the first next resize event.
Use case #1 is what the majority of developers use and recommend. You create a function (like your onScrollbarY) and you register that function using .on() to be called each time the resize event happens from the moment you register it.
The case #2 is very rare and you probably don't want to use .one() unless you only want to handle the first occurence of that event, and none after that. If you wanted to handle more than one, you would have to call .one() again after the event happens to tell it to listen for that event again.
EDIT: You can simplify your code to the following:
var $window = $(window),
width = $window.innerWidth(), // we know the initial width
$elLog = $(".scrollbarYLog"), // we find the scrollbar element
onResize = function() {
width = $window.innerWidth();
},
onMouseOver = function() {
$elLog.html(width);
};
// register the resize function with the "resize" event
$window.on("resize", onResize);
// register the mouseover function with the "mouseover" event on the scrollbar
$elLog.on("mouseover", onMouseOver);
// there is no need to call onResize(), as we've already set the width variable
I have an input element with 2 events attached: focus and click. They both fire off the same helper function.
When I tab to the input, the focus event fires and my helper is run once. No problems there.
When the element already has focus, and I click on it again, the click event fires and my helper runs once. No problems there either.
But when the element does not have focus, and I click on it, BOTH events fire, and my helper is run TWICE. How can I keep this helper only running once?
I saw a couple similar questions on here, but didn't really follow their answers. I also discovered the .live jQuery handler, which seems like it could work if I had it watch a status class. But seems like there should be a simpler way. The .one handler would work, except I need this to work more than once.
Thanks for any help!
The best answer here would be to come up with a design that isn't trying to trigger the same action on two different events that can both occur on the same user action, but since you haven't really explained the overall problem you're coding, we can't really help you with that approach.
One approach is to keep a single event from triggering the same thing twice is to "debounce" the function call and only call the function from a given element if it hasn't been called very recently (e.g. probably from the same user event). You can do this by recording the time of the last firing for this element and only call the function if the time has been longer than some value.
Here's one way you could do that:
function debounceMyFunction() {
var now = new Date().getTime();
var prevTime = $(this).data("prevActionTime");
$(this).data("prevActionTime", now);
// only call my function if we haven't just called it (within the last second)
if (!prevTime || now - prevTime > 1000) {
callMyFunction();
}
}
$(elem).focus(debounceMyFunction).click(debounceMyFunction);
This worked for me:
http://jsfiddle.net/cjmemay/zN8Ns/1/
$('.button').on('mousedown', function(){
$(this).data("mouseDown", true);
});
$('.button').on('mouseup', function(){
$(this).removeData("mouseDown");
});
$('.button').on('focus', function(){
if (!$(this).data("mouseDown"))
$(this).trigger('click.click');
});
$(".button").on('click.click',evHandler);
Which I stole directly from this:
https://stackoverflow.com/a/9440580/264498
You could use a timeout which get's cleared and set. This would introduce a slight delay but ensures only the last event is triggered.
$(function() {
$('#field').on('click focus', function() {
debounce(function() {
// Your code goes here.
console.log('event');
});
});
});
var debounceTimeout;
function debounce(callback) {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(callback, 500);
}
Here's the fiddle http://jsfiddle.net/APEdu/
UPDATE
To address a comment elsewhere about use of a global, you could make the doubleBounceTimeout a collection of timeouts with a key passed in the event handler. Or you could pass the same timeout to any methods handling the same event. This way you could use the same method to handle this for any number of inputs.
Live demo (click).
I'm just simply setting a flag to gate off the click when the element is clicked the first time (focus given). Then, if the element gets focus from tabbing, the flag is also removed so that the first click will work.
var $foo = $('#foo');
var flag = 0;
$foo.click(function() {
if (flag) {
flag = 0;
return false;
}
console.log('clicked');
});
$foo.focus(function() {
flag = 1;
console.log('focused');
});
$(document).keyup(function(e) {
if (e.which === 9) {
var $focused = $('input:focus');
if ($focused.is($foo)) {
flag = 0;
}
}
});
It seems to me that you don't actually need the click handler. It sounds like this event is attached to an element which when clicked gains focus and fires the focus handler. So clicking it is always going to fire your focus handler, so you only need the focus handler.
If this is not the case then unfortunately no, there is no easy way to achieve what you are asking. Adding/removing a class on focus and only firing the click when the class isn't present is about the only way I can think of.
I have it - 2 options
1 - bind the click handler to the element in the focus callback
2 - bind the focus and the click handler to a different class, and use the focus callback to add the click class and use blur to remove the click class
Thanks for the great discussion everybody. Seems like the debouncing solution from #jfriend00, and the mousedown solution from Chris Meyers, are both decent ways to handle it.
I thought some more, and also came up with this solution:
// add focus event
$myInput.focus(function() {
myHelper();
// while focus is active, add click event
setTimeout(function() {
$myInput.click(function() {
myHelper();
});
}, 500); // slight delay seems to be required
});
// when we lose focus, unbind click event
$myInput.blur(function() {
$myInput.off('click');
});
But seems like those others are slightly more elegant. I especially like Chris' because it doesn't involve dealing with the timing.
Thanks again!!
Improving on #Christopher Meyers solution.
Some intro: Before the click event fires, 2 events are preceding it, mousedown & mouseup, if the mousedown is fired, we know that probably the mouseup will fire.
Therefore we probably wouldn't like that the focus event handler would execute its action. One scenario in which the mouseup wouldn't fire is if the user starts clicking the button then drags the cursor away, for that we use the blur event.
let mousedown = false;
const onMousedown = () => {
mousedown = true;
};
const onMouseup = () => {
mousedown = false;
// perform action
};
const onFocus = () => {
if (mousedown) return;
// perform action
};
const onBlur = () => {
mousedown = false;
// perform action if wanted
};
The following events would be attached:
const events = [
{ type: 'focus', handler: onFocus },
{ type: 'blur', handler: onBlur },
{ type: 'mousedown', handler: onMousedown },
{ type: 'mouseup', handler: onMouseup }
];
Let's imagine I have an event handler function attached to an image, for example, an onmouseover handler. I don't want this handler run if an onmouseover event is fired by a particular element.
To be more specific, I have an image which being hovered, a menu is popped out. I want to close that menu if the mouse moves out of that image, unless I move the mouse to the menu, which is adjacent to the image.
So something like this in pseudo code:
img.mouseout = function () {
if (otherelement.onmouseover.fired) {
leave the menu as it is
}
else
{
close the menu
}
So how can I check whether another event was fired?
I would add a timeout and a mouseover handler that clears it in the other handler. While the elements could be exactly next to each other visually, adding a small timer is more safe since there might just be a tiny 1px area where the mouseout event fires, causing an ugly flickering.
This also allows a quick mouseshake without flicker.
Something like:
var timer;
img.onmouseout = other.onmouseout = function() {
timer = setTimeout(closemenu, 100);
}
img.onmouseover = other.onmouseover = function() {
clearTimeout(timer);
}
function closemenu() {
// close it
}