Grinding CSS Animations

Original author: Sarah Drasner
  • Transfer
Creating CSS animations may look like a simple syntax study, but certain subtleties are needed to create beautiful and intuitive animations. Since the animation attracts a lot of attention, it is extremely important to put the code in order so that the timing works correctly and debug everything that works incorrectly. After I figured out this problem myself, I decided to put together several tools that can help in this process.

Using Negative Delay Values


Let's say you have several animations that run at the same time, and you need to arrange them in a checkerboard pattern. You can use animation-delay, but you do not want the user, when visiting the page, to wait for some immovable parts to start.

You can set a negative animation-delay value, and this will move the playback cursor backward in time, as a result of which all animations will move when the user enters the page. This option is suitable, in particular, when animations have the same key frame values, and the difference in movement is only in the delay.

This concept can also be used for debugging. Set animation-play-state: paused; and then set various negative delay values. As you transition, you will see animation frames in various pause states.

.thing {
  animation: move 2s linear infinite alternate;
  animation-play-state: paused;
  animation-delay: -1s;
}


In this funny demo, we see two robots hanging at different time intervals to make it look more natural. When describing the hover animation, we set the purple robot to a negative delay, so that when the user first sees the page, he is already moving.

.teal {
 animation: hover 2s ease-in-out infinite both;
}
.purple {
 animation: hover 2s -0.5s ease-in-out infinite both;
}
@keyframes hover {
  50% {
    transform: translateY(-4px);
  }
}


Multiple Transformation Value Issues



For greater efficiency, some elements should also be moved and changed using the transform function, which will help you avoid the cost of redrawing with a field or left / right shift, etc. Paul Lewis has an excellent resource called CSS Triggers , in which such costs are presented in a convenient table. The pitfalls here are that if you try to shift elements using multiple transformations, you will encounter a number of problems.

The main problem is order. Transformations will not be applied at the same time, as might be expected, and instead they will appear in the order of operations. The first operation will be the far right, and then in order. For example, in the code below, scaling will start first, then transform, and then rotate.

@keyframes foo {
 to {
   /*         3rd           2nd              1st      */transform: rotate(90deg) translateX(30px) scale(1.5);
 }
}


In most situations, this is not an ideal option. Most likely, you need all this to happen in parallel. Moreover, it becomes even more difficult if you begin to divide the transformations into several key frames, in which some values ​​are used at the same time and some are not. For example, in this way:

@keyframes foo {
  30% {
    transform: rotateY(360deg);
  }
  65% {
    transform: translateY(-30px) rotateY(-360deg) scale(1.5);
  }
  90% {
    transform: translateY(10px) scale(0.75);
  }
}


This will lead to slightly surprising and not quite perfect results . The solution, unfortunately, is the use of several nested -elements, with a separate transformation applied to each, so that there are no conflicts.

There are other alternatives, for example, the use of matrix transformations (which are not intuitive for manual code) or the use of JavaScript animation APIs, for example, GreenSock, in which the order of multiple transform interpolations cannot be adjusted.

Using multiple div elements can also help troubleshoot SVG issues. In Safari, you cannot set the transparency and transformation of the animation at the same time - one of the values ​​will not work. A workaround is shown in this article in the first demo.

In early August 2015, definitions of individual transformations were included in Chrome Canary. This means that we no longer need to worry about order. The rotate, translate, and scale functions can now be set individually.

Timing Tools


Both Chrome and Firefox today provide several tools designed specifically for working with animations. They offer a slider for controlling speed, a pause button, and a user interface for working with smoothing values. Slowing the speed of the elements and considering the animation at certain breakpoints helps a lot in debugging CSS animations.

Both tools use Lee Verou's visualization cubic-bezier.com and GUI. This is pretty good, since you no longer have to skip back and forth from cubic-bezier.com to a text editor to check.

These tools allow us to customize the animation more intuitively. Here's what the user interface looks like:
img


Both Chrome and Firefox allow you to control the timing (speed up or slow down), as well as manually clean up animations. More advanced time management tools will be available soon in Chrome and will be able to view many elements at once. That would be nice, since working with only one animation element at a time is a strong limiting factor.

One of the problems I encountered is the quick capture of an element if the animation is short and fast. In such cases, I usually set animation-iteration-count: infinite; to be able to play the animation continuously without having to fight over time.

I also consider it a great way to slow down the animation, and then replay it and adjust the timing in the browser using these tools. This allows you to break each movement at the initial level and watch how everything interacts, and how the movement itself looks. If you set everything up at that speed, then you can set a higher speed, and it will look more professional.

