JS — FLIP technique and requestAnimationFrame, refactor to not be nested - javascript

I'm having some trouble understanding the requestAnimationFrame API. I think it makes the browser wait for the callback to complete until the browser triggers a repaint (or reflow?). Is that correct? If you call requestAnimationFrame within the callback, the browser will repaint and execute the new RAF's callback and then repaint again? If this is the case, how can I make two sequential calls to this API be more async/promisified rather than nesting them like my example below?
Here I have used the FLIP technique to insert a document fragment into the DOM and animate the height of a DOM element, it seems to work fine. I do not like the callback nature/nesting of the code. I struggle to understand how I can make this more async. Is it possible use a promise and make it thenable?
window.requestAnimationFrame(() => {
this.appendChild(frag);
this.style.height = `${rows * rowHeight}px`;
const { top: after } = this.getBoundingClientRect();
this.style.setProps({
"transform" : `translateY(${before - after}px)`,
"transition" : "transform 0s"
});
window.requestAnimationFrame(() => {
this.style.setProps({
"transform" : "",
"transition" : "transform .5s ease-out"
});
});
});
So, are my preconceived notions about RAF correct? And how can the above code example be void of callbacks and not as nested?

I'm not sure to get your own explanation of requestAnimationFrame, but to put it simply, it is just a setTimeout(fn, the_time_until_painting_frame).
To put it a little bit less simply, it puts fn in a list of callbacks that will all get executed when the next painting event loop will occur. This painting event loop is a special event loop, where browsers will call their own painting operations (CSS animations, WebAnimations etc. They will mark one event loop every n-th time to be such a painting frame. This is generally in sync with screen refresh-rate.
So our rAF callbacks will get executed in this event loop, just before browser's execute its painting operations.
Now, what you stumbled upon is how browser do calculate CSS transitions: They take the current computed values of your element.
But these computed values are not updated synchronously. Browsers will generally wait this very painting frame in order to do the recalc, because to calculate one element, you need to recalculate all the CSSOM tree.
Fortunately for you, there are some DOM methods which will trigger such a reflow synchronously, because they do need updated box-values, for instance, Element.offsetTop getter.
So simply calling one of these methods after you did set the initial styles (including the transition one), you will be able to trigger your transition synchronously:
_this.style.height = "50px";
_this.style.setProperty("transform", "translateY(140px)");
_this.style.setProperty("transition", "transform 0s");
_this.offsetTop; // trigger reflow
_this.style.setProperty("transform", "");
_this.style.setProperty("transition", "transform .5s ease-out");
#_this {
background: red;
width: 50px;
}
<div id="_this"></div>

Related

Performance drop when fading element based on scrollTop

seen afew websites with this effect, however it seems to drop the framerate in my attempt. I basically want to change the opacity of an element the more the user scrolls.
$(window).scroll(function(event){
$("#responsive-slider-with-blocks-1").css("opacity", 1 - $(window).scrollTop() / 1500);
}
Is there a better way to do this? (would be ace just CSS, but not possible).
I'm really not a fan of binding to the scroll event.
Edit:
Due to changing the opacity on an element which covers the entire viewport could be why the framerate drops so much. Would fading in black div covering the element maybe not drop the framerate so much?
Scroll events fire so fast, you're right, every little optimization will help. The docs for the scroll event have advice along those lines:
Since scroll events can fire at a high rate, the event handler shouldn't execute computationally expensive operations such as DOM modifications. Instead, it is recommended to throttle the event using requestAnimationFrame, setTimeout or customEvent...
You can adapt the example they have there to your purposes (and I'm trying to leave out jquery on purpose to remove the overhead):
var last_known_scroll_position = 0;
var ticking = false;
var responsiveSlider = document.getElementById('responsive-slider-with-blocks-1');
function doSomething(scroll_pos) {
responsiveSlider.style.opacity = 1 - scroll_pos / 1500;
}
window.addEventListener('scroll', function(e) {
last_known_scroll_position = window.scrollY;
if (!ticking) {
window.requestAnimationFrame(function() {
doSomething(last_known_scroll_position);
ticking = false;
});
}
ticking = true;
});
This is certainly longer, and there are some global scope messes to consider, but something like this may make the performance difference you are looking for.
Scroll event I believe will be triggered very often during scrolling. When scroll event triggered, jQuery needs to find DOM element based on the selector. This operation alone is quite expensive.
Changing the opacity make it worse as more pixels had to be processed.
Move code to select DOM using jQuery selector outside scroll event handler. That way you can avoid jQuery to lookup DOM element each time scroll event fires.
Limit size of element to reduce number of pixels need to be compute when opacity changed.
Change opacity at certain time interval helps reduce number of paint operations that browser need to do during scrolling operation. So instead of changing opacity everytime event fires, you wait until certain time has elapsed and then change opacity.

Transition time ignored unless a DOM style property is queried before it's set

I'm creating a div using JavaScript and inserting it into a page. I'm doing this by changing the parent div's transform: translateY to shift it up by the div's height, inserting the div, then sliding it back down.
Here's the basic code:
attachTo.style.transform = 'translateY(-' + divHeight + 'px)';
attachTo.insertBefore(div, attachTo.firstElementChild);
attachTo.style.transition = 'transform 2s';
attachTo.style.transform = 'translateY(0)';
With that code the transform time is ignored and the added div pops in as normal. If I change it to something like this, however:
attachTo.style.transform = 'translateY(-' + divHeight + 'px)';
attachTo.insertBefore(div, attachTo.firstElementChild);
// Either of these can be used, as can any statement or expression that queries an element's CSS styles.
console.log(document.body.clientHeight);
var foo = pageWrap.offsetParent;
attachTo.style.transition = 'transform 2s';
attachTo.style.transform = 'translateY(0)';
The div will animate properly. Less surprisingly to me, I can also wrap the final transition and transform changes in a zero-length timeout.
The behaviour's the same in Firefox and Chromium.
My question is why this happens, and why the code isn't executed synchronously? I'm thinking it's related to the browser's execution queue, as covered in this question about zero-length timeouts, but not only would I like to know for certain that's the case, I'd also like an explanation on why using a DOM element's style property achieves the same effect (my guess is it creates a slight pause in execution).
When javascript runs a series of commands, the browser does not redraw the elements until it is done, or if one tell it to, and since the last command resets everything, nothing happens.
This is also the case with many other programming languages.
So one either have to put it in a function, in this case using a setTimeout, or call for example window.scrollHeight in between, to make the browser redraw.
Why a property is used is because javascript does not have a method, like for example Application.DoEvents(); in Dot.Net.
Src: What forces layout / reflow in a browser

Are javascript and css timing synchronized?

Are javascript (timeout, interval) and css (animations, delay) timing synchronized ?
For instance :
#anim1 {
animation: anim1 10s linear;
display: none;
}
anim1.style.display = "block" ;
setTimeout(function() {
anim2.style.webkitAnimation= 'anim2 10s linear';
}, 10000);
Will anim2 be precisely triggered at the end of anim1 ? Is it different depending on the browser ? In this case I'm more interested in a webkit focus.
Note that anim1 is triggered via javascript to avoid loading time inconsistencies.
NB : This is a theoretical question, the above code is an illustration and you must not use it at home as there are way more proper means to do so.
As far as I know, there is no guarantee. However, there are events which you can listen for;
anim1.addEventListener('animationend',function(){
anim2.style.webkitAnimation= 'anim2 10s linear';
}
Note that because these are new, there are still vendor prefixes you need to account for; webkitAnimationEnd and oanimationend for Webkit and Opera.
Also as my original answer (wrongly) suggested, there is transitionend (with similar prefixes) if you want to use CSS transitions instead of animations.
This is the wrong way to do this. There isn't any guarantee that they will be in sync, though it's likely they'll be close.
Events are provided for the start, end an repeat of an animation.
https://developer.apple.com/documentation/webkitjs/webkitanimationevent details these.
Use code like:
element.addEventListener("webkitAnimationEnd", callfunction,false);
to bind to it.

Javascript Rendering Animations on HTML5 DOM from Array. First Run SLOW! Second Run Smooth. How come?

I have a question regarding animations via javascript in HTML using DOM. In this case I am using a with absolute possition and css + jQuery and animate the div.
So when I run through my big array of positions x,y the animation runs very slow. I am running at 100ms (80ms) Interval, but it seems that the rendering is not fast enough and my animations takes longer than 10 seconds...
When re-running the animation it seems that the instructions have been somehow cached ( Rendering ) and my animations runs just perfect.
Then again if I wait for lets say 5 mins, it will be slow again. ( Seems to be low level machine code memory which has been deleted, because it was not used again? )
I just cant figure out how to let my animation run smooth, if its executed the first time.
I tried fabric.js to render animations... Same problem. At the first run its slow. Second run and following are smooth.
function render_mouse()
{
if(play_pos < mousefile_length)
{
$('.mouse').remove();
$("body").append(
$('<div id="mouse" class="mouse"></div>')
.css('position', 'absolute')
.css('top', play_mousefile[play_pos+1] + 'px')
.css('left', play_mousefile[play_pos] + 'px')
.css('width', mousesize)
.css('height', mousesize)
.css('background-image', 'url(images/cursor.png')
);
play_pos = play_pos +2;
}
else {
clearInterval(play_mousetimer);
}
}
UPDATED:
$('#mouse').animate({
left: rec_mousefile[play_pos]+"px",
top : rec_mousefile[play_pos+1]+"px"
},80);
The animation would be faster if you didn't do so many operations on the mouse div every time.
Basically, append the mouse to the dom once, with all the CSS needed for the initial render, cache the reference to the appended element, and then manipulate ONLY the css properties necessary for the animation.
By keeping the element in the dom, rather than removing and re-appending each time, you should see a bit of a performance increase. Further, saving a reference to the appended element will prevent you from having to re-query the dom before giving another update.
The animation should always be a bit faster on a second run due to caching, but these optimizations should help the initial run at least a bit.
** edit in response to comment **
You could cache the reference to the mouse div outside the function, or hang it off the render function itself, a la:
var mouseDiv = $('#mouse');
function render_mouse()
{
if(mousefile_length > play_pos)
{
mouseDiv.animate({
left: rec_mousefile[play_pos]+"px",
top : rec_mousefile[play_pos+1]+"px"
},80);
play_pos=play_pos+2;
}
else {playtimer.stop();}
}
or
function render_mouse()
{
// query the first time, and then use the cached version thereafter
render_mouse.mouse = render_mouse.mouse || $('#mouse');
if(mousefile_length > play_pos)
{
render_mouse.mouse.animate({
left: rec_mousefile[play_pos]+"px",
top : rec_mousefile[play_pos+1]+"px"
},80);
play_pos=play_pos+2;
}
else {playtimer.stop();}
}

Animating a div disappearance, how to smooth that?

I've been trying to animate a Dashboard Widget's div disappearance, but it just brutally goes "poof" (as in, disappears as expected, only instantly).
function removeElement(elementId)
{
duration = 9000; // The length of the animation
interval = 13; // How often the animation should change
start = 1.0; // The starting value
finish = 0.0; // The finishing value
handler = function(animation, current, start, finish) {
// Called every interval; provides a current value between start and finish
document.getElementById(elementId).style.opacity = current;
};
new AppleAnimator(duration, interval, start, finish, handler).start();
interval = 1;
start= "visible";
finish = "hidden";
duration = 9001;
handler = function(animation, current, start, finish) {
document.getElementById(elementId).style.visibility="hidden";
};
new AppleAnimator(duration, interval, start, finish, handler).start();
}
I expected this to "disappear" the div a millisecond after its opacity reaches zero, but for a not so obvious reason (to me), it just disappears immediately. If I comment out the second animation code, the div fades out (but it's still active, which I don't want).
All solutions I've yet seen rely on using JQuery and wait for the event at the end of the animation, is there another way to do that, other than JQuery?
If you are looking for a pure javascript solution it probably needs a good understanding of how javascript event work and basically about javascript language. As reference you should check this question on CodeReview
But as I think the best solution for you and not to rely on jQuery is to checkout CSS3 animations. Even if they are not supported by all browsers you could use Modernizer to fill polyfills for animations.
My favorite CSS3 Animation library is Animate.css. It's pretty neat and gives you a variety of demos in the page.
You'll first have to choose an animation and add it to your css stylesheets. Then have another custom class that contain everything about the animation.
.disappear{
-webkit-animation-duration: 3s;
-webkit-animation-delay: 2s;
-webkit-animation-iteration-count: infinite;
}
Then you could use javascript events to toggle in classes to your Elements. Below is how you add a class to an element.
var animationObject = document.getElementById("poof");
animationObject.className = animationObject.className + " disappear";
If you need more help regarding javascript of how this should be done check out this answer.
Hope this helps...
I found it: AppleAnimator possesses animator.oncomplete: A handler called when the timer is complete.
In my case:
var anim = new AppleAnimator(duration, interval, start, finish, handler);
anim.oncomplete= function(){
document.getElementById(elementId).style.visibility="hidden";
};
anim.start();
The Apple documentation actually calls "Callback" the animation code itself, and "handler" the callback, which makes it a bit hard to realize at first.
Thanks frenchie though, the "YourCallbackFunction" made me realize I was missing something related to callbacks :D

Categories

Resources