Questions about Request Animation Frame - javascript

I'm trying to build a parallax site, which will move few elements while scrolling the site.
But instead of using a scroll event listener I'm using requestAnimationFrame, after reading this post by Paul Irish, and this video which said that scroll listener is a bit buggy. My questions are:
It looks quite smooth in Chrome, but it's flickering badly in Firefox. Did I do something wrong here?
Does my code actually taking up more resources than using normal scroll event listener? I can hear my laptop fan blazing every time I'm playing with this code.
My file is located at http://www.socialbuzz.com.au/index.html, and please scroll to the bottom of the page to see the element that's being manipulated from javascript.

You should have a the scroll event trigger a requestAnimationFrame loop. Do not have requestAnimationFrame triggered by the scroll event itself. You should have something like a var scrolling = true; While this is happening run your requestAnimationFrame loop which references event data from the scroll event. You'll need to debounce the scroll event to turn to loop off once you are finished, it's a chore but the results are worth it. Hope this helps.

You are not performing an animation; that is, you are not modifying your graphics continuously without user interaction. Your manipulation of the page happens only when the user scrolls and does not continue after the user stops scrolling. Therefore, there is no benefit to using requestAnimationFrame, and you should stick to a simple event handler.
The purpose of requestAnimationFrame is to provide optimal behavior for continuous animation; none of its advantages apply here. A looped requestAnimationFrame which does nothing inside the loop, as you currently have, is entirely appropriate if each step of the loop updates the graphics in some way, but since here nothing changes when the user is not scrolling, the loop does nothing but waste CPU time.
An example of when you should use requestAnimationFrame is if you had elements which deliberately lagged behind the scrolling and caught up after a moment. The catch-up animation should be done in a requestAnimationFrame loop which is started from the scroll event handler, and that loop should stop itself when the catch-up finishes.

I have had a similar experience and after much playing around with mouse move listeners and setInterval to increase the frequency of the animation steps, I have gone back to just using onscroll and find that on FF10 and FF 15 it is working great.
Maybe my requirements are not the same as yours - it is an element that tracks the scrollbar so onscroll was the cue to change the position of the box. It lagged behind and was jerky on FF, but worked fine on WebKit and IE. What I found was that onscroll did not fire as often on FF as on Chrome/IE.
When I initially tried this it would be on FF 5 or 6 though. Using a mouse move listener or a frequent interval, I was able to increase the frequency with which my handle scroll function go called - but this actually had the effect of making the positioning appear choppier. Just using onscroll seems to be working for me now on 10 ESR and 15, maybe they fixed something.

Related

comparison of Scroll event vs requestAnimationFrame for scroll event animations

Yes i have read this and this thread - this is more specific question of performance. (or i didn't understand the answers)
As many others I learnt that 'scroll' events should not be used for animations but debounced as they trigger too often. The best way to do this hence requestAnimationFrame.
But recently I wondered the same for mouseMove and all other events and decided to just log how often each are triggering, and see how much more often mousemove and scroll events trigger than RAF running along side. And to my surprise the answer was - not at all.
see codepen here.
Now is this the wrong measure? - I thought the problem is that scroll events trigger more often than our framerate and needs to be debounced in some way. but that does not seem to be the case? or does this only happen in certain situations when repaint / scroll are resource intensive? if so can anyone give any examples?
But the top answer in thread above claimed "However the scroll event is triggered every time the browser renders a scroll position change. This means a scroll event is synchronized with the rendering of the page."
And even mdn scroll docs say that "input events and animation frames are fired at about the same rate, and therefore the optimization below is often unnecessary". - so when is it necessary?
What is really the difference between running a simple animation in each, say of an image moving left to right as you scroll.
/* RAF */
let last;
const loop = () => {
if(window.scrollY!==last){
// run animation here ...
// element.style.transform = `...`
}
last = window.scrollY
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
Versus onscroll
/* onscroll */
const onscroll = ()=>{
// run animation here ...
// element.style.transform = `...`
}
window.addEventListener('scroll',onscroll);
raf has certain advantages, i get that, but also it runs this logic on EVERY FRAME, even when we are not scrolling. so if my page grows and there are more linked animations, they will all accumulate in raf?
If scroll event is (kind of) linked and at least never firing more often than raf, then isnt this more efficient, as it only fires on a scroll event, rather than running (some of the logic) on every frame?
Is the difference in what is being delayed by running animation functions that start getting too long? i understand if onscroll callback is too long it can slow down the scrolling. but the same logic in raf will slow down our framerate?
Or is the answer simply it depends?
For a larger example then, say we are animating a lot of different items on scroll, in my project i used intersection observer to only run logic on elements that are visible, we can add and remove the event listener on the intersection events each.
Or would we used logic as above in raf and check each element in the raf loop?
To end with a specific question then - is my measure of comparing number of triggers on scroll and animation frame wrong? what are the dis-/advantages of each method then and when should it be used, in this case for animations on scroll. is there really a significant difference?

Javascript functions are cutting in and out [duplicate]

This question already has an answer here:
What are passive event listeners?
(1 answer)
Closed last year.
I have one issue that is a recurring thing every time I work in Javascript, which is that my code seems to be cutting in and out, depending on whether I scroll, resize the screen, reload, etc. It even seems like the speed of which I scroll seems to be a factor (even though I am, in this situation, trying to create a scroll effect, which does work as long as I scroll slowly). I have tried to figure out work arounds almost constantly since starting coding JS, but I can’t even figure out why any of this is happening, and I don’t even know what to good to find the answers.
Generally, when I create functions, I try to use the following structure
document.addEventListener("click", function functionName() {
/* function content */
});
However, this really only seems to work with click effects, and maybe hover. for scroll effects, because resizing the screen will cause weird things to happen, I have tried using the following structure
function functionName() { /* function content */ };
document.addEventListener('scroll', functionName);
window.addEventListener('load', functionName);
window.addEventListener('resize', functionName);
This generally works better, and generally prevents screen resizing from interfering too much with scroll effects. However, it makes the code what I am seeing very jumpy and glitchy.
Is there a better way to do this, so that I am a scroll effect will always appear how it should, regardless of whether the screen resizes loads or scrolls, etc.? also, it there a way to make the code work better without having three separate event listeners to activate a single function?
The challenge you are running into for other events is that they can be called 10s or 100s of times per second. What you need is to throttle the functions you are calling, especially if they are expensive.
I suggest you look at makeThrottleFn in my xuu library. You might just install it and test like so:
const throttleFn = xuu._makeThrottleFn_({
_fn_ : onResizeFn, _delay_ms_: 250
});
// And now use this for windowresize
window.addEventListener('resize', throttleFn);
Of course there are many other libraries that offer throttling, so feel free to go shopping.
Using document.addEventListener("click") is going to run the function anytime the screen is clicked or touched. And applies to the whole document. Meaning anywhere you click the event listener is going to run.
Using the scroll event is running the block of code multiple times. This will create undesired results unless you put in something to stop it.
So using multiple document.addEventListeners with "click" and "scroll" will most likely overlap in some cases causing wierd things to happen with both functions running multiple time or randomly.

Firefox 52 javascript scroll fire events lag

Ever since FF 52 was introduced, I am having the same lagging issues when executing on scroll events, especially when using the mouse wheel - the same thing occurs on IE Edge as well, but it was considered a minority, now with the addition of FF some kind of a solution has to be found.
I have created a fiddle online that replicates the problem - the issue is visible on FF and IE edge, while it works smoothly across webkit browsers.
Here is the simple scroll function I am using:
$('.scrollable').on('scroll', function() {
scrollY = $(this).scrollTop();
$(this).find('td:first-child span').attr('style', 'transform:translateY(' + -scrollY + 'px)');
})
https://jsfiddle.net/nfmLa7mn/3/
If you scroll with the mouse wheel the issue is more visible. It's a small lag but it's there. In more complex layouts the issue is more pronounced.
Am I the only one bothered by this? I haven't seen any other similar topics online. Is there any way I am not aware of that can fire scroll events in a better way? Or is there any other way around this issue?
Maybe this is due to the fact that Firefox handles scroll asynchronously.
https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Scroll-linked_effects
Often scrolling effects are implemented by listening for the scroll event and then updating elements on the page in some way (usually the CSS position or transform property.) [...]
These effects work well in browsers where the scrolling is done synchronously on the browser's main thread. However, most browsers now support some sort of asynchronous scrolling in order to provide a consistent 60 frames per second experience to the user. In the asynchronous scrolling model, the visual scroll position is updated in the compositor thread and is visible to the user before the scroll event is updated in the DOM and fired on the main thread. This means that the effects implemented will lag a little bit behind what the user sees the scroll position to be. This can cause the effect to be laggy, janky, or jittery — in short, something we want to avoid.

HTML5 canvas not showing redrawn content during mouse drags

I have a circuit simulator that redraws as you drag things around on an HTML5 canvas.
I've noticed that, sometimes, it fails to redraw during drags. It holds the last drawn frame until I stop moving the mouse, then starts showing updated content.
At first I thought this might be some kind of issue with the code I use the throttle the draws below 60fps. Maybe timer events were being swamped out by higher priority UI events or something like that. But I profiled the code in Chrome, and the profiler confirms the draw code is being called and finishing in a reasonable amount of time.
Here's a screenshot from an example profile I collected while dragging with the issue happening. Note the draw code finishing within 5ms, with plenty of idle time:
So mostly I'm stumped and need ideas of what to check.
What are some common reasons for redraws not to show when they are being triggered by user actions?
Use requestAnimationFrame instead of setTimeout.
During my testing (Chrome, Windows 10), I found that if I just spun the mouse wheel up and down quickly then I could prevent setTimeout callbacks from running until I stopped. That makes setTimeout a really bad idea when it comes to canvas animations. I switched the throttling code to using a requestAnimationFrame cycle, that just kept rescheduling itself until the cooldown period was over, and lag decreased noticeably.
Check for other draw loops.
I noticed that the profiler's stack trace is showing the wrong redraw code. The program had a dialog that contains a canvas that sometimes needs to animate. That code was spamming requestAnimationFrame despite the dialog not showing. Somehow this was interfering with the main draw loop, though I don't know exactly why. All the draws were happening in the hidden dialog.
Once I fixed the dialog code spamming requestAnimationFrame, the pause-while-dragging issue disappeared.

Scroll event on Android Browser not firing. Need workaround

I've created a website with a parallax street scene. See here for an archived version.
It works just fine on all major desktop browsers, and Safari Mobile. It works fine in Mobile Firefox and Chrome for Android Beta also. However the default Android browser has issues with the scroll event. Let me be clear. Scrolling is not the issue. The div scrolls as required. The scroll event doesn't fire. This issue I experience on Honeycomb as well as ICS.
I'm not concerned about other mobile browsers because for mobile screen sizes one usually does not see the parallax scene; mediaqueries and conditional JavaScript loading take care of that. Responsive design and all that jazz.
Basically, I've written a parallise() jQuery plugin that positions each image based on its position and 'depth'. This function is bound to the scroll event.
On Android Browser, this event only fires at the start of the next touch, instead of continuously.
OK, so I thought that perhaps if I bound the function to touchstart, touchmove, and touchend events I would solve my issue. No cigar. Other touch events are also bugged. Applying the suggested workaround causes the events to fire, but as I have to e.preventDefault(), scrolling (the whole point of the exercise) is disabled.
What if I just poll the position of the stage div relative to the window div? Turns out that the position information is only updated at the start of the next touch.
I am at the end of my tether. Any help would be much appreciated.
Even if the touch events worked correctly on the bugged versions of Android, and you were then effectively able to track the native scroll position during a drag, this would be prone to error. For example, it wouldn't account for the momentum animation which happens after the touching has finished.
iOS and Android make sacrifices to improve the performance of scrolling. On both platforms, it's not possible to get the accurate scroll position until the scroll has completed. The scroll event (on the <body>) doesn't fire until the momentum animation is finished. So while your original question is about scroll events on an overflowing <div>, fixing this might not be totally helpful for you anyway.
If you want an animation to update in time with the scroll, then you need to perform the scroll programatically rather than using the browser's native scroll. The best library to do this is iScroll. You can achieve parallax effects very easily as seen in this demo.
If you need more complex effects (the walking character, in your example), you can opt for the "probe" version of iScroll which allows pixel-perfect polling of scroll position in return for reduced performance.
However, there are many downsides to using iScroll:
You may need to change your markup and styling
It is unnecessary overhead for desktop browsers, but due to markup changes may be difficult to use only as a fallback
The scrolling will not feel perfect - on iOS, with its usually excellent scrolling performance - the slight difference in momentum calculation can feel jarring. On Android, the scrolling can become more laggy than usual.
Swipe shim that doesn't need preventdefault on touchstart: https://github.com/TNT-RoX/android-swipe-shim

Categories