How to make Vanilla Javascript animation less laggy? - javascript

I have a section of code which is supposed to move this image ( id = 'background' ), which I have downloaded locally, and is quite large. It is supposed to move when I hover over top of a certain div(s). This then changes the opacity CSS value, which in turn is detected by the js, which then makes the image move. The js code looks like this:
setInterval(function(){
var element = document.getElementById('background'),
style = window.getComputedStyle(element),
left = style.getPropertyValue('left');
left = left.replace(/px/g, "")
left = parseInt(left,10)
if(getOpacity('rightbar') == .5){
document.getElementById('background').style.left = left - 8 + 'px'
}
if(getOpacity('leftbar') == .5){
document.getElementById('background').style.left = left + 8 + 'px'
}
},10)
The getOpacity(idName) function looks like this:
function getOpacity(param){
var element = document.getElementById(param),
style = window.getComputedStyle(element),
opacity = style.getPropertyValue('opacity');
return opacity
}
So the problem is that, no matter what movement values or setInteveral time I use, the animation always makes out to be laggy. Is there a way to make this smooth with vanilla js, or better yet, to scrap the opacity detection and do it all with CSS?
It works fine when I put the above code in a fiddle, but when it actually runs full browser (on my personal chrome window), it looks like this.
Note: I am running this full browser window on a 4k monitor, is this just too much for chrome to handle?

1. Use requestAnimationFrame instead of setInterval
This signals the browser you want to do something before the next redraw. The callback you provide is executed exactly once per frame.
(In case this matters: requestAnimationFrame does not work in IE9 and below.)
2. Don't increase by a fixed value per frame, tween between values
Both when using requestAnimationFrame and using setInterval, the time difference between frames vary.
You can verify that yourself by using something like this in the developer toolbar:
var last = new Date();
function onFrame(){
var now = new Date();
console.log(new Date() - last);
last = now;
requestAnimationFrame(onFrame);
}
onFrame();
The developer console will output the frame times in ms, like this:
16
17
17
15
19
...
If you increase position (not so noticeable on e.g. opacity) by a fixed amount on vaying intervals, the animation will look jagged. So Instead of doing left = left + 8;, calculate at which position in the animation you are, based on the current time, something like this:
var myElement = ...;
var initialOpacity = 1.0;
var targetOpacity = 0.5;
var duration = 2000;
var startTime = new Date();
function animation() {
var delta = Math.min(1, (new Date() - startTime) / duration);
// delta is now a number in the range [0 ... 1]
myElement.style.opacity = initialOpacity + delta * (targetOpacity - initialOpacity);
if (delta < 1) requestAnimationFrame(animation);
}
requestAnimationFrame(animation);
Yes, this example tweens opacity and not position, but you get the idea - and your teacher can't claim you copy-pasted ;-)
3. Don't read and write back-and-forth between JS and CSS
Assuming the initial position of your image is not viewport-related (say, left: -10%), there is no need to read the position on every frame.
When your JavaScript is the only thing changing the left property, why read it from CSS? Save it in a variable and set it to CSS from your JavaScript.
var initialX = ComputeCssXPosition(myElement);
...
function animate() {
...
myElement.style.left = computedNewXPosition;
}
If you want to change the postition when the user hovers an element, use mouse events in your JS.
myElement.addEventListener('mouseover', function (ev) { ... });
myElement.addEventListener('mouseout', function (ev) { ... });
Alternative: Use CSS transitions
Already covered in the answer by Shomz.

The best approach would be to use requestAnimationFrame instead of setInterval and not to check for the style changes, but use mouseover listeners (too much communication: CSS->JS->CSS->JS...). See here: https://jsfiddle.net/yqpun3eb/4/
However, if you still want to use setInterval, you can simply put the transition CSS rule on your background element. For example:
#background {transition: left 0.2s linear}
That will smooth out all the value changes because CSS performs way better, so it should be fine even on the 4K screens. The problem was your changes can jump by 8 pixels.
Seems to work nice with 0.2s on my machine: https://jsfiddle.net/yqpun3eb/3/
Oh, and btw. you want good performance, but why are you raping the system with this:
function getOpacity(param){
var element = document.getElementById(param),
style = window.getComputedStyle(element),
opacity = style.getPropertyValue('opacity');
return opacity
}
This doesn't create extra variables (which you don't need anyway):
function getOpacity(param){
return window.getComputedStyle(document.getElementById(param))
.getPropertyValue('opacity');
}
Finally, here's a slightly optimized version using requestAnimationFrame (that's how I would do it + use listeners instead of reading the style values): https://jsfiddle.net/yqpun3eb/4/

