jQuery animation function breaks when switching tabs - javascript

UPDATE: I reproduced the error in a plunkr: See http://plnkr.co/BpYfCNBESUT6ZkiSZHgx
The problem occurs when you do following:
Open website. Refresh website, while you see the page is loading in the browser tab, when you see the spinner in the tab.
Switch to another tab in your browser. If it doens't happen. Try again. If did as said, you are most likely seeing this:
You might say, this is not such a big deal, you have to be real fast to let this error happen. However, imagine somebody with a slow connection, he goes to another tab in his browser, continue watching his youtube video, while website is loading. When he comes back he just sees the page keeps loading.
Lets say ive this animation code:
$pageloaderbar.animate({
width: "46%"
}, {
duration: 700,
complete: function () {
$scope.continue();
}
});
When the first animation completes it calls an new function, called $scope.continue();. Which looks likes this:
$scope.continue = function () {
$timeout(function () {
$pageloaderbar.animate({
width: "100%"
}, {
duration: 500,
complete: function () {
$scope.PageIsLoading = false;
}
});
});
}
The problem is, when a user switches tab in his browser, between the $pageloaderbar.animate and the $scope.continue, the $plageloaderbar.animate function never reaches the complete function. The browser console shows the following error (Chrome)
My question is, how can i see if the user is active on the website? Or, how can i still execute the function, even if the user is not active on the browser tab?
Because there seems no one with an awnser, i have figured an little workaround myself. However, if someone still can explain why the animation breaks when switching tab, im very pleased.
The workaround was quite simple, i only had to add this code.
complete: function () {
if(document.hidden) {
$(window).on("blur focus", function () {
$scope.continue();
});
} else {
$scope.continue();
}
}
instead of:
complete: function () {
$scope.continue();
}

