Considerations for CSS3 Transition Performance - css

As part of a project that needs to support mobile devices, I have been working on mimicking the iPhone toggle control using CSS3. I have the look and feel of the element pretty much there, and am using CSS3 transitions to animate its state change.
When I have the element itself on a page with nothing else, the transition is relatively smooth on iOS. However, when I combine it with other CSS elements on a page, the result in iOS is laggy as anything. It's slightly better than a raw jQuery animation, but not much.
I've set up two test pages to demonstrate what I mean (the difference is hardly noticeable in a regular browser):
Toggle Control on its own > http://ben-major.co.uk/labs/iPhone%20UI/ios_toggle.html
Combined with other elements > http://ben-major.co.uk/labs/iPhone%20UI/
I am looking for any advice on speeding up the transition in mobile devices. What could be the factors that are slowing down its performance on the full page test?
Any advice and comments welcome.

You have to be careful with this, as it can alter the z-index of the element it's applied to, but adding:
-webkit-transform-style: preserve-3d;
To the element you're applying the transition to, can speed animation up considerably, as it forces the hardware to use hardware acceleration for the animation.
If you do encounter layout bugs, you can just switch your 2d transitions to 3d values, so:
-webkit-transform: translate(100px, 100px)
becomes:
-webkit-transform: translate3d(100px, 100px, 0px)
You can see a demo of how this helps speed things up, at http://stickmanventures.com/labs/demo/spinning-gears-Chrome-preserve-3d/#
If after applying this to the element, you see it or elements around it blink upon use, then use:
-webkit-backface-visibility: hidden;
To the element, and that should correct the problem.
These tips have helped me to produce fast, efficient CSS transitions, hope they help. :)

Chrome has recently improved the 2D transition performance, and now this trick is no longer needed. The best thing is that if removed the translate3d you'll no longer have those z-index problems! Use the test to prove. http://stickmanventures.com/labs/demo/spinning-gears-Chrome-preserve-3d/

also you can try will-change: transform; , read more about it here:
https://developer.mozilla.org/en-US/docs/Web/CSS/will-change#Browser_compatibility

I think it quite old already but for anyone who still needs tricks to improve the transition performance on mobile device, you can apply :
-webkit-transform: translateZ(0);
to the element you are animating.
This trick is according to this blog : http://chrissilich.com/blog/fix-css-animation-slow-or-choppy-in-mobile-browsers/.
I have tried and it works quite well.

Related

CSS hardware accelerating triggering GPU crash

On the webapp I'm working on me and my team have some users who is experiencing browser crashes when they're using our app. They are then forced to restart the browser. The prolem occurs in all browsers on different OS's. We havent found any plugins in the browsers that could affect this issue. We have been tracking our own logs, windows logs etc. without any findings that could relate to the problem. We also watched the CPU- and Hardrive usage when we succeeded to trigger the problem ourself. But nothing was of the chart. This have made me and my team suspicious that it is GPU were having problem with.
We made some research and found out that animation, transition, opacity, transformation in CSS can trigger CSS crashes. But we don't have the knowledge nor experience to conclude that it's that who is the problem.
We are using hardware accelerating CSS like:
transform: translate3d(0,0,0) translateZ(0);
and some we're also using some CSS rotating keyframe animations:
#keyframes rotate {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
}
}
On some of the users who are experiencing the browser crashes we've implemented a feature switch. The feature switch removes all transitions, transforms and keyframe animations. They have been telling us that they think the feature switch was a improvement. But we can't tell if it's just placebo or if it really did help.
So before we remove a bunch of transtions and transforms from the app I thought it was worth checking out if there are anyone else experiencing the same problems. All thoughts and ideas are welcome.
It's worth mentioning that our webapp is a one-page-app using knockout.js
I've faced same kind of trouble which was crashing iOS - Safari browser. I wrote some note that time which is pasted in below -
Don't use all in transition property, only use which property you want to animate (avoid transition: all 400ms, try transition: color
400ms, background 400ms)
Use keyframe animation as much as you can
Decrease the size of document
Reduce request and optimize your images
Use translate3D, which will force iOS to use 3D acceleration and better memory allocation.
If you are still out of luck, remove all the transition for iOS and
animate using JS, i know it's expensive when you can't use library
like TweenMax but this is the only solution.
Note: JS animation may affect your animation frame-rate. Using rAF
(request frame animation) may enhance a bit.
*** CSS3 animations are very much in-need for website where animation amount is less and it does output ~60 FPS, but JS does vary based on
device.
Hope you found it useful. I would suggest you to stick with new feature what you've, just try fix it.
Best of luck.

How to use and how works CSS' will-change property?

I found CSS will-changeW3.org docs, MDN docs property (which already works in Chrome and is partiali supported by Firefox and Opera) but I'm not really sure how it works.
Does anybody know something more about this mysterious thing?
I have just read that it allows browser to prepare for calculation over the element in the future.
I don't want to misunderstand it. So I have a few questions.
Should I add this property to the element class or its hover state?
.my-class{
will-change: 'opacity, transform'
}
.my-class:hover{
opacity: 0.5
}
.my-class:active{
transform: rotate(5deg);
}
OR
.my-class{
...
}
.my-class:hover{
will-change: 'opacity'
opacity: 0.5
}
.my-class:active{
will-change: 'transform'
transform: rotate(5deg);
}
How can it increase a browser performance? Theoretically, when CSS is loaded, the browser already "knows" what will happen with each element, doesn't it?
If you can add any good example illustrating how to use it efficiently, I will be grateful :)
I won't copy paste the entire article here but here's a tl;dr version:
Specifying what exactly you want to change allows the browser to make better decisions about the optimizations that it needs to make for these particular changes. This is obviously a better way to achieve a speed boost without resorting to hacks and forcing the browser into layer creations that may or may not be necessary or useful.
How to use it:
will-change: transform, opacity;
How not to use it:
will-change: all;
.potato:hover {
will-change: opacity;
opacity:1;
}
Specifying will-change on hover has no effect:
Setting will-change on an element immediately before it changes has
little to no effect. (It might actually be worse than not setting it
at all. You could incur the cost of a new layer when what you’re
animating wouldn’t have previously qualified for a new layer!)
I spent some time on searching how will-change property works and how we use it. I hope, it will be useful summary. Thanks everybody for answers.
1. Layers Hack / Null Transform Hack
In 'ancient times' (like 2 years ago) somebody discovered that you can draw your CSS animation faster.
How does it work?
If you add transform: translateZ(0) to a css selector, it will force a browser to move the element with this selector to the new compositor layer. What is more, it will increase performance (in most situations, use powers of GPU instead CPU) read more here.
2. Bye Bye hacks, welcome "will-change:"
Probably, it's too early to say bye bye to Layer Hack, but this time will come shortly.
The new property will change appeared in specs of CSS and will be a great successor of layer hack.
3. Browser support
For now, it's available in Chrome and Opera and partially supported by Firefox as well.
4. How to use it properly
Don’t use will-change anywhere in your CSS until after you will
complete implementing your animations. Only then should you go back to
your CSS and apply will-change. More
This is probably the most valuable advice that you can get.
There is no point to use it straight before an action begins by e.g. adding it to the :hover state of a selector. Because browser will not have required time to prepare optimization before change occurrence. Browser will need approx. 200ms to apply optimization, so for example it is better to add will-change to a element when a parent element is on hover state. More
Example:
.parent:hover .change{
will-change: opacity;
}
.change:hover{
opacity: .5;
}
You need to use it really sparingly. If you want to optimize everything, the results will be opposite than expected ones. will-change forces browser to keep optimization turned on and reserve resources like memory for changes in the future which may never happen. It is recommended to turn off will-change afterwards, when it is not necessary anymore e.g. when the animation is finished.
You can do it easily using JavaScript document.getElementById('my_element_id').style.willChange = off;
Now with the help of CSS you can do various of animations, and sometimes this animation becomes a bottleneck for a CPU. So instead of doing this job on a CPU, it would be nice to use GPU for this task. And not so long ago people started to use a trick to do this for a 2D transformation by doing something like:
.accelerate {
-webkit-transform: translate3d(0, 0, 0);
}
This made a browser think that it is doing a 3D transformation, and because all 3D transformations are using GPU, this will offload CPU. This looks hacky (because in fact it is) and will-change property is going to change this by informing the browser to look out for changes in the future, and optimize and allocate memory accordingly.
Based on W3C draft,
The will-change CSS property … allows an author to inform the UA ahead
of time of what kinds of changes they are likely to make to an
element. This allows the UA to optimize how they handle the element
ahead of time, performing potentially-expensive work preparing for an
animation before the animation actually begins.
Good idea to use will-change is to let the browser know in advance what changes on an element can be expected. This allows the browser to make the proper optimizations in advance, which leads to quicker rendering.
This information was taken from this excellent explanation about will-change property. The article has additional example when it is nice to use it and when it is useless or even detrimental
As far as I know...
It is an alternative for translate-z:0.
I dont know about hover, but afaik its best to use it on properties that are being changed gradually by JS, changing opacity, position during scrolling etc.
This property shouldnt be overused, especially on phones, tablets, using this on too many
elements can cause performance issues.
It is encouraged to remove/turn-off this property by JS when it is no longer relevant.
So example usage would be applying that at some point of scroll, with scrollTop:400, then gradually animate opacity and at lets say scrollTop:500, disable will-change again.
Source: shoptalkshow podcast - they mention this article - https://dev.opera.com/articles/css-will-change-property/ - which is probably better source of info than my post :D
Thanks to will-change CSS property we can declare to the browser our intention to change an element’s:
contents,
scroll-position,
various tag properties like transform or opacity,
declare multiple values at once: will-change: transform, opacity, top;
or removing the special optimization, using the value auto.
This allows the browser to schedule the use of GPU/Hardware Acceleration instead of the standard CPU usage.
But we have to use it wisely. We need:
to give this to an element that will change,
assign it early early enough before the change occurs,
remove it from the element that has changed and will not be anymore.
Note from MDN web docs:
will-change is intended to be used as a last resort, in order to try to deal with existing performance problems. It should not be used to anticipate performance problems.
They also mention that if we decide to use the will-change property, we should not set it rigidly in the CSS style sheet, because it probably will cause the browser to keep the optimization in memory for much longer than it is needed... It is better to use this property directly from JavaScript (there is also an example with that).
A good additional resource to explore this topic deeper: Everything You Need to Know About the CSS will-change Property.
I'm using react-window package and there is a "will-change: transform;" property on the outer div. The package dynamically render parts of a large set of data in the visible view according to the scroll position, for example only render 10 items of a 100000 length list. Without the will-change property, the list will be blank when scroll. It shows better on slower cpu.
You can see the example here: react-window-fixed-size-list-vertical. Open the developer tool , uncheck this property in styles, slow down the cpu in Performance tab and scroll the list rapidly.list blank between renders
There is also a discussion.https://github.com/bvaughn/react-window/issues/47
The best solution for me:
transform : translate(calc(-50% + 0.5px), calc(-50% + 0.5px));
But, this solution has trouble with calc in ios safari in a fixed position can cause high battery consumption

CSS Transition fallback based on "smoothness"

I've a web application (HTML/CSS/JS) that "transitions" between different screens - the screen are reasonably complex - having many elements on them. Actually, it's very like http://beta.usatoday.com/ except it goes up and down as well as left to right. (And it is probably slightly less complex).
So I'm using CSS3 transitions to manage the sliding between the views. On my machine they are smooth and beautiful, and the application looks really good. However, on older machines, the experience is much less satisfying. e.g. a Core2Duo laptop with integrated graphics - around 3 years old. On this, I get really jumpy transitions, and the transitions take a really long time. They are set with a duration of 0.3s, however, on the older machines, they take 4/5 seconds.
So my questions are:
What can I do to improve the smoothness on older hardware?
If I can't, is there a way to fallback based on the hardware (or by measuring the transition actual duration) so I can just set position?
For some browser versions and mobile browsers there's a bug in webkit that makes transitions very laggy. One of the fixes that's worked for me is to add:
-webkit-transform: translate3D(0, 0, 0);
Another solution had to do with how backface-visibility was rendered during transitions. The fix for that was to add the following, even if the backface wasn't previously altered.
-webkit-backface-visibility: hidden;
Without seeing your code it's hard to say whether these will help, but give em a try (and obviously sub in the appropriate prefix for the browser).

backside-visibility not working in IE10 - works fine in webkit

I'm building a simple pure-css 'card flip' animation, it has to work in IE10, but sadly what I've written doesn't.
jsFiddle demo here or sample html zip here
I can see that backside-visibility works in IE10 from their demo here so maybe I've just overlooked something stupid, maybe a fresh pair of eyes might help!
Thanks in advance!
Well some Microsoft IE devs saw my tweet and jumped in with a fix already!
Apparently IE10 does not support preserve-3d, and they whipped up this jsFiddle demonstration
Big thanks to #reybango and #sgalineau for the help - very much appreciated.
This seems to be a duplicate of CSS3 - 3D Flip Animation - IE10 transform-origin: preserve-3d workaround
The backside-visibility is working on IE10 when it is applied to the element itself (if applied to the parent, it will not work). You should combine it in the same transform property like this:
.back{
transform : perspective(1000px) rotateY(0deg);
}
.front{
transform : perspective(1000px) rotateY(180deg);
}
I only have the backface-visibility applied to the child element, and IE10 shows the backface anyway. Removing preserve-3d defeats one of the main visual features of 3d animation, so it's not really a viable workaround.
Unfortunately the demo referred to above by #reybango and #sgalineau changes the effect appearance from a 3d rotation to a simple 2d change in width, so it's not a viable workaround either.
Bottom line is that IE10 needs to be upgraded to support the CSS 3d animation spec as written.
(This is to comment on why microsoft's 360° turn example works.)
First take a look at the example itself, MS's workaround removed the preserve-3d transform-style property from initial code.
Turns out IE10 has no support for preserve-3d, and they suggest such workaround on msdn:
http://msdn.microsoft.com/en-us/library/ie/hh673529%28v=vs.85%29.aspx#the_ms_transform_style_property
With transform-style set to default 'flat' value, child elements will inherit parent rotation. Thus both card front/back are rotated to 360° (= 0°), the trick here is that back side will appear on top, because it comes later in DOM.
Just to make this a bit more apparent, I added opacity:0.5 to back-side for both examples, now you can see what's really going on:
http://jsfiddle.net/7FeEz/12/
http://jsfiddle.net/ax2Mc/71/
So the MS way will work in some scenarios, but not all without real support for preserve-3d
Here is my solution. http://jsfiddle.net/UwUry/531/
I tried on IE 11 and Chrome. It worked like a flip box.

iPad Safari scrolling causes HTML elements to disappear and reappear with a delay

I'm currently developing a web application using HTML5 and jQuery for iPad Safari. I'm running into a problem wherein large scroll areas cause the elements that are offscreen to appear after a delay when I scroll down to them.
What I mean by that is, if I have a row of images (or even a div with a gradient) that is offscreen, when I scroll down (or up) to it, the expected behavior is for the element to appear on screen as I am scrolling to it.
However, the element does not appear until I lift my finger off the screen and the scroller finishes all its animations.
This is causing a super noticeable problem for me, making the whole thing look choppy, although it is not. I'm guessing the iPad Safari is trying to do something to save memory. Is there a way in which I can prevent this choppy-ness from happening?
Additionally, what is iPad Safari actually trying to do?
You need to trick the browser to use hardware acceleration more effectively. You can do this with an empty three-dimensional transform:
-webkit-transform: translate3d(0, 0, 0)
Particularly, you'll need this on child elements that have a position:relative; declaration (or, just go all out and do it to all child elements).
It is not a guaranteed fix, but it is fairly successful most of the time.
Hat tip: iOS 5 Native Scrolling–Grins & Gotchas
I was using translate3d before. It produced unwanted results. Basically, it would chop off and not render elements that were offscreen, until I interacted with them. So, basically, in landscape orientation, half of my site that was offscreen was not being shown. This is a iPad web application, owing to which I was in a fix.
Applying translate3d to relatively positioned elements solved the problem for those elements, but other elements stopped rendering, once offscreen. The elements that I couldn't interact with (artwork) would never render again, unless I reloaded the page.
The complete solution:
*:not(html) {
-webkit-transform: translate3d(0, 0, 0);
}
Now, although this might not be the most "efficient" solution, it was the only one that works. Mobile Safari does not render the elements that are offscreen, or sometimes renders erratically, when using -webkit-overflow-scrolling: touch. Unless a translate3d is applied to all other elements that might go offscreen owing to that scroll, those elements will be chopped off after scrolling.
(This is the complete answer to my question. I had originally marked Colin Williams' answer as the correct answer, as it helped me get to the complete solution. A community member, #Slipp D. Thompson edited my question, after about 2.5 years of me having asked it, and told me I was abusing SO's Q & A format. He also told me to separately post this as the answer.
#Colin Williams, thank you! The answer and the article you linked out to gave me a lead to try something with CSS. So, thanks again, and hope this helps some other lost soul. This surely helped me big time!)
Targeting all elements but html: *:not(html)
caused problems on other elements in my case. It modified the stacking context, causing some z-index to break.
We should better try to target the right element and apply -webkit-transform: translate3d(0,0,0) to it only.
Sometimes the translate3D(0,0,0) doesn't work. We can use the following method, targeting the right element:
#keyframes redraw{
0% {opacity: 1;}
100% {opacity: .99;}
}
/* iOS redraw fix */
animation: redraw 1s linear infinite;
When the translate3d doesn't work, try to add perspective. It always works for me
transform: translate3d(0, 0, 0);
-webkit-transform: translate3d(0, 0, 0);
perspective: 1000;
-webkit-perspective: 1000;
Increase Your Site’s Performance with Hardware-Accelerated CSS
Adding -webkit-transform: translate3d(0,0,0) to an element statically doesn't work for me.
I apply this property dynamically. For example, when a page is scrolled, I set -webkit-transform: translate3d(0,0,0) on a element. Then after a short delay, I reset this property, that is, -webkit-transform: none
This approach seems to work.
Thank you, Colin Williams for pointing me in the right direction.
I had the same issue with iscroll 4.2.5 on iOS 7. The whole scroll element just disappear.
I've tried to add translate3d(0,0,0) as was suggested here. It has solved the problem, but it disabled the iscroll "snap" effect.
The solution came with giving the "position:relative; z-index:1000;display:block" CSS properties to the whole container that holds the scroll element and there isn't any need to give translate3d to child elements.
I had the same issue using an older version of Fancybox.
Upgrading to v3 will solve your problem or you can just add:
html, body {
-webkit-overflow-scrolling : touch !important;
overflow: auto !important;
height: 100% !important;
}
At time translate3d may not work. In those cases perspective can be used
transform: translate3d(0, 0, 0);
-webkit-transform: translate3d(0, 0, 0);
perspective: 1000;
-webkit-perspective: 1000;
I'm pretty darn sure I just solved this with:
overflow-y: auto;
(Presumably just overflow: auto; would work too depending on your needs.)
There are cases where a rotation is applied and/or a Z index is used.
Rotation: An existing declaration of -webkit-transform to rotate an element might not be enough to tackle the appearance problem as well (like -webkit-transform: rotate(-45deg)). In this case you can use -webkit-transform: translateZ(0px) rotateZ(-45deg) as a trick (mind the rotateZ).
Z index: Together with the rotation you might define a positive z-index property, like z-index: 42. The above steps described under "Rotation" were in my case enough to resolve the issue, even with the empty translateZ(0px). I suspect though that the Z index in this case may have caused the disappearing and reappearing in the first place. In any case the z-index: 42 property needs to be kept -- -webkit-transform: translateZ(42px) only is not enough.
This is a very common problem faced by developers and that is mainly due to Safari's property of not recreating elements defined as position : fixed.
So either change the position property or some hack needs to be applied as mentioned in other answers.
Issues with position fixed & scrolling on iOS
Change to position fixed on iOS Safari while scrolling
In my case (an iOS PhoneGap app), applying translate3d to relative child elements did not resolve the issue. My scrollable element didn't have a set height as it was absolutely positioned and I was defining the top and bottom positions.
Adding a min-height (of 100 pixels) fixed it for me.
I faced this problem in a Framework7 and Cordova project. I tried all the solutions above. They did not solve my problem.
In my case, I was using more than 10 CSS animations on the same page with infinite rotation (transform). I had to remove the animations. It is OK now with the lack of some visual features.
If the solutions in other answers do not help you, you may start eliminating some animations.
The -webkit-transform: translate3d(0, 0, 0); trick didn't work for me. In my case I had set a parent to:
/* Parent */
height: 100vh;
Changing that to
height: auto;
min-height: 100vh;
solved the issue in case someone else is facing the
same situation.
In my case, CSS did not fix the issue. I noticed the problem while using jQuery re-render a button.
$("#myButton").html("text")
Try this:
$("#myButton").html("<span>text</span>")

Resources