i'm applying a css keyframe animation to an element. i only specify one keyframe (100%) for a simple transform. while the animation is running i pause using the animation playstate and apply a class specifying a different keyframe animation. what i want is that the second animation starts where the first animation was interrupted but instead the element jumps back to its start position and is animated from there. i played a bit with animation-fill-mode but it doesnt change which i think is because the animation was interrupted before it reached 100%. any ideas what i could do to make this work?
I was actually brainstorming this a couple of days ago. You are correct in assuming that your issue is the result of your animation not reaching 100%. The problem is that there is no way to simply select the values indicating how far your animation made it in the animation. From this, you have the following three options (note: I have not tested all of them):
You can break the animation into defined steps (10% intervals, 20% intervals, etc.) and then only pause it on one of the steps. This is probably the safest solution, but you will likely have to base it on time (i.e., setInterval, etc. - Yuck!)
You can calculate (also based on time - Double Yuck!) where the element is, using JavaScript, and set up a new keyframe accordingly
You can try to look at the element's properties at the time that the animation is paused (I have not tried this and I highly doubt it will work)
http://jsfiddle.net/gxve9/1/
Animations don't actually change the css, they just animate it and then put it back where it was. You just have to use javascript to save the css using something like
var prevWidth = $("div").css("width");
and then after pausing the animation, set it with
$("div").css("width",prevWidth);.
That way it stays permanently set where the first animation put it.
You have a few options here:
Append the new animation to the list with a different play state. This is not very scalable but works for some cases.
Capture the animated value using getComputedStyle and apply it yourself. The disadvantage of this is that for transform, the value returned by getComputedStyle is converted to a matrix() value so the interpolation for rotate animations will sometimes behave differently.
Use commitStyles() to do it for you. The trouble with this is browser support. It should work in the latest Firefox and Safari, and next version of Chrome (works for me in Canary).
You can see each approach demonstrated here: https://jsfiddle.net/birtles/8bv5of6n/14/
Related
Introductory information:
I've made a fixed menu button to show the navigation menu when using a mobile device. For this application I'm using the Headroom.js script to make the button smaller when scrolling downwards to ensure that it doesn't block too much of the content. The animation/transition is applied by adding a class with the given changes.
In the original method i changed the size and look of the button by changing height/width of the parent element and padding of the child element with CSS (and css transition).
The new method, which I've read could/should be better according to various sites, is changing the size of the button by using transform: scale(). Note that i'm also moving the element a bit by also applying translate3d(20px,20px,0) in this method. However, it feels a bit smoother when scrolling using the transform: scale() method (could be a placebo effect though), but using chrome dev tools' timeline gives me seemingly inconclusive results.
Therefore a part of my question is also how I should evaluate the best method. Is timeline in Chrome Dev tools the best option, or is there a better way to do it? And which elements of the timeline should I base my choices on? and the other thing is, based on your interpretation of the images and/or tests combined with your knowledge, which method performs the best (or should perform the best in theory)?
Beneath you can see two examples of the timeline with each method.
Changing height/width and padding (original method):
Method using transform: scale() to change the size:
Also you can try the different methods in fiddles here:
link: Original method changing height/width and padding
link to new method: using transform:scale
Please ignore the poor layout of everything; especially the button. The ugly image inside the menu button is just to show, that there's an image included in the layout on my own page and to take that into performance considerations. The images in the back is also included since it's a webshop with a lot of images which could influence performance.
CSS for added class that makes the changes in the original method:
.mobile-nav.headroom--unpinned {
height: 40px;
width: 40px;
}
.headroom--unpinned .mobile-content{
padding-top:4px;
}
CSS for the added class using transform:scale():
.mobile-nav.headroom--unpinned {
transform:scale(.5) translate3d(20px,20px,0);
}
So to summarize my questions:
How do I evaluate which methods has the best performance, and which method would you say performs the best?
A final note: I know that the methods are different (animating different things and more elements in the original method) but these are the 2 options which i prefer as it is right now.
I believe you are missing the point, Chris: the reason why no other property but transform and opacity should ever be animated is because they don't trigger a repaint in anything else, even if the element is in the document flow (and because you can basically do anything with these two alone in like 95% of the cases).
From the "hit-on-performance" point of view, there are two types of animations:
those that trigger a repaint in other elements than the animated element
those that do not.
That's the main reason behind recommending animations by transform, opacity or position:relative;left|right|top|left. Because they don't actually move the element in flow, thus not triggering a repaint to every single other element in flow after the one being animated.
Now, if the said parent was positioned absolute (which I assume to be the case), it wouldn't have triggered a repaint to the rest of DOM anyway so the differences between that method and transform would have been minor. Inconclusive, as you put it. In theory, repainting two elements instead of one should be slower.
If you need to test, make 10k clones and trigger animation on all of them, with each method.
That will be conclusive.
If you really want to min-max this (as in spend absurd amounts of time on hardly noticeable improvements, as I do) you will find plenty of resources that will recommend:
replacing any .animate() with .velocity()
never animating anything but transform or opacity, although Velocity claims they animate anything without a hit on performance (i find that debatable/arguable, at best) - but it's a net improvement over .animate()
sticking to CSS transitions, if possible (basically if you don't need chains)
using Web Animations API
Personal advice: never count on synced CSS animations, especially when you have many of them. If you change tabs or the system does something extremely resource heavy for a while, your animations will be way off. If you need chains, chain.
I'd been trying to render a CSS animation but affected by external dynamic values handled by JavaScript, so now I'm wondering if there's any way to move step by step throw the keyframes of a CSS animations.
You can force an animation to start partway through by setting a negative animation delay.
(You would need to calculate the appropriate time value for the keyframe you want to display.)
Perhaps if you also set the animation state to paused, the animation would appear at the specified position, but would not move. I haven't tried it though!
I'm currently making a little navigation menu. In one of those dropdown menus, I need one category to expand. It works. But, when category retracts with the help of jQuery .hide('slow'), the menu I want to hide blinks once at the end of the hide animation, and then is properly hidden.
Here's some code below to illustrate my problem :
$(document).ready(function(){
var verif = false;
$("a").hover(function() {
var texte = $(this).text();
var tab = new Array;
tab = texte.split(" ");
if (tab[0] === "+ Deroule" && verif === false) {
$("#submenu").show("slow");
verif = true;
}
else if (tab[0] === "+ Deroule" && verif === true) {
$("#submenu").hide(8000);
//There, I can clearly see, at the end of the hide animation, the previously opened content blinking once before it hides completely.
verif = false;
}
});
});
Without seeing your HTML/CSS it's hard to actually pin the problem down - hint hint create a fiddle hint hint :) - but the first thing that I would do, if I were you, is change the .hover() method to .mouseenter() and .mouseleave() (NOT .mousein() and .mouseout() - that's another conversation about IE support - you can/should look that up on jQuery's site, if you're not familiar).
Why do this? The .hover() method is actually just shorthand for .mouseenter()/.mouseleave() but, for various reasons (including the fact that it is not as widely supported as the two full methods), it occasionally fails to work properly. I cannot guarantee that this is the problem in this situation, but, especially since this appears to be a browser issue, it is always best to go back to basics and start with as much support as possible.
EDIT:
After seeing your simplified fiddle, I think I have a better understanding of what your issue is and what you're trying to do. I have had the same issue before and, from my experience, I can tell you that it is actually a difficult issue to account for.
Elements jump when they are shown/hidden using the .show(), .hide(), slide, and fade methods because as soon as the animation completes they are removed from the flow (in most browsers - not chrome - the show/hide methods do not cause the element to shrink all the way to nothing, so there is still choppiness). Here are two of my most recent solutions to this issue:
In one situation, I was tasked with creating view-more and view-less buttons inside a menu, which would make elements appear/disappear SMOOTHLY one at a time. If you simply try to call a show/hide, slide, or fade method on the collection to be shown, they will all animate at the same time, and the same thing happens when trying to make them run on callback. Additionally, sliding methods wouldn't work because this animation was going to be called on multiple items at a time, thus causing a lot of overhead and making the effect choppy in slower browsers (and even a bit in FF). The show/hide methods worked in some browsers, as they actually do reduce the size of the element, but they are choppy in others so they were out (effectively, their animations don't "complete" in all browsers - see above). Thus, I was left with three options: use fade, animate, or a combination of both.
Solution 1:
The original plan, as it was (with the given specs) the best solution at the time, was to use the animate method to simply slide a wrapper div (with overflow set to hidden up or down, based on the offset of a target element in the list). This worked great, but I was then given the requirement of having each element disappear in succession, via a fade effect. If you do choose this method, then I suggest that you instead use slide methods - as you are not attempting to show view more/less links, this shouldn't be a problem (yes, I know that this probably doesn't sound like it would be an issue for view more/less links, but the way I had to implement it, slides actually did cause an issue).
Solution 2:
Using fade on its own was an issue because everything was still going to fade at the same time and, even if they didn't, the animation would still jump once the fade finished. Thus, the first issue I needed to solve was the animations running at the same time. Using a timer was out of the question because of the overhead, so I wrote a function that recursively called the fade on each element in the collection after calculating the necessary delay between calls (based on a supplied speed argument, and the number of elements to be faded). Next, I still had to solve the jumping issue once the animations were complete. To fix this, I implemented the .fadeTo() method, thus keeping the elements in the flow of the document after their animations. This did, however, cause two more issues:
The .fadeTo() method is not supported in IE (it may be in 9 and 10 - I can't remember)
After the animation, there was a lot of empty space due to the elements not being removed from the flow
To remedy the first issue, I added a browser-specific method for IE, which set the display property of each element to block and their visibility to hidden once their fade-outs were completed - it also did the reverse just before their fade-ins were started.
I solved the second issue by using the animation method that I described previously. To make this work, I had to write another auxiliary method that calculated the speed at which the container needed to slide, based on a given speed of the animation and the number of elements being shown/hidden.
I know that this was a lot of detail, but I hope it helps you determine how best to go about this.
Let me know if you have any questions or need any clarification when you move forward with your implementation. Good luck! :)
When running a Jquery animation like slideDown(), it looks like a number of element-specific css properties is set to be updated at a specific interval and when the animation is complete these properties are unset and the display property is simply set to auto or whatever. At least in firebug you can't see those temporary properties any more.
The problem I've encountered is the scenario where we stop the slide down with stop(). The element is then left with the current temporary css values. Which is fine because it has to, but let us say that I stoped the slidedown because I have decided to slide it back up again a bit prematurely. It would look something like this:
$(this).slideDown(2000)
//The below events is not in queue but will rather start execute almost
simultaneously as the above line. (dont remember the exact syntax)
$(this).delay(1000).stop().slideUp(2000)
The above code might not make much sense, but the point is:
After 1 second of sliding down the animation is stopped and it starts to slide back up. Works like a charm.
BUT!!! And here is the problem. Once it it has slid back up the elements css properties are reset to the exact values it had 1000ms into the slideDown() animation (when stop() was called). If we now try to run the following:
$(this).slideDown(2000)
It will slide down to the very point the prior slideDown was aborted and not further at half the speed (since it uses the same time for approximately half the height). This is because the css properties were saved as I see it. But it is not especially wished for. Of course I want it to slide all the way down this time.
Due to UI interaction that is hard to predict everything might soon break. The longer animations we use increases the risk of something like this happening.
Is this to be considered a bug, or am I doing something wrong? Or maybe it's just a feature that is not supported?
I guess I can use a callback function to reset the css properties, but depending on the animation used, different css properties are used to render it, and covering your back would result in quite a not-so-fancy solution.
You could try to replace the slideUp and slideDown with animate.
http://api.jquery.com/animate/
This way, you are explicitly telling it what to do.
This is the expected (though not desirable) behavior...to get the .slideDown() to go to the full height, start the slides from the finished position, by using .stop(true, true), so the animation completes. The second true argument, telling it to skip to the end of the animation is the important part here, so those "final" values it slides back to are the full height, etc...rather than the height it was at when it stopped sliding.
This was a bug that has been fixed in jQuery 1.7.2: http://bugs.jquery.com/ticket/8685
I have the following working code to make a element of my scrollbox visible:
var next = elements.item(i+1);
var xpcomInterface = scroll.boxObject.QueryInterface(
Components.interfaces.nsIScrollBoxObject);
xpcomInterface.ensureElementIsVisible(elements);
But I would like to make a smooth-scroll (slow or not). Any idea how to do it?
update
By the way, it's for Mozilla environment.
The simplest way would be to just use a setTimeout function and just keep moving the top and left value on the element's div by a small number, until you get where you want to be.
You may want to experiment with how fast to make it move, as there is a trade-off with smooth, and the fact that it should reach the endpoint in some reasonable time.
Update:
I forgot, you will want to keep calling the setTimeout until you reach the final destination, otherwise it won't redraw the browser window.
I don't know exactly how to do it, but here's a Chrome Extension called "Smooth Scroll" where you can potentially pick through the code to see what they do and maybe get you in the right direction.
PS I love this extension.
jQuery .animate() with accelerated motion can be kind of smooth (see demo for animate with a relative move). And with a plug-in, there are other easing equations it can use.