Hardware Accelleration Causes Flicker on Css Animation

CSS3 transitions bring simple and elegant animations to web applications, but in that location'due south a lot more to the spec than first meets the eye.

In this post I'm going to delve into some of the more than complicated parts of CSS transitions, from chaining and events to hardware acceleration and animation functions.

Letting the browser command animations sequences allows it to optimize functioning and efficiency by altering the frame charge per unit, minimizing paints and offloading some of the piece of work to the GPU.

Browser support

CSS transitions are supported in practically every version of Firefox, Safari and Chrome. They're supported in IE 10 and onwards. If CSS animations aren't supported in a given browser, than the properties will exist practical instantly, gracefully degrading.

Webkit based browsers (Safari and Chrome), still require -webkit prefixes for animations and gradients, but these are soon being removed.

Applying transitions

A uncomplicated way of applying transitions is with CSS pseudo-classes, such as :hover. Discover we're specifying the belongings proper noun, the length of transition, and 1 of the default timing functions, linear [demo].

                .chemical element {   height: 100px;   transition: meridian 2s linear; }  .element:hover {   height: 200px; }                              

When the :hover pseudo-form is activated, the tiptop volition be transitioned linearly from 100px to 200px over a period of two seconds.

elapsing is the only required item in the transition shorthand. The browser defaults to a timing role of ease, and a holding of all, unless these are provided.

Nosotros don't desire to exist restricted to using psuedo-classes when it comes to activating transitions - clearly that'southward not very flexible. The solution is to programmatically add and remove classes [demo].

                /* CSS */ .chemical element {   opacity: 0.0;   transform: calibration(0.95) translate3d(0,100%,0);   transition: transform 400ms ease, opacity 400ms ease; }  .element.active {   opacity: 1.0;   transform: scale(ane.0) translate3d(0,0,0); }  .element.inactive {   opacity: 0.0;   transform: scale(1) translate3d(0,0,0); }  // JS with jQuery var agile = office(){   $('.element').removeClass('inactive').addClass('active'); };  var inactive = part(){   $('.element').removeClass('active').addClass('inactive'); };                              

In the example above, nosotros've got 2 different transitions, the element slides up when activated, and fades out when deactivated. All the JavaScript does is toggle the two classes active and inactive.

Transitioning gradients

Not every CSS property can be transitioned, and the bones dominion is that you tin only transition through absolute values. For example, y'all can't transition between a height of 0px to auto. The browser can't calculate the intermediate transition values, then the property alter is instant. Oli Studholme has conveniently provided a full list of transition support properties.

The other major belongings that tin't exist transitioned between is background gradients (although pure colors are supported). In that location'southward no technical reason behind this limitation, it's just taking a while for the browsers to implement support.