Debugging CSS Animation Events Using JavaScript


If you want to pinpoint where and when each animation starts, you can use a little JavaScript to determine and indicate when each event occurs by connecting the animationstart, animationiteration, and animationend elements.

Demo

Keyframes must remain compact


I often see people define the same property and value in a key frame at 0% and in a key frame at 100%. This is optional, and this can lead to more bloated code. The browser will take the property value as the initial value, and the final value as the default.

This is already too much:

.element {
 animation: animation-name 2s linear infinite;
}
@keyframes animation-name {
  0% {
   transform: translateX(200px);
 }
  50% {
   transform: translateX(350px);
 }
 100% {
   transform: translateX(200px);
 }
}


It should look like this:
.element {
 transform: translateX(200px);
 animation: animation-name 2s linear infinite;
}
@keyframes animation-name {
  50% {
   transform: translateX(350px);
 }
}


"Dry" animation


Creating a beautiful and succinct animation usually means writing a certain smoothness function cubic-bezier (). A well-tuned smoothness function works like a company palette. You have a certain branding and “voice” in motion. If you use them all over the site (and you have to endure everything in one style), the simplest way to do this is to save one or two smoothness functions in a variable, just like we do in palettes. SASS and other pre / post processors make this pretty simple:

$smooth: cubic-bezier(0.17, 0.67, 0.48, 1.28);
.foo { animation: animation-name 3s $smooth; }
.bar { animation: animation-name 1s $smooth; }

When creating animations using CSS keyframes, we want as much help as possible from the GPU. This means that if you are animating multiple objects, you need a way to easily prepare the DOM for incoming movement and layering the element. You can hardware accelerate a native DOM element (not SVG) using CSS by using a standard description component. Since we reuse it on all the elements that we animate, it makes sense to make a little description and additionally use impurities or inheritance:

@mixin accelerate($name) {
 will-change: $name;
 transform: translateZ(0);
 backface-visibility: hidden;
 perspective: 1000px;
}
.foo {
  @include accelerate(transform);
}

Be careful. Unloading too many elements at a time can have the opposite effect and lower performance. Most animations should be detailed, but be careful if you use something like haml to generate a huge amount of DOM elements.

Cycles for Higher Performance


Recently, a good article was published at Smashing Magazine , which presented work on a fantastic project - Species in Pieces. In one of the sections, the author describes in sufficient detail how the animation of all the details at a time led to performance problems. He's writing:

Imagine that you are moving 30 objects at a time; you require too much from the browser, and it’s logical that this can lead to problems. If the speed of each object is 0.199 seconds and the delay is 0.2 seconds, shifting only one object at a time can solve the problem. The fact that the same number of movements actually occurs does not matter: if the animation is performed in a chain, productivity immediately increases by 30 times.


You can use for loops in Sass or other pre / post processors to create such functionality. Here is a pretty simple version that I wrote to loop an nth-child child:

@for $i from 1 through $n {
  &:nth-child(#{$i}) {
    animation: loadIn 2s #{$i*0.11}s $easeOutSineforwards;
  }
}


In addition, they can be used to stagger visual effects such as color, etc.

Set up multiple animations in sequence


When creating longer animations, you can build a sequence of several animations or events by linking them in a chain with progressive delays. For example, like this:

animation: foo 3sease-in-out, bar 4s 3sease-in-out, brainz 6s 7sease-in-out;

But suppose you make certain changes and find that the second score of the first animation needs to be changed. This will affect the delays of everything that comes after it, so you need to adjust the timing of each subsequent animation. It is not difficult.

animation: foo 2.5sease-in-out, bar 4s 2.5sease-in-out, brainz 6s 6.5sease-in-out;

Now, let's add another animation and change the timing of the second one again (such a configuration option happens almost always when creating really high-quality animation). It is getting a little ineffective. If you do this more than three times, it will be truly ineffective.

Now imagine that in the middle of the path, two animation movements should start at the same time, so you need to adhere to a uniform timing with two different properties and ... well, you understand. Therefore, every time I sequence three or four related animations, I switch to JavaScript. Personally, I like the GreenSock animation API, because it has a very convenient time setting functionality, while most JS animations make it easy to build animations without recounts, which definitely affects the workflow.

Debugging an animation is more important than just building an animation. As a rule, editing, improving and debugging turn a project into a well-organized and effective thing. I hope these tips will bring some new tools to your piggy bank and smooth your workflow.


Also popular now: