I have some code for swipe gesture, the main parts are:
this.topSlide = this.elementRef.nativeElement.querySelector('.product_rate_slide');
if (this.topSlide) {
this.topSlide.addEventListener('touchstart', this.handleTouchStart);
this.topSlide.addEventListener('touchmove', this.handleTouchMove);
this.topSlide.addEventListener('touchend', this.handleTouchEnd);
}
and this is a part of the handler for TouchEnd:
private handleTouchEnd = (evt) => {
if (this.left > 150) {
const rightInterval = setInterval(() => {
this.left += 30;
if (this.left > 500) {
clearInterval(rightInterval);
this.removeTopSlide();
this.addListener();
this.slideSwiped.emit(evt);
}
this.cdr.detectChanges();
}, 17);
Code inside setInterval gets called every 2 seconds (note the interval is set to 17ms)
This works fine on fast machines, the problem occurs when running on a real mobile device (tested with Samsung Galaxy S8) or setting Chrome Performance CPU throttling to 6x slowdown.
The timeout is more a 'request', if the device is too busy doing other stuff, like repainting the DOM and it is not fast enough to keep up, you get delays longer than you want.
So it can be that you need to do something different on slow devices. Besides that: it is better to use setTimeout than setInterval, set a new timeout when the first call is finished. So events don't stack up and get fired at the same time.
Reference (check: Reasons for delays longer than specified):
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified
Related
I have a Chrome extension that tracks the order in which tabs are accessed by listening for tab-related events like tabs.onActivated, onRemoved, etc. It uses an event page instead of a persistent background page to add the event listeners. The tab access order is stored in chrome.storage.local.
The extension works fine while in the normal course of using the browser. But when Chrome is first launched and restores the previous session, it reopens the windows in the order they were originally opened, firing onActivated events for the reopened tabs.
If the extension listened to these events, they would cause the stored tab access order to change, which I'm trying to avoid. I don't want to start listening to the tab events until Chrome has finished restoring the session and has settled down. But I'm not sure how to detect that change in state using an event page that normally has to re-add the event listeners every time it's loaded.
I've tried something like the following to delay adding the tab event listeners until shortly after the last window has been created during startup (it listens for windows.onCreated because Chrome will start up in the background when you restart Windows, but no windows are created at that point):
var gStartingUp = false;
chrome.runtime.onStartup.addListener(() => {
var timer = null;
gStartingUp = true;
chrome.windows.onCreated.addListener(window => {
clearTimeout(timer);
timer = setTimeout(() => {
gStartingUp = false;
addListeners();
}, 750);
});
);
if (!gStartingUp) {
addListeners();
}
In normal usage, gStartingUp would default to false and the listeners would get added. But when the browser fires the onStartup event, the handler isn't called fast enough to prevent the addListeners() call from happening, so the listeners are added during startup as well. I suppose I could add a timeout before calling addListeners(), but that would delay adding them during normal usage.
Is there a way for an extension's event page to reliably detect that Chrome has finished its startup processing?
My initial solution below is too unreliable. I actually saw a case where Chrome restarted, the event page loaded, the timeout to add the listeners fired after 100ms, but the onStartup event didn't fire for another two whole seconds.
So I gave up on trying to delay adding the event handlers until after the onStartup event was handled. Instead, each handler now checks a startingUp flag that's set in the onStartup handler, and only processes the event if it's not in the startup phase. That check is unnecessary for most of the lifetime of the extension, but it at least avoids adding an artificial delay before handling events.
I also switched to listening for tabs.onActivated events during startup, as Chrome seems to reopen the windows first during startup, and then reopens the tabs. So waiting for a pause in tab activations should be a better signal that Chrome has finished starting up.
var startingUp = false;
chrome.runtime.onStartup.addListener(() => {
var timer = null;
startingUp = true;
function onActivated()
{
clearTimeout(timer);
timer = setTimeout(() => {
startingUp = false;
chrome.tabs.onActivated.removeListener(onActivated);
}, 500);
}
chrome.tabs.onActivated.addListener(onActivated);
);
chrome.tabs.onActivated.addListener(tabID => {
if (!startingUp) {
// handle event
}
});
Initial solution
The answer to that question would appear to be "no". The best I've been able to do is indeed delay calling addListeners() long enough for the onStartup listener to fire when Chrome is starting up:
var startingUp = false,
addedListeners = false;
chrome.runtime.onStartup.addListener(() => {
var timer = null;
startingUp = true;
chrome.windows.onCreated.addListener(window => {
clearTimeout(timer);
timer = setTimeout(() => {
startingUp = false;
addListeners();
}, 500);
});
);
setTimeout(function() {
if (!startingUp && !addedListeners) {
addListeners();
}
}, 100);
This seems to mostly work, and the 100ms delay in adding event handlers every time the page is reloaded doesn't seem to be noticeable in actual usage (I've tried 0, 10 and 50ms delays, but they'd sometimes fire before the onStartup handler).
But it all feels very kludgy and brittle if the timing is off. Any improved solutions are welcome.
I'm currently developing a web page with a scrolling parallax effect (Stellar.js) on the header and three other sections of the site: however, scrolling them causes lag, especially at the top of the page.
I've already tried to reduce the background images' size by using compression, but it hasn't made too much difference; removing the blur effect didn't solve the problem, either (it did reduce the lag, but it still wasn't smooth enough).
The website runs pretty well on Firefox (W10), with almost no frame drops, but there's quite some lag on Chrome (both Windows and OS X) and Safari.
There's a few JS scroll-triggered scripts running, but I don't know if those may be the cause. Any suggestions?
What you're going to want to do is throttle scroll events. Debouncing events means an event can't fire again until after a certain amount of time. Throttling events means that the event can only fire so much per period of time.
Here's function to throttle events (credit: http://sampsonblog.com/749/simple-throttle-function)
// Create the listener function
function throttle (callback, limit) {
var wait = false; // Initially, we're not waiting
return function () { // We return a throttled function
if (!wait) { // If we're not waiting
callback.call(); // Execute users function
wait = true; // Prevent future invocations
setTimeout(function () { // After a period of time
wait = false; // And allow future invocations
}, limit);
}
}
}
To use it just do something like this:
function callback () {
console.count("Throttled");
}
window.addEventListener("scroll", throttle( callback, 200 ));
I have a banner that animates through a few background images. The animations are defined in CSS and triggered from JavaScript using the triggerAnimation() function in this fiddle and included below. It works by animating the next-banner div on top of the current-banner div, and then updating the current-banner background to the new image after the animation finishes and resets. The swap from the next-banner to the current-banner should be instantaneous, so that the user doesn't see it.
This works fine in both Firefox and Chrome, but in IE 11 it will occasionally flicker at the end of the animation. It resets back to the beginning state of the animation before the animationend callback makes the swap, causing a noticeable flicker from the new image to the old one, and then back. It happens unpredictably, and doesn't even seem to be consistent in how often it happens. Sometimes it will happen almost every time, while others I'll have to wait through 5 or more transitions before seeing it.
There is evidence that it's caused by the animationend event firing late, because if I set a timeout to 50ms after the animation should have ended, and cancel that timeout in the animationend callback, then my timeout callback only executes when the flicker occurs, and doesn't execute when it behaves normally.
triggerAnimation() code:
window.triggerAnimation = function(element, animation, callback) {
// omitted stuff to determine correct property and event names, which seems to work correctly
element.style[propName] = animation;
element.addEventListener(eventName, function(e) {
element.style[propName] = "";
element.removeEventListener(eventName, arguments.callee);
if (callback) callback(e);
}, false);
};
Is this a problem with my code or with IE, and how can I prevent this from happening?
The closest thing I've found to a solution is to set a timeout for just after when the animation should have finished, which "catches" the cases when IE doesn't fire the callback correctly. Then, the actual event handler cancels the timeout for the cases when the callback is fired correctly.
So, this is what I've ended up with:
window.triggerAnimation = function(element, animation, callback) {
// Omitted stuff to determine correct property and event names
element.style[propName] = animation;
// Extract the duration from the animation CSS and convert it to ms.
var duration = parseFloat(animation.match(/\d+\.\d+/g)[0], 10) * 1000;
var timeout = setTimeout(function() {
element.style[propName] = "";
element.removeEventListener(eventName, arguments.callee);
if (callback) callback(null);
}, duration + 10);
element.addEventListener(eventName, function(e) {
clearTimeout(timeout);
element.style[propName] = "";
element.removeEventListener(eventName, arguments.callee);
if (callback) callback(e);
}, false);
};
The only two downsides to this are: 1) it still will rarely flicker, but much less often and much less noticeably; and 2) any time that the timeout is invoked instead of the event handler, the event object (e) doesn't get passed to callback. In my case that's not a problem.
Add this code to CSS file:
html {
filter: expression(document.execCommand("BackgroundImageCache", false, true));
}
I have a jQuery slider on my site and the code going to the next slide is in a function called nextImage. I used setInterval to run my function on a timer, and it does exactly what I want: it runs my slides on a timer. BUT, if I go to the site in Chrome, switch to another tab and return, the slider runs through the slides continuously until it 'catches up'. Does anyone know of a way to fix this. The following is my code.
setInterval(function() {
nextImage();
}, 8000);
How to detect when a tab is focused or not in Chrome with Javascript?
window.addEventListener('focus', function() {
document.title = 'focused';
},false);
window.addEventListener('blur', function() {
document.title = 'not focused';
},false);
To apply to your situation:
var autopager;
function startAutopager() {
autopager = window.setInterval(nextImage, 8000);
}
function stopAutopager() {
window.clearInterval(autopager);
}
window.addEventListener('focus', startAutopager);
window.addEventListener('blur', stopAutopager);
Note that in the latest version of Chromium, there is either a bug or a 'feature' which is making this less reliable, requiring that the user has clicked at least once anywhere in the window. See linked question above for details.
I post an answer here: How can I make setInterval also work when a tab is inactive in Chrome?
Just do this:
setInterval(function() {
$("#your-image-container").stop(true,true);
nextImage();
}, 1000);
inactive browser tabs buffer some of the setInterval or setTimeout functions.
stop(true,true) - will stop all buffered events and execute immadietly only last animation.
The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).
A few ideas comes to mind:
Idea #1
You can make it so that a short burst is idempotent. For example, you could say:
function now() {
return (new Date()).getTime();
}
var autopagerInterval = 8000;
function startAutopager() {
var startImage = getCurrentImageNumber();
var startTime = now();
var autopager = setInterval(
function() {
var timeSinceStart = now() - startTime();
var targetImage = getCurrentImageNumber + Math.ceil(timeSinceStart/autopagerInterval);
if (getCurrentImageNumber() != targetImage)
setImageNumber(targetImage); // trigger animation, etc.
},
autopagerInterval
);
return autopager;
}
This way even if the function runs 1000 times, it will still run in only a few milliseconds and animate only once.
note: If the user leaves the page and comes back, it will have scrolled. This is probably not what the original poster wants, but I leave this solution up since it is sometimes what you want.
Idea #2
Another way to add idempotence (while still keeping your nextImage() function and not having it scroll to the bottom of the page) would be to have the function set a mutex lock which disappears after a second (cleared by another timeout). Thus even if the setInterval function was called 1000 times, only the first instance would run and the others would do nothing.
var locked = false;
var autopager = window.setInterval(function(){
if (!locked) {
locked = true;
window.setTimeout(function(){
locked=false;
}, 1000);
nextImage();
}
}, 8000);
edit: this may not work, see below
Idea #3
I tried the following test:
function f() {
console.log((new Date()) + window.focus());
window.setTimeout(f, 1000);
}
f();
It seems to indicate that the function is being called every second. This is odd... but I think this means that the callbacks are being called, but that the page renderer refuses to update the page in any graphical way while the tab is unfocused, delaying all operations until the user returns, but operations keep piling up.
Also the window.focus() function doesn't say if the window has focus; it GIVES focus to the window, and is thus irrelevant.
What we want is probably this: How to detect when a tab is focused or not in Chrome with Javascript? -- you can unset your interval when the window loses focus (blur), and reset it when it gains focus.
I don't know exactly what is going on in your function nextImage(), but I had a similar issue. I was using animate() with setInterval() on a jQuery image slider that I created, and I was experiencing the same thing as you when I switched to a different tab and back again. In my case the animate() function was being queued, so once the window regained focus the slider would go crazy. To fix this I just stopped the animate() function from queuing.
There are a couple ways you can do this. the easiest is with .stop(), but this issue and ways to fix it are documented in the jQuery docs. Check this page near the bottom under the heading additional notes: http://api.jquery.com/animate/
I had faced similar issue, somehow this code below works fine for me.
var t1= window.setInterval('autoScroll()', 8000);
window.addEventListener('focus', function() {
focused = true;
window.clearInterval(t1);
t1 = window.setInterval('autoScroll()', 8000);
},false);
window.addEventListener('blur', function() {
focused = false;
window.clearInterval(t1);
},false)
function autoScroll()
{
if ( running == true){
if ( focused = true){
forwardSlide();
}
}
else {
running = true;
}
}
If you are using Soh Tanaka's image slider then just add this...to solve your Google Chrome issue:
$(".image_reel").stop(true, true).fadeOut(300).animate({ left: -image_reelPosition}, 500 ).fadeIn(300);
Take note of the .stop() function. Ignore the fading in and out stuff, that's what I used on my version
Thanks
Seconding the comment by jgerstle to use page visibility events instead, see https://www.w3.org/TR/page-visibility/#example-1-visibility-aware-video-playback for more around subscribing to 'visibilitychange' for hidden/visible states.
This seems to be more useful than focus/blur these days as it covers visible-but-not-selected windows if concerned also about multi-window operating systems.
I am not too familiar with the specifics of every javascript implementation on each browser. I do know however that using setTimeout, the method passed in gets called on a separate thread. So would using a setTimeout recursively inside of a method cause its stack to grow indefinitely until it causes a Stack Overflow? Or would it create a separate callstack and destroy the current frame once it goes out of focus? Here is the code that I'm wondering about.
function pollServer()
{
$.getJSON("poll.php", {}, function(data){
window.setTimeout(pollServer, 1000);
});
}
window.setTimeout(pollServer, 0);
I want to poll the server every second or so, but do not want to waste CPU cycles with a 'blocking loop' - also I do not want to set a timelimit on how long a user can access a page either before their browser dies.
EDIT
Using firebug, I set a few breakpoints and by viewing the "Script -> Stack" panel saw that the call stack is literally just "pollServer" and it doesn't grow per call. This is good - however, do any other implementations of JS act differently?
I am not sure if it would create a stack overflow, but I suggest you use setInterval if the period is constant.
This is how prototype implements its PeriodicalExecuter.
// Taken from Prototype (www.prototypejs.org)
var PeriodicalExecuter = Class.create({
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
execute: function() {
this.callback(this);
},
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
this.timer = null;
},
onTimerEvent: function() {
if (!this.currentlyExecuting) {
try {
this.currentlyExecuting = true;
this.execute();
} finally {
this.currentlyExecuting = false;
}
}
}
});
setTimeout executes sometime later in the future in the event pump loop. Functions passed to setTimeout are not continuations.
If you stop and think about it, what useful purpose or evidencec is there that the call stack is shared by the timeout function.
If they were shared what stack would be shared from the setter to the timeout function ?
Given the setter can do a few returns and pop some frames - what would be passed ?
Does the timeout function block the original thread ?
Does the statement after the setTimeout function execute after the timeout executes ?
Once you answer those questions it clearly becomes evident the answerr is NO.
setTimeout does not grow the callstack, because it returns immediately. As for whether your code will run indefinitely in any browser, I'm not sure, but it seems likely.
take a look at the jQuery "SmartUpdater" plugin.
http://plugins.jquery.com/project/smartupdater
Following features are available:
stop() - to stop updating.
restart() - to start updating after pause with resetting time interval to minTimeout.
continue() - to start updating after pause without resetting time interval.
status attribute - shows current status ( running | stopping | undefined )
updates only if new data is different from the old one.
multiplies time interval each time when data is not changed.
handle ajax failures by stopping to request data after "maxFailedRequests".