iOS 6 js events function not called if has setTimeout in it - javascript

I noticed this strange behaviour with the latest iOS (iOS 6). If calling a function for any touch event which has a setTimeout inside, the part inside the setTimeout is never triggered.
This happens only when there is a "system animation" such as scroll and zoom-in/out.
For example:
http://jsfiddle.net/p4SdL/2/
(I used jquery just for testing but the same happens with pure js)
Open that page with safari on any iOS 6 device and zoom in or out. The alert will never be called.
If tested on any iOS 5 device this will work just fine! It seems that during these animations the setTimeout or setInterval are reset by the OS. Is this the intended behaviour or a bug?
Thanks

Note: It looks like UIWebView does not support requestAnimationFrames. Thanks to Guillaume Gendre for pointing it out!
We ran into a similar issue with a web app we're working on.
For us, it was touchmove that caused issues. We implemented a workaround (found here: https://gist.github.com/3755461) that seemed to work pretty well until another issue forced us to abandon it. (I tried adding the workaround to your fiddle and was able to get the timer to fire once or twice, but it required a weird gesture+scroll event that was damn near impossible to consistently reproduce.)
Anyway, one of the new features in iOS 6 for developers are requestAnimationFrames. My workaround is basically a wrapper for timers, allowing the developer to pass a boolean, which will call either the native function or the workaround function.
For example:
setTimeout(function(){alert("HI")}, 1000); // using native
setTimeout(function(){alert("HI")}, 1000, true); // using workaround
Here are additional ways to use the workaround:
setInterval(function(){console.log("Interval")}, 1000, true);
var timer = setTimeout(function(){ /* ... */ }, 60000, true);
clearTimeout(timer);
var interval = setInterval(someFunc, 10000, true);
if(someCondition) clearInterval(interval);
Here are two fiddles with the workaround examples. Try pinch/zooming on the black squares:
http://jsfiddle.net/xKh5m/embedded/result (Uses native setTimeout function)
http://jsfiddle.net/ujxE3/embedded/result
We've been using this workaround for a few months in a production environment, and have not run into any major issues.
Here's a public gist of the workaround: https://gist.github.com/4180482
Here's more information about requestAnimationFrames:
MDN documentation
Paul Irish on requestAnimationFrame
Good luck!

Related

under what circumstances do browsers refuse to continue calling setInterval callbacks?

I'm dealing with a difficult bug where a setInterval callback only gets called 20 or 21 times and then never again, even though I never cleared the interval. (running in Chrome at least)
The page remains responsive, so the callback is not getting stuck in an infinite loop.
Do browsers decide to stop calling a setInterval callback if it takes too long to execute? For instance if some expensive canvas drawing is performed in the setInterval callback?
Unfortunately there are too many potentially-involved layers in this issue (i.e. React, Redux, custom Redux middleware, Canvas drawing) to post a good code sample here. I am asking about the expected behavior of setInterval in modern browsers.
EDIT I think I may be seeing a bug in Chrome to do with canvas drawing inside of setInterval/setTimeout. I can't reproduce in Firefox.
Judging by the setInterval documentation, the only mention of "variation" from the standard behavior seems to be:
Starting in Gecko 5.0 (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2), intervals are clamped to fire no more often than once per second in inactive tabs.

Eliminate 300ms delay on click events in mobile Safari

I've read that mobile Safari has a 300ms delay on click events from the time the link/button is clicked to the time the event fires. The reason for the delay is to wait to see if the user intends to double-click, but from a UX perspective waiting 300ms is often undesirable.
One solution to eliminate this 300ms delay is to use jQuery Mobile "tap" handling. Unfortunately I'm not familiar with this framework and don't want to load some big framework if all I need is a line or two of code applying touchend in the right way.
Like many sites, my site has many click events like this:
$("button.submitBtn").on('click', function (e) {
$.ajaxSubmit({... //ajax form submisssion
});
$("a.ajax").on('click', function (e) {
$.ajax({... //ajax page loading
});
$("button.modal").on('click', function (e) {
//show/hide modal dialog
});
and what I'd like to do is to get rid of the 300ms delay on ALL those click events using a single code snippet like this:
$("a, button").on('tap', function (e) {
$(this).trigger('click');
e.preventDefault();
});
Is that a bad/good idea?
Now some mobile browsers eliminate 300 ms click delay if you set the viewport. You don't need to use workarounds anymore.
<meta name="viewport" content="width=device-width, user-scalable=no">
This is currently supported Chrome for Android, Firefox for Android and Safari for iOS
However on iOS Safari, double-tap is a scroll gesture on unzoomable pages. For that reason they can't remove the 300ms delay. If they can't remove the delay on unzoomable pages, they're unlikely to remove it on zoomable pages.
Windows Phones also retain the 300ms delay on unzoomable pages, but they don't have an alternative gesture like iOS so it's possible for them to remove this delay as Chrome has. You can remove the delay on Windows Phone using:
html {
-ms-touch-action: manipulation;
touch-action: manipulation;
}
Source: http://updates.html5rocks.com/2013/12/300ms-tap-delay-gone-away
UPDATE 2015 December
Until now, WebKit and Safari on iOS had a 350ms delay before single taps activate links or buttons to allow people to zoom into pages with a double tap. Chrome changed this a couple of months ago already by using a smarter algorithm to detect that and WebKit will follow with a similar approach. The article gives some great insights how browsers work with touch gestures and how browsers can still get so much smarter than they are today.
UPDATE 2016 March
On Safari for iOS, the 350 ms wait time to detect a second tap has been removed to create a “fast-tap” response. This is enabled for pages that declare a viewport with either width=device-width or user-scalable=no. Authors can also opt in to fast-tap behavior on specific elements by using the CSS touch-action: manipulation as documented here (scroll down to the 'Styling Fast-Tap Behavior' heading) and here.
This plugin -FastClick developed by Financial Times does it perfectly for you!
Make sure though to add event.stopPropagation(); and/or event.preventDefault(); directly after the click function, otherwise it might run twice as it did for me, i.e.:
$("#buttonId").on('click',function(event){
event.stopPropagation(); event.preventDefault();
//do your magic
});
i know this is old but can't you just test to see if "touch" is supported in the browser? Then create a variable that's either "touchend" or "click" and use that variable as the event that gets bound to your element?
var clickOrTouch = (('ontouchend' in window)) ? 'touchend' : 'click';
$('#element').on(clickOrTouch, function() {
// do something
});
So that code sample checks to see if the "touchend" event is supported in the browser and if not then we use the "click" event.
(Edit: changed "touchend" to "ontouchend")
I've come across a hugely popular alternative called Hammer.js (Github page) which I think is the best approach.
Hammer.js is a more full-featured touch library (has many swipe commands) than Fastclick.js (most upvoted answer).
Beware though: scrolling fast on mobile devices tends to really lock up the UI when you use either Hammer.js or Fastclick.js. This is a major problem if your site has a newsfeed or an interface where users will be scrolling a lot (would seem like most web apps). For this reason, I'm using neither of these plugins at the moment.
Somehow, disabling zoom seems to disable this small delay. Makes sense, as double-tap isn't needed anymore then.
How can I "disable" zoom on a mobile web page?
But please be aware of the usability impact this will have. It may be useful for webpages designed as apps, but shouldn't be used for more general-purpose 'static' pages IMHO. I use it for a pet project that needs low latency.
Unfortunately there is no easy way to do this. So just using touchstart or touchend will leave you with other problems like someone starts scrolling when click on on a button for example. We use zepto for a while, and even with this really good framework there are some issues that came up over the time. A lot of them are closed, but it seems is not a field of simple solution.
We have this solution to globally handle clicks on links:
$(document.body).
on('tap', 'a',function (e) {
var href = this.getAttribute('href');
if (e.defaultPrevented || !href) { return; }
e.preventDefault();
location.href= href;
}).
on('click', 'a', function (e) {
e.preventDefault();
});
I searched for an easy way without jquery and without fastclick library. This works for me:
var keyboard = document.getElementById("keyboard");
var buttons = keyboard.children;
var isTouch = ("ontouchstart" in window);
for (var i=0;i<buttons.length;i++) {
if ( isTouch ) {
buttons[i].addEventListener('touchstart', clickHandler, false);
} else {
buttons[i].addEventListener('click', clickHandler, false);
}
}
In jQuery you can bind "touchend" event, witch trigger code inmediatly after tap (is like a keydown in keyboard). Tested on current Chrome and Firefox tablet versions. Don't forget "click" also, for your touch screen laptops and desktop devices.
jQuery('.yourElements').bind('click touchend',function(event){
event.stopPropagation();
event.preventDefault();
// everything else
});
Just to provide some extra information.
On iOS 10, <button>s on my page could not be triggered continuously. There was always a lag.
I tried fastclick / Hammer / tapjs / replacing click with touchstart, all failed.
UPDATE: the reason seems to be that the button is too close to the edge! move it to near the center and lag gone!
You're supposed to explicitly declare passive mode :
window.addEventListener('touchstart', (e) => {
alert('fast touch');
}, { passive : true});

iOS Javascript DOM "Freezing?"

A few questions here:
Is there anyway to keep iOS from freezing javascript on the page while scrolling?
Does iOS freeze javascript when your in another tab or if you switch apps?
Are there any other major javascript limitations on iOS?
iOS 6.x suspends all event timers in response to touch events like scrolling and has a tendency not to start up all the timers again once the event is done. It's a well known iOS 6 bug that is super-annoying. It pretty much breaks parallax and stuff. Some people have resorted to building their own scroll functionality.
Here's another StackOverflow on the same topic:
iOS 6 safari, setInterval doesn't get fired
and another:
setInterval pauses in iphone/ipad (mobile Safari) during scrolling
and here is the closest thing you'll get to a bug report on it (Apple doesn't make bug reports public to maintain the illusion of perfection, so developers made their own bug site): http://openradar.appspot.com/12756410
This bit of code will unfreeze timers that are broken / lost / destroyed by iOS during a page scroll: https://gist.github.com/ronkorving/3755461
This is another attempt to fix the freeze: iOS 6 js events function not called if has setTimeout in it
Unfortunately, there is nothing you can do to fire events WHILE page scrolling. Like fade out a back-to-top link when scrolling up the page. When it comes to scrolling, iOS6 is incapable of rubbing it's tummy and patting it's head. (iOS5 works fine, btw. This is a regression)
To answer the third question, a decent-sized limitation is that sometimes innerHTML just plain doesn't work. From the accepted answer:
It happens when the CPU of the phone is very busy (say 100%). Then the rendering engine sometimes forget about innerHTML settings.
The solution included in my unify project is to test if there is an element in childNodes, otherwise apply it again.

How to get the progress event working with jplayer

I am using jQuery 1.5 and jPlayer 2.0.0, please ignore the fact that this is going to auto play music, I am going to supply enough warning before it starts, the user has to press a big button to initiate the page and I will provide a big STOP button.
I would like the audio file to begin playing and a function be called after the page is ready and the audio is loaded.
I have the following javascript (I have modified the code so it can be understood better).
function FemeMusic() {
FemeMusic.prototype.addMusic = function(actionAfterMusic) {
$("#jp").jPlayer({
ready: function() {
var jPlayerElement = $(this);
jPlayerElement.jPlayer('setMedia', {
mp3: 'aSongByJamesBlunt.mp3'
});
jPlayerElement.bind($.jPlayer.event.progress,
function(event) {
if (event.jPlayer.status.seekPercent === 100) {
jPlayerElement.jPlayer("play");
}
if (actionAfterMusic) {
actionAfterMusic.call();
}
});
},
swfPath: "/flash",
preload: "auto"
});
}
}
I have this function to trigger the above code.
$(document).ready(function() {
var femeMusic = new FemeMusic();
femeMusic.addMusic(killMyself);
});
I was using the same code but with the $.jPlayer.event.loadeddata event without the if statement instead of $.jPlayer.event.progress and it worked fine in some browsers but isn't compatible when it comes to using flash because it doesn't fire the event (Also I had a problem with it in firefox 3.5 but I don't know why). The above seems to work in Firefox
I read this
"I suggest you use the progress event instead and check that the:
event.jPlayer.status.seekPercent === 100. "
On this post http://groups.google.com/group/jplayer/browse_thread/thread/9dc9736f9d6947bd?pli=1 by the creator
Any one got any suggestions?
Edit
Having not got this working in chrome and safari I tested this on a new version 3.6 of Firefox (which I should have done in the first place) and it worked as expected, despite the fact that in the documentation event.progress id described as:
A robust jPlayer event supported by Flash and HTML5 browsers
and in the link above the creator suggests to use event.progress to get round browser problems I can only assume it's a browser specific issue.
Would be nice to get some clarification on this though.
From the creator of jPlayer:
So your goal here is to auto-play AFTER the media is buffered?
If you want the short answer. Forget it. x-browser buffering is a mine
field. Some simply ignore it as you have found. Just setMedia and play it.
If you really want to try and do it. Then a play then pause would help,
since a media.play() is better x-browser than a media.load() i.e. its
operation varies less. The iOS Safari would ignore the play completely
though. The user would need to click on something that starts the
operation i.e. a play button.
That last point applies to trying any form of autoplay, so ensure that there
is a play button available. The jPlayer interface will cope with iOS not
playing through feedback from the browser events i.e. stalled.
I had problems with the progress event with flash and html5 backend in most browsers. as far as I can see the timeupdate event is working much better.

Opera: Detecting back, forward, refresh and close events

I'm currently working on a jQuery plugin that tracks a visitors mouse behavior. Movements, clicks, scrolling and resizing are all recorded and sent, via Ajax, to a location where this data is parsed and stored.
Originally, the data is sent to a script when the user leaves the page. By 'leaves' I'm referring to refreshing, going back and forth though history, closing the window/tab and going to a different address.
The solution works in all browsers EXCEPT for Opera. I'm using jQuery's 'unload' event which isn't supported by Opera at all. Neither is onbeforeunload or onunload.
The question is, how do I implement this kind of functionality for Opera browsers?
One solution I had was to make special use of a 'polling' feature I created. This feature allows you to specify an interval which pushes the content to the server every 'x' seconds. Setting this to 1 second specifically for Opera browsers would probably solve this issue, but it's an awful amount of overhead and the requests aren't always completed in sequence, etc ...
Any suggestions or am I only stuck with the above option?
Thanks!
I suppose I could just link you guys to the plugin source. http://www.thedrunkenepic.com/junk/jquery.mousalytics.js
Regarding the code linked above, adding:
if(window.opera)
{
options.interval = 1;
}
On line 89 works great. My only concern is overhead, so I'm still looking for a more elegant solution.
According to http://bytes.com/topic/javascript/insights/799229-browser-quirk-onload-onunload-do-not-fire-back-forward-refresh-opera, Opera never really fires onload / onunload events, so functionality like this isn't possible without hacks.
http://dev.opera.com/articles/view/efficient-javascript/?page=4 seems to confirm this, and basically states that opera tries to maintain the state of the page across requests.
On further investgation, http://unitehowto.com/Onunload indicates that it might be possible with opera.io.webserver.addEventListener('_close', onunload, false); (where onunload is a previously defined function), however it also indicates that this functionality is not consistent across all versions of opera, and might not work at all.
I think that your best option is probably to use the polling option for Opera, or possibly use a server-side check for the current page and where it falls in the history queue.
Does adding this line of JavaScript work for you?
history.navigationMode = 'compatible';
Source: http://www.opera.com/support/kb/view/827/
I've had the same problem and this saved my day:
if( typeof(opera) != 'undefined' )
{
opera.setOverrideHistoryNavigationMode( 'compatible' );
history.navigationMode = 'compatible';
}
More info about this problem can be found at: http://www.opera.com/support/kb/view/827/

Categories