This is being caused by the jQuery animate function, which passes the animate options object to a function called speed. This function checks to see if the document is hidden - which it will be if the tab is inactive. If it is hidden, (or fx.off is set to true), all animation durations are set to 0.
You can see this on line 7137 in your plunkr's jQuery file.
if ( jQuery.fx.off || document.hidden ) {
opt.duration = 0;
As the animation now has no duration, it becomes synchronous, and so the fact that your complete function comes after the call to animate, is an issue.
To fix this, you would need to move the complete function declaration above your call to animate.

In the newer version of chrome this is related to visibility of the tab and your function is breaking due to that. you can use visibility API to know that tab is visible to the user or not.
Please have a look at this link which i found :
http://dystroy.org/demos/vis-en.html

$scope.continue = function () {
$timeout(function () {
$pageloaderbar.animate({
width: "100%"
}, {
duration: 500,
complete: function () {
$scope.PageIsLoading = false;
$("body").css("overflow", "auto");
$pageloaderwrap.addClass("page-loader-finished");
$scope.pageReady();
}
});
});
}
var $pageloaderwrap = $(".page-loader-wrap");
var $pageloader = $(".page-loader");
var $pageloaderbar = $(".page-loader .page-loader-bar");
$("body").css("overflow", "hidden");
$pageloaderbar.animate({
width: "46%"
}, {
duration: 700,
complete: function () {
if (document.hidden) {
$(window).on("blur focus", function () {
$scope.continue();
});
} else {
$scope.continue();
}
}
});
Try to define your continue function before call $pageloaderbar.animate . That may not be the main problem but, it is always possible to have a issue because of that.
Your solution is just a patch, not a real solution and not seems good. Even if it solves your problem, you must find a certain way to prevent this error.
I strongly recommend you to leave jquery. Such problems usually comes from jquery - angular incompatibilities.

Related

How to stop a delay if attribute is clicked

Testing this out and I'm trying to figure out how to stop a delay if i click another attribute. I'll post the site address to make this explanation a lot better, but basically when you press menu my nav appears with a delay, when I press Assignment 6 I want everything else to hide, which it does, but I see that because I have a delay when it hides and it's not done delaying it will continue to print out the rest of the elements even though they are supposed to be hidden. Also a disclaimer, I've gotten a lot of heat on this site before because I think people think I expect an answer. This is not the case, I love to learn and although the answer would be helpful and I would be able to de-engineer it and learn it, I would much rather have some guidance. So yeah, I'm not just looking for an answer if anyone thinks that's what I'm on here for (I come on here when I can't figure it out any other way).
site
my jQuery script:
$(document).ready(function () {
//$('ul').hide();
$('ul li').hide();
$('nav>li').hide();
$('nav>h1>').click(function (event) {
event.preventDefault();
$('nav>ul li:hidden').each(function(i) {
$('nav>li').show();
$('nav>h1').hide();
$(this).delay(i*600).fadeIn(200);
});
$('nav>ul li:visible').each(function(i) {
$('nav>h1').hide();
$(this).delay(i*600).fadeOut(200);
});
return false;
}); //closes a.btnDown
$('nav>li').click(function (event) {
$('nav>h1').show();
$('nav>li').hide();
$('ul li').hide();
return false;
}); //closes a.btnDown
}); //closes .ready
setTimeout is a useful mechanism to solve what you are after. It waits for (at least) the delay specified, and executes the callback function.
var elements = $('nav>ul li:hidden');
var timeoutId;
function doAnimation(index) {
timeoutId = window.setTimeout(function () {
if (index < elements.length) {
$(elements[index]).fadeIn(200);
doAnimation(++index);
}
}, 600);
}
The clue is to declare the timeoutId outside a recursive function, and assign it within the function. By doing it in a recursive fashion, you don't start the next timeout before the current timeout is finished, and it can be aborted at any time.
window.clearTimeout(timeoutId);
I've made a little fiddle that demonstrates the concept, but I haven't implemented a complete solution. Hope this helps you get further with your project.
http://jsfiddle.net/pyMpj/
You can replace your delays with setTimeout and clear them with clearTimeout
$('nav>ul li:hidden').each(function(i) {
$('nav>li').show();
$('nav>h1').hide();
var fadeTimeout = setTimeout(function () {
$(this).fadeIn(200);
}, i * 600);
});
$('nav>li').click(function (event) {
$('nav>h1').show();
$('nav>li').hide();
$('ul li').hide();
clearTimeout(fadeTimeout);
return false;
});

jQuery: try catch { call function } does not work?

I'm having a lot of JavaScript on my page and I'm using typekit. In order to make my page work correctly (grid and stuff) I'm using the new typekit font events.
It's simply a try and catch statement that checks if fonts get loaded or not. However somehow I'm not getting it. I'm calling the setGrid() function if typekit fonts are loaded, but e.g. iPad or iPhone doesn't support that yet and so my page doesn't get properly shown when I don't call the setGrid() function.
Anyway, I want to call the function in the error statement as well, so if the page is called on the iPhone, the page works without webfonts as well.
try {
Typekit.load({
loading: function() { },
active: function() { setGrid(); },
inactive: function() { }
})
} catch(e) {
alert('error'); //works
setGrid(); //doesn't get called
}
However, the alert works, the setGrid() function doesn't get called.
Any ideas?
edit: the function looks like that:
var setGrid = function () {
$('#header, #footer').fadeIn(500);
return $("#grid").vgrid({
easeing: "easeOutQuint",
time: 800,
delay: 60
});
};
Try making it "real" function, like this:
function setGrid() {
$('#header, #footer').fadeIn(500);
return $("#grid").vgrid({
easeing: "easeOutQuint",
time: 800,
delay: 60
});
};
The function does get called, but it just doesn't work as you expected causing you to think that it isn't getting called. You can see that it is getting called by adding an alert as the first line of setGrid.
jsfiddle link
Can you:
try/catch around setGrid, too
alert after setGrid to confirm it's getting through setGrid

Best practice for function flow in Javascript?

I have what I thought was a relatively easy Ajax/animation that I'm adding to a client site to select between projects for display as images. The flow goes something like this:
User clicks on a thumbnail in a project overview (pHome).
Using jQuery, ajax load in an XML file with the image, caption and project description data (project1).
Construct the HTML from the XML file and inject it into the DOM (div id="project1").
Animate and (4.5) fade out pHome.
Fade in project1.
Add a 'puff' effect to the thumbnail that brought the user to the project.
All of these things need to happen synchronously, but I can't find the right flow for the functions. Attaching all of these steps to the user click fades out pHome before the animation completes, and the 'puff' effect fires before the project1 div is even visible.
As it is, I have functions for all of these steps, and it seems like a real mess. What I'm asking for is some best-practice way of structuring these functions so they all happen synchronously (with the possible exception of 2 & 3). Just as an aid, here's the pseudocode for my problem:
$('#thumbnail').live('click', function(){
loadXML(thumbnail_id);
makeHMTL(data);
$('pHome').animate({blah}).fadeOut();
$('project1').fadeIn();
$('thumbnail_id').puff();
});
I know this is obviously a bad way to do it - but I can't figure out how to nest/structure the functions to make them all synchronous. And really I'd like an answer that gives me some way to structure my functions in the future to avoid rat-nests. Educate me! :)
Nesting animation functions is one way to do but can be nasty when you do a lot of them and you'll easily lose overview.
An option is to pack them all into an object and pass reference to the callback as such:
$('#thumbnail').live('click', animation.step1);
var animation = {
step1: function() {
$("#Element").show("puff", {}, "slow", animation.step2);
},
step2: function() {
$("#Element").hide("linear", {}, "fast", animation.step3);
},
step3: function() {
$("#Element").show("bounce", {}, 500);
}
}
Or as an alternative you can use the built in animation queues like this:
$("#go1").click(function(){
$("#block1").animate( { width:"90%" }, { queue:true, duration:100 } )
.animate( { fontSize:"24px" }, 1500 )
.animate( { borderRightWidth:"15px" }, 1500);
})
Also check the documentation: link
I would recommend you to make a callback function parameter in your loadXML function, for being able to execute the makeHTML function and your effects when the XML data is loaded from the server.
For the animations, you can execute the following animation on the previous one callback, for example:
$('#thumbnail').live('click', function(){
loadXML(thumbnail_id, function (data) { // data is loaded
makeHMTL(data);
$('pHome').animate({blah}, function () {
$(this).fadeOut();
});
$('project1').fadeIn('slow', function () {
$('thumbnail_id').puff();
});
});
});
Your loadXML function may look like this:
function loadXML (thmbId, callback) {
$.post("/your/page", { thumbnail: thmbId }, function (data) {
callback.call(this, data);
});
}
$("#thumbnail").live("click",function() {
$.ajax({
type: "GET",
url: "myUrl.ashx",
data: { param1: 1, param2: 2 },
success: function(data, textStatus) {
buildAndAppend(data); // Make sure it starts hidden.
$("#Element").show("puff", {}, "slow", function() {
anythingElse();
});
}
});
});
You can use a callback to sequence animations like so:
$('#Div1').slideDown('fast', function(){
$('#Div2').slideUp('fast');
});
Also see Finish one animation then start the other one
The animated, fadeIn and puff events/actions should all have callback options of their own that are called when they're complete. So you'd need to nest those as well and not chain them as you have.
$("#pHome").animate({}, function(){
$("#project1").fadeIn(500, function(){
$("#thumbnail_id").puff();
});
});

How to tell if browser/tab is active [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Is there a way to detect if a browser window is not currently active?
I have a function that is called every second that I only want to run if the current page is in the foreground, i.e. the user hasn't minimized the browser or switched to another tab. It serves no purpose if the user isn't looking at it and is potentially CPU-intensive, so I don't want to just waste cycles in the background.
Does anyone know how to tell this in JavaScript?
Note: I use jQuery, so if your answer uses that, that's fine :).
In addition to Richard Simões answer you can also use the Page Visibility API.
if (!document.hidden) {
// do what you need
}
This specification defines a means for site developers to
programmatically determine the current visibility state of the page in
order to develop power and CPU efficient web applications.
Learn more (2019 update)
All modern browsers are supporting document.hidden
http://davidwalsh.name/page-visibility
https://developers.google.com/chrome/whitepapers/pagevisibility
Example pausing a video when window/tab is hidden https://web.archive.org/web/20170609212707/http://www.samdutton.com/pageVisibility/
You would use the focus and blur events of the window:
var interval_id;
$(window).focus(function() {
if (!interval_id)
interval_id = setInterval(hard_work, 1000);
});
$(window).blur(function() {
clearInterval(interval_id);
interval_id = 0;
});
To Answer the Commented Issue of "Double Fire" and stay within jQuery ease of use:
$(window).on("blur focus", function(e) {
var prevType = $(this).data("prevType");
if (prevType != e.type) { // reduce double fire issues
switch (e.type) {
case "blur":
// do work
break;
case "focus":
// do work
break;
}
}
$(this).data("prevType", e.type);
})
Click to view Example Code Showing it working (JSFiddle)
I would try to set a flag on the window.onfocus and window.onblur events.
The following snippet has been tested on Firefox, Safari and Chrome, open the console and move between tabs back and forth:
var isTabActive;
window.onfocus = function () {
isTabActive = true;
};
window.onblur = function () {
isTabActive = false;
};
// test
setInterval(function () {
console.log(window.isTabActive ? 'active' : 'inactive');
}, 1000);
Try it out here.
Using jQuery:
$(function() {
window.isActive = true;
$(window).focus(function() { this.isActive = true; });
$(window).blur(function() { this.isActive = false; });
showIsActive();
});
function showIsActive()
{
console.log(window.isActive)
window.setTimeout("showIsActive()", 2000);
}
function doWork()
{
if (window.isActive) { /* do CPU-intensive stuff */}
}
All of the examples here (with the exception of rockacola's) require that the user physically click on the window to define focus. This isn't ideal, so .hover() is the better choice:
$(window).hover(function(event) {
if (event.fromElement) {
console.log("inactive");
} else {
console.log("active");
}
});
This'll tell you when the user has their mouse on the screen, though it still won't tell you if it's in the foreground with the user's mouse elsewhere.
If you are trying to do something similar to the Google search page when open in Chrome, (where certain events are triggered when you 'focus' on the page), then the hover() event may help.
$(window).hover(function() {
// code here...
});

How can I return true/false from an animation queue thats inside a click() function?

This seems so tricky to me that I think I am overlooking something simple here.
Can you help me find it?
Basically, the situation is this
(variables where the name is not important are named with a capital letter) :
$('a').filter('somecriteria').each(function() {
var self = $(this);
self.click(function() {
B.animate({ something: somevalue }, { duration: "fast", complete: function(){
return true; // <--- see notes below
}
});
return false;
});
});
Notes:
How can I let this return be the return that is sent from click() so that the user is taken to the new page only after animate() has fully completed (NB: in my real code I have three animate()'s chained together).
I need to do this because the animation is cut off and the new page is loaded prematurely. The animation only comes to full effect when return false is sent from click() until the animation really ends.
I tried capturing the href attribute of the link and doing a window.location = capturedlink instead of return true.
This works, but if possible I want to avoid that because some people have disabled changing location because of security concerns.
Or is there another way of keeping jQuery changing to a new URL in the middle of an animation?
Thank you very much for checking out this question.
Andre
I'm afraid capturing the href way would be the most obvious way of doing what you want. I am not sure if doing the following would circumvent people that disabled changing location in their security settings, but you could try it:
$('a').filter('somecriteria').each(function() {
var self = $(this);
self.click(function() {
if(self.hasClass('animated')) return true; // return true if already animated
B.animate({ something: somevalue }, { duration: "fast", complete: function(){
self.addClass('animated').click(); // fire the click event
}
});
return false;
});
});

Categories

Resources