Stopping a key event from firing excessively - javascript

I made a simple image gallery with a lightbox-style modal each time the image clicks. (I called it lightbox, but it's not the Lightbox library.) It works and I started getting creative with it, deciding to make the images navigable if I hit the arrow keys while the modal is open.
This almost works. The images usually go back in forth in order. However, sometimes it'll flash to the wrong image before it goes to the right one. I've captured it doing this by using console.log on the index of the array that holds the images. What I see in my console is a bunch of weird numbers between the consecutive numbers. It'll go from the right image, to the right image, but occasionally it'll flicker. It seems to often report those weird numbers, whether it flickers or not. It doesn't always do this, so I'm not entirely sure what the cause is, but I suspect that it's reading "key_up" too many times.
Here's the code:
//Allow for keyboard navigation, only if the lightbox is open.
document.addEventListener("keyup", (event)=> {
if(lightbox) {
if(event.key === "ArrowRight") {
index++;
if(index === images.length) {
index = 0;
}
lb_img.firstElementChild.src = images[index].src;
console.log(index);
}
if(event.key === "ArrowLeft") {
index--;
if(index === -1) {
index = images.length - 1;
}
lb_img.firstElementChild.src = images[index].src;
console.log(index);
}
}
});
If you want the full code, I have it on a gist: https://gist.github.com/jckuhl/f8a56349d29308eb4af207a09787270a
EDIT: gist is updated with the new code.
And here's an example of my console: https://i.imgur.com/uWBMS4J.jpg
Any ideas why it might be doing this?

You are attaching the eventlistener as much times as you have clicked on images, because you attach it inside the eventhandler for the click event. This is causing your trouble.
Make sure you attach it only once, by initializing it outside the for loop and outside the click handler.

Related

trying to get onmouseenter and onmouseout working on new img in dom

Environment: Safari 110.0.1, OSX (MacOS) 10.12.6
I am working on an image gallery type of display. There's an element that is loaded with an image by the Javascript; I want that element to have functionality on onmouseenter and onmouseout.
It would appear that I can't add those when I'm creating the img element, because the image is not loaded, and I read (somewhere, lol, it's been a long day) that an element has to be loaded before one can attach functions to it. I did try it in the HTML; no joy.
So I have a timer going, and I am trying to attach it there, but that doesn't work either - the functions are never called.
I tried starting the timer after the page is loaded, but, that doesn't work - the over/out functions still don't fire. The timer is definitely running, that's what forces the redraw if the page scaling changes, and that works. You can see what I want to happen with the mouse over/out by clicking the checkmark below the image.
Here's the page, all the HTML and Javascript code is visible there (lower on the page.) Any insight is appreciated.
The best I've been able to do so far is to get the events to attach to a surrounding div by specifying out in the HTML, where they do pretty much the right thing as far as the div is concerned. But when the mouse is over the image, it is out of the div, so the opposite of what I want happens: the onmouseout fires and the thing I want to have happen over the image goes away.
Is there some reason I can't attach these functions during the timer? Here's the timer code:
function mousingover()
{
console.log('over');
if (dismode == 1) return;
show_image_notes('mypic');
}
function mousingaway()
{
console.log('away');
if (dismode == 0) return;
show_image('mypic');
}
function ticker()
{
// var pic = document.getElementById('mypic'); // get (eventually) ready element
// var eltype = pic.nodeName;
// console.log('eltype:',eltype);
// pic.onmouseenter = function() { mousingover(); }
// pic.onmouseout = function() { mousingaway(); }
// console.log(pic.onmouseout);
// When the display is rescaled, the width of this div changes
// This is used to re-fire the calculation of where the notes go:
var myAnchor = document.getElementById('picdiv');
var xd = myAnchor.offsetWidth;
if (working == 0 && xd != xdim)
{
working = 1;
if (dismode == 1) show_image_notes('mypic');
else show_image('mypic');
xdim = xd;
working = 0;
}
tcounter = tcounter + 1;
if (tcounter > 10 || tcounter < 0) // trying to delay so image has time to load
{
var pic = document.getElementById('mypic'); // get (eventually) ready element
pic.onmouseenter = function() { mousingover(); }
pic.onmouseout = function() { mousingaway(); }
tcounter = 0;
}
}
The console confirms that I am finding the IMG with that ID, which is correct, and also that the functions are attached. They just don't fire.
I'm actively working on this, so the code for the timer will change; the page always displays the code that's in use at the moment.
I'm trying to do this with pure Javascript.
I looked at your HTML, and you have a canvas element in there along with the img element. It looks like the canvas overlays the image, yes? If so, the canvas is going to get the mouse events, and the img won't see them.

Hashchange history breaks iScroll

I have created the following application using iScroll: http://preview.na-software.co.uk/Demo/FutureLearning4/#/section-0
As the user flicks left and right or clicks the arrows in the bottom corners, the application moves the content sections it updates the history by changing the hash so that the user can move back and forth to other sections and bookmark them etc.
However! If you access a hash like: http://preview.na-software.co.uk/Demo/FutureLearning4/#/section-2 and then navigate a few sections and then use the back buttons two issues happen:
1.) It scrolls to the first screen (even though currentSection is correct, and iScroll has been told the correct section).
2.) If you click the back or forward button multiple times, you stop the animation and cause it to become confused and stick in between two sections.
Looking into the code, and seeing that the correct indexes and elements are being passed to iScroll on hashchange, and console logging out the offsets, I've discovered the issue is cause because the offsets are incorrectly set... however just doing refresh() won't fix the issue, as it will then reset the position.
Can anyone see where the problem is or see a way to fix this?
I should note that this bug ONLY happens if you come into the application on a URL that isn't section 0 and then scroll around the application. This is because the offsets will be created correctly by your interactions. But if you come into a URL like section 3, then the offsets will be incorrect and so the hashchanges don't work correctly, if that makes sense.
The hashchange method looks like:
// handle hashchange events
$(window).hashchange( function(){
// read the hash to find out what the new section number is
var nums = location.href.match(/(section)-\d+/g).map(
function(x){ return +x.replace(/\D/g,"") }
);
// set currentSection
currentSection = nums[0];
// if the hashchange was called by user scrolling
if(hashCalledByScroll){
// no need to anything as they have already updated hash and scrolled
hashCalledByScroll = false;
} else {
// find the section to scrollTo
sectionToScrollTo = $('#horizontal > .sections > .section').eq(currentSection).attr('id');
// tell iscroll to scroll to the section
horizontal.scrollToElement( '#' + sectionToScrollTo, null, null, true );
}
// hide the menu on hashchange
hideMenu();
});
Testing your site, I noticed the following: Whenever I access the site via section-3 and then enter the url for section-2, the navigation would instead send me to section-0.
I believe this is the same behaviour as you are experiencing in 1).
So I investigated and came to the following analysis:
In the function horizontal.scrollToElement( '#' + sectionToScrollTo, null, null, true )
iScroll retrieves the utils.offset(el) [iScroll.js#772] for the given el-ement. This offset tells it, where the element to scroll to is.
iScroll goes through the element and all of its offsetParents to add up their offsets. This is where things are breaking: <div class="sections"> has a negative offset to its parent, which imho it should not have.
This, in turn, messes up the scrollTo-coordinates.
To see what I am talking about: document.querySelector('.sections').offsetLeft
This has all just been analysis. My approach to fix this would be to avoid scrollToElement() and instead use scrollTo():
...
} else {
// find the section to scrollTo
sectionToScrollTo = $('#horizontal > .sections > .section').eq(currentSection).attr('id');
// tell iscroll to scroll to the section
var posLeft = -$('#' + sectionToScrollTo)[0].offsetLeft;
var posTop = -$('#' + sectionToScrollTo)[0].offsetTop;
horizontal.scrollTo(posLeft, posTop, 1000);
}
// hide the menu on hashchange
hideMenu();
});
Thus, just calculate the location of the section you want to go to yourself.
About 2) I am not sure if there is much one can do about it. Jumping around quickly breaks a lot of carousels. Maybe a delayed callback to scrollEnd, verifying the validity of the current state.
Another thing I noticed is that you can accidentally stop the transition. Try to click, hold and release the cursor midway a transition - you need to be quick.
Hope this helps.
Found not best solution and it doesn't solve main problem, but it works.
$(window).hashchange(function () {
if (hashCalledByScroll) {
hashCalledByScroll = false;
} else {
var hpage = window.location.hash;
var hpage = hpage.replace('#/section-', ''); //get number of target page
var cpage = currentSection; //number of current page
var count = parseInt(hpage) - parseInt(cpage); //difference
while (count > 0) { //if difference positive: go forward count-times
horizontal.next();
count--;
}
while (count < 0) { //if difference negative: go backward count-times
horizontal.prev();
count++;
}
}
hideMenu();
});
FIDDLE

Limiting click detection on the page [javascript]

I'm trying to limit the user's ability to click on an object to a certain time limit. I looked around and found that apparently, setTimeout() is the correct function to use for this type of thing. I've applied the function to my code, but its not working. I'm thinking/know now that the problem is that the setTimeout in my code isn't limiting the actual click event, which I need to do. Here is a snippet of my click code:
function clickRun(event) {
var $objectVersion = correspondingObject(event.target.id);
if (isAnyVisible() == false) { // none open
$objectVersion.makeVisible();
} else if (isAnyVisible() && $objectVersion.isVisible()) { //click already open div
$objectVersion.makeInvisible();
} else if (isAnyVisible() && $objectVersion.isVisible()==false) { //different div open
searchAndDestroy();
$objectVersion.delay(600).makeVisible();
};
};
$('.ChartLink').click(function(event) {
setTimeout(clickRun(event),5000);
});
I've also created a JSFiddle to represent what I'm talking about: http://jsfiddle.net/FHC7s/
Is there a way to achieve limiting the actual click detection on the page?
I think the easiest way to do it is to keep track of the time of the previous click and if the current click is too soon after that, then don't do anything:
onClick = function(){
if(new Date().getTime() - lastCheck < MIN_CLICK_SPACING) return;
}
Have a look at this JSFiddle, I've set it up so you can have the button disable itself for time duration after detecting a click. Just make sure to remember how your closures are operating with your setTimeouts.
Your code contains an error... your line should be
setTimeout(function(){clickRun(event)},5000);
but even then I don't think that's exactly what you're looking for; that code will "delay" the click by 5 seconds, not actually prevent more clicks. If your true intent is to ignore all clicks after a certain amount of time, then I would go with mowwalker's answer; there's no way to stop the clicks, but you can check to see if you should honor them or not.

Flickering mousenter mouseleave with delay, button list

I have made a list(<p>) with buttons. When I move my mouse over them it's a 1,2 sec delay before my textbox are marked with yellow to show where I can write. When I move my mouse away they turn normal(white).
My problem is when I quickly hover my mouse over the buttons back and forth a lot of the textboxes gets marked.
I had hoped the 1,2 sec delay would have worked then but it doesn't. But it works if I move my mouse slowly in and out of the button.
Here is a fiddle to it: http://jsfiddle.net/Pota/Fj6E6/
Here is my JavaScript code
$(function () {
$("p.pRespRoleId").mouseenter(function () {
var timeOut = 1200;
$this = $(this);
$this.data("delay", setTimeout(function () {
mouseInRespRoleId();
}, timeOut)
);
})
.mouseleave(function () {
$this = $(this);
if ($this.next(mouseOutRespRoleId()).is(":visible")) {
clearTimeout($this.data("delay"));
mouseOutRespRoleId();
}
else {
$this.next("p.pRespRoleId").show();
}
});
});
and
function mouseInRespRole()
{
var txtInRespRole = document.getElementById("<%=txtRespRoleName.ClientID %>");
txtInRespRole.style.background = "#FFFF00";
if (document.getElementById('txtRespRoleName').value == '')
{
document.getElementById('txtRespRoleName').innerHTML = txtInRespRole;
return false;
}
}
function mouseOutRespRole()
{
var txtOutRespRole = document.getElementById("<%=txtRespRoleName.ClientID %>");
txtOutRespRole.style.background = "white";
if (document.getElementById('txtRespRoleName').value == '')
{
document.getElementById('txtRespRoleName').innerHTML = txtOutRespRole;
return true;
}
}
Your jsFiddle is surely confusing to me (I am not sure what you are trying to achieve - there is a tangible possibility that you are overcomplicating things). I hope I got your requirement right...
Anyway, I believe your logic was right, but there were some flaws in the implementation. So, here is a modified (and partially corrected) version of your jsFiddle, which does what (I believe) you were trying to achieve.
Your use of '$this.next(mouseOutRespRoleId()).is(":visible")' was sure the most confusing, so I removed it completely. (In case it was fulfilling some other, not obvious purpose, you'll have to provide a more detailed description.)
The main problem was that $this.next(mouseOutRespRoleId()).is(":visible") was never evaluating to true, thus never clearing the timer that called mouseInRespRoleId().
EDIT:
I updated my jsFiddle illustration so that it takes care of IE9's strange behaviour (a.k.a. bug (?)). It should work without flickering now.
Short explanation of the problem:
Aparantly, in IE9 the mouse-events generation has some "timing issues", so that when entering (mouseOver) and leaving (mouseOut) a component multiple times rapidly, sometimes the mouse-events order gets messed up. E.g.:
The following event sequence (i.e. actual events):
mouseOver -> mouseOut -> mouseOver
May produce the following (obviously wrong) javascript-event sequence (i.e. events triggered by JS-engine in IE9):
mouseOver -> mouseOver(!) -> mouseOut(!)
So, I added an extra clearTimeout($this.data("delay")) in the "mouseentered" handler-function, in order to clear any pending scheduled executions of "mouseInRespRoleId".
It does not work perfectly on IE9 (and probably previous versions of IE - not tested), but it is as good as it can get (afaik).
(NOTE: It still works as intended on other (non-buggy) browsers.)

Can't see problem in JS

I want that when mouse is over an image, an event should be triggered ONCE, and it should be triggered again only after mouse is out of that image and back again, and also at least 2 seconds passed.
If I leave my mouse over the image,it gets called like every milisecond,and by the logic of my function once you hover on the variable 'canhover' becomes 0 until you move mouse out
This code seems to have a bug and I cant see it. I need a new pair of eyes, but the algorithm is kinda logical
Working code :
<script type="text/javascript">
var timeok = 1;
function redotimeok() {
timeok = 1;
}
//
function onmenter()
{
if (timeok == 1)
{
enter();
timeok = 0;
}
}
//
function onmleave()
{
setTimeout(redotimeok, 2000);
leave();
}
//
$('#cashrefresh').hover(onmenter,onmleave);
function enter(){
$("#showname").load('./includes/do_name.inc.php');
$("#cashrefresh").attr("src","images/reficonani.gif");
}
function leave(){
$("#cashrefresh").attr("src","images/reficon.png");
}
</script>
I don't know if this will solve your entire problem (since we don't have a detailed description of what it is), but instead of:
$('#cashrefresh').hover(onmenter(),onmleave());
try:
$('#cashrefresh').hover(onmenter,onmleave);
And the same thing here:
setTimeout(redotimeok, 2000); // just the function name
Also, I don't see where you ever set timeok to zero. Do you mean to set timeok = 0 in onmenter()?
There are two methods in jquery for your problem:
.mouseenter() and .mouseleave()
Check out the demos there.
EDIT:
I thought hover was for mouseover and mouseout, sorry for confusion.
I checked your code again. And it seems that you're changing the image when mouse gets over the image, which forces browser to load the new image and the old image disappears for a very little while till the new one appears and i think this must be triggering both handlers continuosly and you're getting this behaviour.
Try not to change the source of the image, comment out that line and instead console.log("some message") there and see if the message is repeated as much as .load() was fired before.
Hope this helps.
Try changing onmleave function as follows:
function onmleave()
{
setTimeout(redotimeok, 2000);
leave();
}

Categories