In the mean time, there are a few skillful workarounds. The kickoff involves adding a transparency to the gradient, and so transitioning between groundwork colors. For example [demo]:

                .panel {   background-colour: #000;   background-image: linear-gradient(rgba(255, 255, 0, 0.4), #FAFAFA);   transition: background-color 400ms ease; }  .panel:hover {   background-color: #DDD; }                              

If the gradient is continuous, you can transition the background-position as documented hither. Otherwise, your concluding resort is to create 2 elements, one overlaid on tiptop of the other, and transition their opacity [demo].

                .element {     width: 100px;     meridian: 100px;     position: relative;   background: linear-slope(#C7D3DC,#5B798E);     }    .element .inner {    content: '';   position: accented;   left: 0; elevation: 0; right: 0; bottom: 0;   groundwork: linear-gradient(#DDD, #FAFAFA);             opacity: 0;   transition: opacity 1s linear; }  .element:hover .inner {   opacity: 1; }                              

The caveats to the latter approach are that it does require extra markup, and the inner div can grab arrow events. Pseudo elements, such as :before and :afterwards would be an ideal utilize-case here, just unfortunately just Firefox supports pseudo element transitions. Eliott Sprehn is working on support for Webkit, which is coming soon.

Hardware acceleration

Transitioning certain properties, such as left and margin causes the browser torecalculating styles every frame. This is adequately expensive, and can pb to unnecessary re-paints, peculiarly if you have a lot of elements on the screen. This is especially noticeable in less powerful devices, such as mobiles.

This solution is to offload the rendering to the GPU using CSS transformations. In unproblematic terms, this turns the chemical element into an image during the transition, avoiding any way recalculations which greatly increases performance. A simple manner of forcing the browser to hardware render an element is to set the transformation'due south z axis, which you lot tin can do with translate3d:

                transform: translate3d(0,0,0);                              

Now, this isn't a magic cure to performance issues, and comes with lots of problems of its own. Y'all should merely apply hardware dispatch when it'southward required, and certainly not enable it on every element.

For example, hardware acceleration can cause subtle font issues, such every bit a font appearing to lose its weight. This is due to a bug where subpixel anti-aliasing isn't supported when an element is being hardware accelerated. You tin see a clear difference between the two rendering modes:

antialiasing.png

The brusk-term fix, albeit controversial, is to disable subpixel anti-aliasing completely. However, be certain to understand the caveats in doing so.

                                  font-smoothing: antialiased;                              

In improver, different browsers apply different hardware dispatch libraries, which can crusade cross-browser problems. For example, whilst Chrome and Safari are both built on WebKit, Chrome uses Skia for graphics rendering while Safari uses CoreGraphics. The differences between the two are subtle, but real.

You tin can use Chrome'southward Inspector to Profile the page, showing all the repaints. Additionally you tin can show paint triangles in the Inspector'southward options, and fifty-fifty turn on Composited Render Layer Borders in near:flags to see which layers are operating on the GPU. The key is to reduce paints past batch updating the DOM, and move as much as possible to the GPU.

Painting

If you're having display problems between browsers with hardware acceleration, such every bit flickering or juddering, make sure you're non nestling elements with the transform3d() CSS holding fix. As a last resort, endeavor having browser specific transformations.

It'southward worth noting that the translate3d hack is becoming less relevant. In fact recent builds of Chrome automatically use the GPU for opacity and 2nd transitions. iOS6 Safari has explicitly disabled this pull a fast one on, and requires still more workarounds.

Clipping

To take advantage of GPU rendering, you lot'll need to avert way recalculations by using CSS transformations rather than properties like width. What do you do though if you do need to animate an element's width? The solution is clipping.

In the example below, you can see a search box with two transition states. The second expanded country is hidden by a clipping element.

Clipping

To transition to the expanded width, all we need to is translate the X axis left. The key thing hither is that we're using translate3d rather than altering the chemical element'due south width [demo].

                .clipped {   overflow: hidden;   position: relative; }  .clipped .clip {   correct: 0px;   width: 45px;   height: 45px;   background: url(/images/clip.png) no-repeat }  input:focus {   -webkit-transform: translate3d(-50px, 0, 0); }                              

Past ensuring that we're not recalculating the chemical element's width every frame, the transition volition a whole lot smoother and performant.

Timing functions

So far we've been using some of the browser's pre-defined timing functions: linear, ease, ease-in, ease-out and ease-in-out. For more complex timing functions nosotros're going to have to write our own timing function past specifying four points along a cubic-bezier curve.

                transition: -webkit-transform 1s cubic-bezier(.17,.67,.69,1.33);                              

Rather than guessing at values, information technology's often easier to either apply a bunch of pre-defined curves, or play around with a graphing tool.

Cubic-bezier

Discover you can drag the values out of bounds and produce a billowy transition, for example:

                transition: all 600ms cubic‑bezier(0.175, 0.885, 0.32, 1.275);                              

Programmatic transitions

Writing transitions in CSS is all very well, merely sometimes you demand a bit more control, peculiarly when information technology comes to chaining transitions. Luckily we can not just invoke transitions from JavaScript, just also define them.

CSS transitions take a magical all belongings which ensures that whatsoever property changes are transitioned. Let'south see how to apply this in practice [demo].

                var defaults = {   duration: 400,   easing: '' };  $.fn.transition = function (properties, options) {   options = $.extend({}, defaults, options);   properties['webkitTransition'] = 'all ' + options.duration + 'ms ' + options.easing;   $(this).css(backdrop); };                              

Now we have this jQuery office $.fn.transition, which we can utilise to programmatically invoke transitions.

                $('.element').transition({groundwork: 'red'});                              

Transition callback

The adjacent footstep to chaining transitions is having transition end callbacks. You can achieve this in Webkit, by listening to the webkitTransitionEnd event. For other browsers, you lot'll need to do a bit of sniffing to find the right outcome proper noun.

                                  var callback = office () {     // ...   }      $(this).one('webkitTransitionEnd', callback)   $(this).css(properties);                              

Be aware that sometimes this event doesn't fire, usually in the case when properties don't change or a paint isn't triggered. To ensure we always get a callback, let's set a timeout that'll trigger the consequence manually.

                $.fn.emulateTransitionEnd = part(duration) {   var called = simulated, $el = this;   $(this).one('webkitTransitionEnd', function() { chosen = truthful; });   var callback = part() { if (!called) $($el).trigger('webkitTransitionEnd'); };   setTimeout(callback, duration); };                              

Now we can invoke $.fn.emulateTransitionEnd() before we fix the element's CSS to ensure our transition end callback is triggered [demo].

                $(this).1('webkitTransitionEnd', callback); $(this).emulateTransitionEnd(options.duration + l); $(this).css(properties);                              

Chaining transitions

So now we can programmatically apply transitions, getting callbacks when they end, we tin start queuing transitions. We could write our ain queue to exercise this, but every bit we're using jQuery we might as well tap into the library's existing functionality.

jQuery provides two main functions to communicate with its queuing API, $.fn.queue(callback) and $.fn.dequeue(). The former adds a callback to the queue, while the latter executes the next item on the queue.

In other words we need to set our CSS transition inside a $.fn.queue callback, and then make sure we invoke $.fn.dequeue when the transition is complete [demo].

                var $el = $(this); $el.queue(function(){   $el.i('webkitTransitionEnd', office(){     $el.dequeue();   });   $el.css(properties); });                              

That'due south example is fairly unproblematic, only it lets us build upwardly complex chained animations, and even apply jQuery's delay() office; for example:

                $('.element').transition({left: '20px'})              .delay(200)              .transition({background: 'red'});                              

Redrawing

Oft when transitioning, you lot'll take two sets of CSS properties. The initial properties that the animation should start at, and the final gear up of properties the transition should terminate on.

                $('.element').css({left: '10px'})              .transition({left: '20px'});                              

Still, y'all'll find that if y'all apply both sets of properties, one immediately after the other, then the browser tries to optimize the property changes, ignoring your initial properties and preventing a transition. Behind the scenes, browsers batch up property changes earlier painting which, while usually speeding upwards rendering, can sometimes have agin affects.

The solution is to forcefulness a redraw between applying the two sets of properties. A simple method of doing this is just by accessing a DOM element'due south offsetHeight property, like and so [demo]:

                $.fn.redraw = function(){   $(this).each(function(){     var redraw = this.offsetHeight;   }); };                              

This will piece of work in most browsers, simply I've had occasions in Android where this hasn't been enough. The alternative is to either use timeouts, or by toggling a class name.

                $('.chemical element').css({left: '10px'})              .redraw()              .transition({left: '20px'});                              

The future

Transitions are being actively worked on, and the next spec looks really promising. The proposals include a new JavaScript API focussing on addressing some of the existing limitations to transitions, and giving developers much more flexibility.

In fact, you tin can find a shim on the new API on GitHub. It involves instantiating a Animation constructor, passing in an chemical element to breathing, the backdrop to animate to, and various other options such every bit a delay.

                var anim = new Blitheness(elem, { left: '100px' }, 3); anim.play();                              

With this new API you can synchronize animations, provide custom timing functions, and become completion callbacks. This is truly heady stuff!

Transitions

By now, y'all hopefully have a deeper understanding of CSS transitions, and how a simple API tin be combined to produce complex and rich effects.

Nigh of the JavaScript examples come directly out of the source of GFX, a jQuery CSS transition library. Likewise as the core library, I've included a number of additional effects, such as slide in/out, explode in/out and 3d flipping.


Thanks to Paul Irish for reviewing this article.

Incidentally, I've been turning the contents of this post into a talk. If yous know or organize a conference that would be suitable for this textile, please do go far affect! Over the last twelvemonth I spoke at vi conferences, and I'm groovy to do even more in 2013.

Enjoying these posts? Subscribe for more


0 Response to "Hardware Accelleration Causes Flicker on Css Animation"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel