I'm having problems getting standard javascript timers to clear on mobile devices (Android and iOS, phone and tablet).
My page contains 2 buttons, a play/pause toggle and a stop button (both FontAwesome icons), the simple HTML for which is:
<span class="fa fa-pause control-button" id="pause-button"></span>
<span class="fa fa-stop control-button" id="stop-button"></span>
The interval is initiated with the following function:
var interval = function() {
$('.control-button').fadeIn(300);
//initiate the interval
infiniteInterval = window.setInterval(Tiles.infiniteTick, speed);
};
Where speed is defined in an earlier function (default is 300). infiniteTick is a very simple function which is working fine. I haven't explained it here as it would require an explanation of the whole program but I can provide code if required.
The play and pause toggles are as follows:
$('body').on('click touchstart', '#pause-button', function() {
if ($(this).hasClass('fa-pause')) {
window.clearInterval(infiniteInterval);
$(this).removeClass('fa-pause');
$(this).addClass('fa-play');
} else {
infiniteInterval = window.setInterval(Tiles.infiniteTick, speed);
$(this).removeClass('fa-play');
$(this).addClass('fa-pause');
}
});
Finally, the interval is terminated with this (some purely aesthetic extras removed for simplicity)
$('body').on('click touchstart', '#stop-button', function() {
window.clearInterval(infiniteInterval);
$('.control-button').fadeOut(300);
});
I initially thought from researching this that it was due to click events not being properly registered, but as you can see I have added touchstart to all the click events and that has made no difference. It's working absolutely fine on all desktop browsers.
Any help is greatly appreciated, and I'd be happy to answer any further questions.
Thanks,
Ben
I've managed to fix the problem, which it turns out was twofold.
Firstly, the click event was firing twice. This was fixed using this SO question: jquery mobile click event fires twice
Secondly, I wasn't properly clearing the intervals.
Edited with #MjrKusanagi's comments
A simple call to clearInterval() before every setInterval() call has fixed the problem, making sure that the interval was always reset before starting again.
Original sketchy workaround:
I've called
infiniteInterval = null;
after every clearInterval() call, as well as wrapping the setInterval() calls with
if (infiniteInterval === null)
Thanks to everyone who commented and hopefully this will help someone sometime :)
First, your click event is firing twice because of this sentence:
$('body').on('click touchstart', '#pause-button', function() { ...
It listens to two events click and touchstart, thus it will be triggered twice, once on click event and once on touchstart event. This is also why your code works well on pc because there's no touchstart event in pc browsers.
So every time you touch that button, things happened like this:
1st event triggered
interval set, handle id is 1 (for example)
infiniteInterval = 1
2nd event triggered
another interval set, handle id is 2
infiniteInterval = 2
And now there's two timing cycles running instead of one, and you only have track of the second one. When you invoke clearInterval, only the handle id = 2 interval is cleared, and 1 is still running.
So the solution is:
Fix the twice-triggered events problem, by only listen to click. (try fastclick or zepto or other lib to deal with the click latency on mobile devices)
As your own answer said, set infiniteInterval to null, and if it is not null do not ever start another interval. (I think it is more elegant than "always clear before setting" works, as infiniteInterval works as a flag of running interval)
Hope these could solve your problem.
Related
Edit
I'm essentially trying to create the Mario style jump, so as you touch / mousedown on the body I have an object that starts travelling up, but when you let go this acceleration stops. This means I can't use FastClick as I'm looking for touchstart, touchend events, not a single click event.
~
I'm trying to respond to a touchstart event on mobile in browser. At the moment I'm using these two listeners:
document.body.addEventListener('touchstart', function(e) {
e.preventDefault();
space_on();
return false;
}, false);
document.body.addEventListener('touchend', function(e) {
e.preventDefault();
space_off();
return false;
}, false);
I'm essentially trying to emulate something I've had working really well where I use keydown and keyup events to make a box jump and fall respectively.
The issue I'm having is that a touch start, if you don't swipe, is actually delaying for a short while. Either that, or a calculation is making me lose framerate.
I'm already using fastclick and that isn't effecting this (probably because it was never meant to fire on touchstart listeners). You can see what I mean here:
https://www.youtube.com/watch?v=8GgjSFgtmFk
I swipe 3 times and the box jumps immediately, and then I click 3 times and you can see (especially on the second one) it loses framerate a little bit or is delayed. Here is another, possibly clearer example: https://www.youtube.com/watch?v=BAPw1M2Yfig
There is a demo here:
http://codepen.io/EightArmsHQ/live/7d851f0e1d3a274b57221dac9aebc16a/
Just please bear in mind that you'll need to either be on a phone or touch device or emulate touches in chrome.
Can anyone help me lose the framerate drop or delay that is experienced on a touchstart that doesn't turn into a swipe?
You should not write your animation loop using setInterval.
Try to replace it with requestAnimationFrame, like this:
function render() {
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
draw_rects();
move();
fall();
move_scenery();
move_jumper();
jumper.y += jumper.vy;
requestAnimationFrame(render);
}
$(window).load(function() {
requestAnimationFrame(render);
});
Like I've done in this pen.
Now your render function is called as soon as the browser is ready to render a new frame. Note that this implementation doesn't use your fps variable, so your frame rate now depends on the browser/device speed. I tested the pen I've edited on my phone and now the animation is way smoother but the scenery is moving too fast (for me at least).
If you want a constant frame rate you can throttle it as explained, for example, here.
Note that you really don't need Fastclick because you aren't binding any click event in your code.
I am making an incremental game in javascript, so I am replacing some links in the page all the time, this links are showing a really weird behavior. I am attaching the click events with .on(, and some times the click event is fired and some times not, is very random.
Here is a clear example of the beheavior. I can imagine that is something related with the time interval, because if I set the interval in 1000 millis, the event always is fired. Of course that I can make some changed in the code in order to don't replace the link and only update it, but I want to know if this is fixable first.
HTML:
<div id="container"><a id="link" href="#">test</a></div>
JS:
$(document).ready(function(){
$("#container").on("click","#link",function(){
alert("clicked");
});
setInterval(function(){
var newA = $("#link").clone();
$("#link").remove();
$("#container").append(newA);
},100);});
Example in jsFiddle:
http://jsfiddle.net/MtR6b/1/
Thanks!!
Your setInterval code is very fast 100 ms that's why when then event is about to trigger element is replaced with new one so click event is not triggered.
Try clicking on the link many times you will see the alert.
DEMO with 1000ms=1s setInterval
I've written an html5 application which is supposed to work on mobile devices. 90% of the time it works fine however in certain devices (mostly androids 4.0+) the click events fire twice.
I know why that happens, I'm using iScroll 4 to simulate native scrolling and it handles the events that happen inside the scroll.(line 533 dispatches the event if you're interested) Most of the time it works fine but in certain devices both the iScroll dispatched event and the original onClick event attached to the element are fired, so the click happens twice. I can't find a pattern on which devices this happen so I'm looking for alternatives to prevent double clicks.
I already came up with an ugly fix that solves the problem. I've wrapped all the clicks in a "handleClick" method, that is not allowed to run more often than 200ms. That became really tough to maintain. If I have dynamically generated content it becomes a huge mess and it gets worse when I try to pass objects as parameters.
var preventClick = false;
function handleClick(myFunction){
if (preventClick)
return;
setTimeout(function(){preventClick = true;},200);
myFunction.call():
}
function myFunction(){
...
}
<div onclick='handleClick(myfunction)'> click me </div>
I've been trying to find a way to intercept all click events in the whole page, and there somehow work out if the event should be fired or not. Is it possible to do something like that?
Set myFunction on click but before it's called, trigger handleClick()? I'm playing with custom events at the moment, it's looking promising but I'd like to not have to change every event in the whole application.
<div onclick='myfunction()'> click me </div>
You can do that with the following ( i wouldn't recommend it though):
$('body').on('click', function(event){
event.preventDefault();
// your code to handle the clicks
});
This will prevent the default functionality of clicks in your browser, if you want to know the target of the click just use event.target.
Refer to this answer for an idea on how to add a click check before the preventDefault();
I don't like events on attributes, but that's just me.
Thinking jquery: $(selector).click(function(){ <your handler code> } you could do something like:
$(selector).click(function(event){
handleClick(window[$(this).attr("onclick")]);
};
of course, there wouldn't be any parameters...
EDIT
Based on the number of views and the complete lack of responses I have to assume that I did a poor job of communicating my issue. I'm going to try to rectify that now.
I extended the HTMLElement prototype with a new tap method like so:
HTMLElement.prototype.tap = function (func) {
this.addEventListener("touchend", func, false);
};
I also created a custom tap event in jQuery:
$(document).delegate("*", "touchend", function (e) {
$(this).trigger("tap");
});
I also created a jQuery plugin called tap:
$.fn.tap = function (func) {
this.bind("tap", func);
};
If I try to use any of these with a callback function that includes an alert statement the callback executes twice. I tap the element to pop up the alert. I tap the "OK" button in the alert to close it. The next time I tap the screen no matter how long I wait the alert pops up again. This time tapping the "OK" button doesn't seem to set up another repeat.
However if the callback function doesn't include an alert statement (e.g. I use a console.log statement instead) the callback only executes the one time.
Does anyone know a way to deal with this? I'm about to try unhooking the event handler from within itself and then rebinding it afterwards, but that's nothing more than a hack if it's even successful.
I'd rather do things the "right" way. :-)
ORIGINAL
I just finished writing a "tap" function that I can use by extending the HTMLElement or Element prototypes as well as a custom "tap" event and "tap" plugin both for jQuery. I thought I had this in the bag until I decided to use a simple alert statement as test code.
When I use these with some element on my test page, they fire properly when I first "tap" the element, but the problem arises after I touch the alert's "OK" button and then, any amount of time later, tap the screen again at which point the event handler fires a second time.
At first I thought it was my custom code, but when I tried it with the following very basic JavaScript I was able to replicate the exact same issue.
document.getElementById("some-element").ontouchend = function (e) {
alert("Howdy doody!");
};
I imagine it must have something to do with the fact that I have to touch the screen again to execute the "OK" on the alert while still technically "inside" the event handler (since the alert is in effect "blocking" the completion of the handler function).
The fact that the behavior isn't replicated with the following slightly different code seems to support my imagination. :-)
document.getElementById("some-element").ontouchend = function (e) {
console.log("Howdy doody!");
};
If I include the above code in a page and touch that element after the callback fires I won't get a repeated firing of that callback function as opposed to the previous block of code where I'll see the alert pop up a second time the next time I tap the screen after hitting "OK" no matter where on the page I tap.
A strange issue indeed, and I haven't been able to find any information about why this might be happening. Does anyone have an idea what is happening?
I believe the visual, full-page alert being triggered on touch end is interfering with the touch event cycle. Try to call the alert after yielding to the DOM. eg.
setTimeout(function() {
alert('btn clicked');
}, 0);
I'm trying to trigger a function when the window is scrolled more than a certain number of pixels.
Here's my code:
$(window).scroll(function(){
if( $(this).scrollTop() >= 100 ) {
someFunction();
} else {
someOtherFunction();
}
});
It kinda works, but there's either a delay of around 2-4 seconds after scrolling before the function(s) are fired or else the functions aren't triggered at all.
Tried it out in Safari / Chrome. Don't know if that helps or not!
The code looks fine and works for me.
As Wolfram says, it's rarely a good idea to attach handlers directly to the scroll event, as this fires a lot and can bring the user's system to a crawl.
I'd recommend using Ben Alman's jquery throttle/debounce plugin.
It works using jQuery 1.6.1 + mousewheel / scrollbar in Chrome15/Safari5.1/FF7 on OSX. What are you doing in those two functions? For testing, I put a simple alert() in someFunction and nothing in someOtherFunction.
Remember that one of those functions is executed every time the scroll-event fires unless you stop it once it was called... e.g. someFunction is called a lot after you scrolled below the 100px line.
John Resig: It's a very, very, bad idea to attach handlers to the window scroll event.
If you're scolling by holding in the click-button instead of using the scroll wheel, I believe the event won't fire until you release the click-button.
Have you considered running a loop that checks the scrollTop instead?
EDIT:
I just check an old project of mine using window scroll event, and it runs perfect with the same event.
I asume you have this script of yours wrapped inside:
$(function() {
// code
});