Related

Slow Scroll Toggle with no jQuery?

I know its a bit to ask, but is the following possible without using jQuery? I have it running with jQuery now but it seems to be presenting performance issues. If you could help I will be most grateful. I am not lazy, just not very code knowledgable. Took me a while to even get this far.
//
// default speed ist the lowest valid scroll speed.
//
var default_speed = 1;
//
// speed increments defines the increase/decrease of the acceleration
// between current scroll speed and data-scroll-speed
//
var speed_increment = 0.01;
//
// maximum scroll speed of the elements
//
var data_scroll_speed_a = 2; // #sloganenglish
var data_scroll_speed_b = 5; // #image-ul
//
//
//
var increase_speed, decrease_speed, target_speed, current_speed, speed_increments;
$(document).ready(function() {
$(window).on('load resize scroll', function() {
var WindowScrollTop = $(this).scrollTop(),
Div_one_top = $('#image-ul').offset().top,
Div_one_height = $('#image-ul').outerHeight(true),
Window_height = $(this).outerHeight(true);
if (WindowScrollTop + Window_height >= (Div_one_top + Div_one_height)) {
$('#sloganenglish').attr('data-scroll-speed', data_scroll_speed_a).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_a * speed_increment);
$('#image-ul').attr('data-scroll-speed', data_scroll_speed_b).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_b * speed_increment);
increase_speed = true;
decrease_speed = false;
} else {
$('#sloganenglish').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
$('#image-ul').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
decrease_speed = true;
increase_speed = false;
}
}).scroll();
});
I don't see any performance issue in your code, although there is space for some optimization. And I don't think jQuery might be the problem.
First thing to notice is the CSS access.
The height attribute is very expensive to access because it causes the browser to process many rendering steps of the pipeline, as you can see in CSS Triggers.
You are retrieving the height of two elements in a scroll event, which means that they will be calculated many times. Is it really necessary?
If your #image-ul element doesn't change its height, maybe you can calculate it outside of the event only once.
In the case of the window height, I believe it won't change in the scroll event. How about to create different handlers, one for the events that need to (re)calculate the window height and another for the events that don't need that calculation?
Another noticeable point is that you set the 'data-current-scroll-speed' and the 'data-speed-increments' attribute always with the same constant value. No change, no unset. Is it really necessary?
Actually, it is not clear what you are really doing. Your performance issue might be somewhere else.

Should I use requestAnimationFrame to fade in several elements? To achieve 60fps animation?

Trying to maintain 60 fps animation. Currently, I'm getting a lot of spikes of slow frames that are happening at like 30~ fps and the choppiness is noticeable to my eye.
Significant Edits: Throwing out old obsolete code, adding in new code with explanation
fadeIn: function(ele){
raf = window.requestAnimationFrame(function() {
console.log(ele);
var opacity = 0;
function increase () {
opacity += 0.05;
if (opacity >= 1){
// complete
ele.style.opacity = 1;
return true;
}
ele.style.opacity = opacity;
requestAnimationFrame(increase);
}
increase();
});
},
fadeInElements: function(elements, properties, speed, delay){
var raf;
var ele;
for (i = 0; i < properties.length; i++){
ele = elements[properties[i]];
console.log('outside loop ' + ele);
instance.fadeIn(ele);
}
},
My new code is above. It is successfully:
Iterating through several elements (each as ele) and then calling fadeIn(ele)
So, all elements fade in.
However, I want a 50ms delay between each "fade in" (each triggering of fadeIn() on a new element
The good news is that it's not actually recursion — it's more like a timeout. You provide a function that draws a frame, and the browser calls it for you.
Here's an answer showing excerpts from a complete JSFiddle. It doesn't try to replicate your exact code, but instead tries to explain what you need to know so you can adapt your code accordingly. The code was written to be easy to understand, so I'm sure there are other ways to do it faster!
This answer works from the top level down, which means I describe the end of the JSFiddle source and work my way backwards. I personally think this makes it easier to understand than does starting with the details.
You need to start the animation somewhere. So the very last thing in the JSFiddle is
window.requestAnimationFrame(eachFrame); //start the animation
This will call a function called eachFrame() when it's time for the next frame, e.g,. on the next multiple of 60 times per second. It will only do it once, though.
You need eachFrame() to keep track of where we are in the animation.
var startTime = -1.0; // -1 = a flag for the first frame.
function eachFrame()
{
// Render this frame ------------------------
if(startTime<0) {
// very first frame (because of the -1.0): save the start time.
startTime = (new Date()).getTime();
render(0.0);
// the parameter to render() is the time within the
// animation.
} else {
// every frame after the first: subtract the saved startTime
// to determine the current time.
render( (new Date()).getTime() - startTime );
}
// Now we're done rendering one frame. ------
//Start the timer to call this function again
//when it's time for the next frame.
window.requestAnimationFrame(eachFrame);
}; //eachFrame
eachFrame() determines what the current time is with respect to the beginning of the animation. getTime() gives you the time in milliseconds.
The other thing eachFrame() does is to call window.requestAnimationFrame(eachFrame); again. This isn't recursion. Instead, eachFrame() will finish running, and then after that, the next time a frame comes around, the browser will call eachFrame() again.
The last function you need is something to actually draw the frame! That is render(current time). Assume that, e.g., head1 and head2 refer to two heading elements you want to animate, e.g., <h1> elements declared in your HTML. The clamp(x) function returns x but clamped below at 0 and above at 1.
function render(currTime)
{ // *** Put your rendering code here ***
// How opaque should head1 be? Its fade started at currTime=0.
var opacity1 = clamp(currTime/FADE_DURATION);
// over FADE_DURATION ms, opacity goes from 0 to 1
// How opaque should head2 be?
var opacity2 = clamp( (currTime-FADE_SPACING)/FADE_DURATION );
// fades in, but doesn't start doing it until
// FADE_SPACING ms have passed.
// Apply the changes
head1.style.opacity = opacity1;
head2.style.opacity = opacity2;
} //render
In render(), you figure out opacity based on the current time. You don't have to worry about delaying between frames, because requestAnimationFrame handles that for us. You stagger the transitions by offsetting the time. In this example, opacity1 depends on currTime and opacity2 depends on currTime minus a constant FADE_SPACING, so the opacity change for element 2 will start later than the opacity change for element 1 by FADE_SPACING ms.
The JSFiddle has all the details filled in. It animates the opacity of two <h1> elements, with a spacing between the beginning of the animation for each element. I hope this helps!

How to animate a style property in plain vanilla javascript?

So for a while I've been moving away from jQuery, and in general just reducing my library use where-every possible to a) write leaner code, and b) really understand at a low level whats going on, particularly around the UI. While I've moved the majority of my UI animation to CSS3, theres often times when you need a little more control, but for a single tiny animation I'd prefer not to always have to pull in velocity.js or greensock etc.
Looking at you-might-not-need-jquery theres a fadeIn function they demostate that looks like this :
function fadeIn(el) {
el.style.opacity = 0;
var last = +new Date();
var tick = function() {
el.style.opacity = +el.style.opacity + (new Date() - last) / 400;
last = +new Date();
if (+el.style.opacity < 1) {
(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16);
}
};
tick();
}
fadeIn(el);
I generally understand this function, but have a few questions on very specific items :
What does the + before new Date() and el.style.opacity on lines 3,5,6 & 8 indicate? is it something like += ?
On line 5, why the division by 400?
Is there anything inherently wrong with this recursive requestAnimationFrame technique for general quick animations?
If I understand the concept behind this pattern :
we set our starting position (force feeding) and time we're beginning,
we then update the style relevant to the amount of time thats passed,
until the final state is satisfied, call tick again, on the next animation frame.
Is this correct?
A unary + is a quick way to force a value to be interpreted as a number.
The division by 400 is the way that code sets the rate of fade-in. A bigger number would make the fade take longer, and a smaller number would make it faster. The number gives the number of milliseconds that will elapse (more or less) before the element is fully opaque.
It's not recursive. The reference to the function is passed to the timer mechanism (either setTimeout() or requestAnimationFrame()) but by the time the timer fires the original call will have exited.

Smooth div width change in javascript

I have a simple function which is increasing the width of a div but its not doing it smoothly its kinda of "juddery".
I'm using request animation frame to do this on Chrome.. and I decided not to round the numbers so I could get decimal width increments.. but I can't get it to be smooth at all was wondering how I can improve on my method.
This is my function:
function test(data){
var start = parseInt(data.start);
var length = parseInt(data.length); //total length in minutes to complete
var dif = (new Date().getTime() / 1000) - start; //start holds seconds since epoch
var minutes = dif / 60; //convert seconds past into minutes past
var percentage = (minutes/length) * 100;
if(percentage > 100){ percentage = 100; }
if( percentage != 100 ){
document.getElementById('r').style.width = percentage+'%';
document.getElementById('rt').innerHTML = Math.round(percentage)+'%';
} else if (percentage == 100 ){
document.getElementById('r').style.width = percentage+'%';
document.getElementById('rt').innerHTML = 'COMPLETED';
}
}
My function is called like this:
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback, element){
window.setTimeout(callback, 200 / 100);
};
})();
function Update(){
requestAnimFrame( Update );
test();
}
JSFIddle: http://jsfiddle.net/RmXr9/7/
Any suggestions on ways to improve the smoothness of div width incrementing ?
try using css transitions. You get way smoother animations, but requires you to structure your code a bit differently. An example of a css transition property is this:
transition:300ms linear;
then whatever property you change (the width for example) will make a smooth linear 300 millisecond transition towards it.
so making a smooth width change is as simple as setting up the transition then doing something like this in javascript:
div.style.width="400px";
Here's a quick example i mocked up:
http://jsfiddle.net/nTMsC/1/
here's a nice tutorial to get you started:
http://www.css3.info/preview/css3-transitions/
One of the biggest causes of 'juddery' animations for me has always been frame rate. If your frame rate is too slow, obviously the animation 'judders'. But if it is too fast for the browser to handle, the browser gets confused, and you get a different kind of 'juddery'.
I'd recommend a frame rate of between 13 and 30 milliseconds. JQuery is supposed to use 13ms, but I've found that that is sometimes still too fast. I generally start with 16ms, and experiment from there.
The key is to ensure that you time it so that one frame starts as or after the previous frame is finished. This will depend on the code you process. I notice that you call the next frame before you begin processing the current frame, so it may be possible that you're still getting backed up. Perhaps try:
function Update(){
test();
requestAnimFrame( Update );
}
Your fallback function has a frame rate of 200 / 100, which is 2ms. It is extremely unlikely that your browser can complete the animation in 2ms, so it is likelyto get backed up. requestAnimationFrame uses a maximum frame rate of 16ms.
UPDATE:
The problem you're having, according to your jsfiddle, is that, while you're calculating your percentage often, the changes to the percentage are very small, and they don't translate into a change in the width of the div. This http://jsfiddle.net/RmXr9/13/ should demontrate the changes in the percentage, and show the corrsponding changes in actual width. So, although you do a calculation often (maybe 60 times a second), the actual visual change only happens once every 16 frames or so. So, your actual frame rate is only about 4 frames per second, which makes a 'juddery' animation. Your only options, I'm afraid, are to make the animation run faster (perhaps by decreasing your length variable), or to make the div much longer (or both).
As an aside, I notice you don't have a way to stop the animation at the end, and I've added that into the jsfiddle as well.

javascript smooth animation from X,Y to X1,Y1

I'd like to sloothly move an image (or an element) from its actual X, Y location to X1, Y1.
When the distance between X and X1 is equal to that between Y and Y1 its easy.
But what if the X difference is say 100px and Y diff is 273px?
Being new to Javascript, I don't want to re-invent the wheel!
Besides, since I'm learning, I do NOT want to use jQuery or the likes. I want pure javascript.
Please supply with simple script :-)
One solution:
function translate( elem, x, y ) {
var left = parseInt( css( elem, 'left' ), 10 ),
top = parseInt( css( elem, 'top' ), 10 ),
dx = left - x,
dy = top - y,
i = 1,
count = 20,
delay = 20;
function loop() {
if ( i >= count ) { return; }
i += 1;
elem.style.left = ( left - ( dx * i / count ) ).toFixed( 0 ) + 'px';
elem.style.top = ( top - ( dy * i / count ) ).toFixed( 0 ) + 'px';
setTimeout( loop, delay );
}
loop();
}
function css( element, property ) {
return window.getComputedStyle( element, null ).getPropertyValue( property );
}
Live demo: http://jsfiddle.net/qEVVT/1/
Doing smooth animation on systems with a variety of different capabilities (CPU, graphics power, other things going on on the computer) is not a trivial task. A proper implementation involves developing an effective "tweening" algorithm that can figure out adaptively (as the animation runs) what increments to be using in the animation in order to stay on schedule and be as smooth as possible.
The best way to do this is to stand on the shoulders of others and use what has been invented before. In this day and age, I would never try to write this myself from scratch. It's there to use in CSS3 transitions/animations, but those aren't yet supported everywhere. It's there to use or analyze in jQuery and YUI3. My first choice would be to use one of the frameworks that has a rich set of capabilities here. You don't have to use the framework for anything else, you can just use it for the animation if you want. YUI3 will even let you construct a library that has the least code in it possible for just what you want. jQuery isn't very big to start with.
If you're still dead set against using one of the libraries, then download the source to the relevant modules for each library and study how they do it. Build a sample app in each and step through how it works, setting breakpoints at interesting spots. That will be the best teacher and show you how to build an effective tweening algorithm that can adapt to the speed capabilities of the host computer.
To give you an idea of how a tweening algorithm works for a straight animation (with linear easing), you make an initial calculation of what you want your animation step value to be for the time you want the animation to run. This is probably just a guess as to what the system can support. You divide the number of steps that creates into the time the animation runs and you set a timer for that amount of time so you know when to run the next step. You then run one or two steps of the animation and you see how much time has actually elapsed. If the computer can't keep up with your step value, you will be behind schedule and you will have to adapt and pick a larger step.
Now, if you want to do something other than linear easing, there's obviously even more involved.
Firefox and Chrome have also implemented some new experiemental APIs to help with smooth animation. I discovered this myself when looking at the jQuery source because it uses it when it's available. In Chrome it's called webkitRequestAnimationFrame and you can read about it here in a Firefox blog post.
If you are targeting modern browsers, CSS transitions make the life easier (Example for firefox, for other browsers, change the -moz prefix):
<body>
<input type="button" onclick="move()" value="press" />
<div id="sq" style="position:absolute; top:50px; left:50px; height:50px; width:50px; background-color:Black; -moz-transition : all 0.8s ease 0s;" />
</body>
And the script
function move() {
var sq = document.getElementById("sq");
sq.style.left = "300px";
sq.style.top = "150px";
}
If I was going to write it from scratch I would start with something like this:
function linearEase(start, end, percent) {
return start + ((end - start) * percent);
}
function animateTo(settings) {
var elem = settings.element;
var ease = settings.ease;
var start = { left: elem.offsetLeft, top: elem.offsetTop };
var lastTime = new Date().getTime();
var timeLeft = settings.totalTime;
function update() {
var currentTime = new Date().getTime();
var elapsed = currentTime - lastTime;
timeLeft -= elapsed;
lastTime = currentTime;
var percentDone = 1 - timeLeft/settings.totalTime;
elem.style.top = ease(start.top, settings.top, percentDone) + "px" ;
elem.style.left = ease(start.left, settings.left, percentDone) + "px" ;
if(timeLeft > 0) {
setTimeout(update, 33);
}
}
update();
}
For example, to move a div to (50,50) over the next two seconds.
var elem = document.getElementById("animatable");
setTimeout(function() {
animateTo({
element: elem,
left: 50,
top: 50,
totalTime: 2000,
ease: linearEase
})
}, 10);
Which is a fairly standard pattern for doing this kind of stuff. Getting the element position and setting the style stuff could be better implemented for sure. But abstracting out an ease function will make your life a lot easier in the long run. I've provided a simple linear ease, but other more complicated easing algorithms would abide by that same interface.
Another thing to note is that timeouts and intervals are not guaranteed to run at a set time, so its usually best to set the total time that you want the transition to take to run, and then figure out how much time has elapsed since the last time you rendered.
Also if you are animating a bunch of elements at once, I would definitely refactor this to a single "render loop". calls to the animateTo would push workers into a queue of workers, but only have setTimeout loop that calculates the time elapsed then invokes each worker, so you don't have a bazillion timeout closures floating around.
Anyway, fiddle here

Categories

Resources