CSS animate position

The world of web animations has become a sprawling jungle of tools and technologies. Libraries like GSAP and Framer Motion and React Spring have sprung up to help us add motion to the DOM.

The most fundamental and critical piece, though, is the humble CSS transition. It's the first animation tool that most front-end devs learn, and it's a workhorse. Even the most grizzled, weathered animation veterans still reach for this tool often.

There's a surprising amount of depth to this topic. In this tutorial, we'll dig in and learn a bit more about CSS transitions, and how we can use them to create lush, polished animations.

Intended audience

This tutorial is meant to be accessible to developers of all experience levels. It can be thought of as "CSS transitions 101". That said, I've sprinkled in some interesting and obscure tidbits — no matter your experience level, I bet you'll learn something!

The fundamentals

The main ingredient we need to create an animation is some CSS that changes.

Here's an example of a button that moves on hover, without animating:

Code Playground

Format code using Prettier

Reset code

HTML

<button class="btn">

Hello World

</button>

<style>

.btn {

width: 100px;

height: 100px;

border-radius: 50%;

border: none;

background: slateblue;

color: white;

font-size: 20px;

font-weight: 500;

line-height: 1;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

Result

Refresh results pane

Enable ‘tab’ key

This snippet uses the

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

4 pseudoclass to specify an additional CSS declaration when the user's mouse rests atop our button, similar to an

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

5 event in JavaScript.

To shift the element up, we use

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

6. While we could have used

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

7 for this,

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

8 is a better tool for the job. We'll see why later.

By default, changes in CSS happen instantaneously. In the blink of an eye, our button has teleported to a new position! This is incongruous with the natural world, where things happen gradually.

We can instruct the browser to interpolate from one state to another with the aptly-named

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

9 property:

Code Playground

Format code using Prettier

Reset code

HTMLCSS

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

Result

Refresh results pane

Enable ‘tab’ key

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

9 can take a number of values, but only two are required:

  1. The name of the property we wish to animate

  2. The duration of the animation

If you plan on animating multiple properties, you can pass it a comma-separated list:

css

Selecting all properties

css

1 takes a special value:

css

2. When

css

2 is specified, any CSS property that changes will be transitioned.

It can be tempting to use this value, as it saves us a good chunk of typing if we're animating multiple properties, but I recommend not using it.

As your product evolves, you (or someone on your team) will likely wind up updating this code at some point in the future. An unexpected animation could slip through.

Animation is like salt: too much of it spoils the dish. It pays to be really precise with the properties we animate.

Timing functions

When we tell an element to transition from one position to another, the browser needs to work out what each "intermediary" frame should look like.

For example: let's say that we're moving an element from left to right, over a 1-second duration. A smooth animation should run at 60fps*, which means we'll need to come up with 60 individual positions between the start and end.

Let's start by having them be evenly-spaced:

To clarify what's going on here: each faded circle represents a moment in time. As the circle moves from left to right, these are the frames that were shown to the user. It's like a flipbook.

In this animation, we're using a linear timing function. This means that the element moves at a constant pace; our circle moves by the same amount each frame.

There are several timing functions available to us in CSS. We can specify which one we want to use with the

css

4 property:

css

Or, we can pass it directly to the

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

9 shorthand property:

css

css

6 is rarely the best choice — after all, pretty much nothing in the real world moves this way*. Good animations mimic the natural world, so we should pick something more organic!

Let's run through our options.

ease-out

css

7 comes charging in like a wild bull, but it runs out of energy. By the end, it's pootering along like a sleepy turtle.

Try scrubbing with the timeline; notice how drastic the movement is in the first few frames, and how subtle it becomes towards the end.

If we were to graph the displacement of the element over time, it'd look something like this:

When would you use

css

7? It's most commonly used when something is entering from off-screen (eg. a modal appearing). It produces the effect that something came hustling in from far away, and settles in front of the user.

ease-in

css

9, unsurprisingly, is the opposite of

css

7. It starts slow and speeds up:

As we saw,

css

7 is useful for things that enter into view from offscreen.

css

9, naturally, is useful for the opposite: moving something beyond the bounds of the viewport.

This combo is useful when something is entering and exiting the viewport, like a modal. We'll look at how to mix and match timing functions shortly.

Note that

css

9 is pretty much exclusively useful for animations that end with the element offscreen or invisible; otherwise, the sudden stop can be jarring.

ease-in-out

Next up,

css

4. It's the combination of the previous two timing functions:

This timing function is symmetrical. It has an equal amount of acceleration and deceleration.

I find this curve most useful for anything that happens in a loop (eg. an element fading in and out, over and over).

It's a big step-up over

css

6, but before you go slapping it on everything, let's look at one more option.

ease

If I had a bone to pick with the CSS language authors when it comes to transitions, it's that

css

6 is poorly named. It isn't descriptive at all; literally all timing functions are eases of one sort or another!

That nitpick aside,

css

6 is awesome. Unlike

css

4, it isn't symmetrical; it features a brief ramp-up, and a lot of deceleration.

css

6 is the default value — if you don't specify a timing function,

css

6 gets used. Honestly, this feels right to me.

css

6 is a great option in most cases. If an element moves, and isn't entering or exiting the viewport,

css

6 is usually a good choice.

Time is constant

An important note about all of these demos: time is constant. Timing functions describe how a value should get from 0 to 1 over a fixed time interval, not how quickly the animation should complete. Some timing functions may feel faster or slower, but in these examples, they all take exactly 1 second to complete.

Custom curves

If the provided built-in options don't suit your needs, you can define your own custom easing curve, using the cubic bézier timing function!

css

All of the values we've seen so far are really just presets for this

css

3 function. It takes 4 numbers, representing 2 control points.

Bézier curves are really nifty, but they're beyond the scope of this tutorial. I'll be writing more about them soon though!

In the meantime, you can start creating your own Bézier timing functions using this wonderful helper from Lea Verou:

Once you come up with an animation curve you're satisfied with, click “Copy” at the top and paste it into your CSS!

You can also pick from this extended set of timing functions. Though beware: a few of the more outlandish options won't work in CSS.

CSS animate position

When starting out with custom Bézier curves, it can be hard to come up with a curve that feels natural. With some practice, however, this is an incredibly expressive tool.

Time for me to come clean

I have a confession to make: the demonstrations above, showing the different timing functions, were exaggerated.

In truth, timing functions like

css

9 are more subtle than depicted, but I wanted to emphasize the effect to make it easier to understand. The

css

3 timing function makes that possible!

Show more

Animation performance

Earlier, we mentioned that animations ought to run at 60fps. When we do the math, though, we realize that this means the browser only has 16.6 milliseconds to paint each frame. That's really not much time at all; for reference, it takes us about 100ms-300ms to blink!

If our animation is too computationally expensive, it'll appear janky and stuttery. Frames will get dropped, as the device can't keep up.

Experience this for yourself by tweaking the new "Frames per second" control:

In practice, poor performance will often take the form of variable framerates, so this isn't a perfect simulation.

Animation performance is a surprisingly deep and interesting area, well beyond the scope of this introductory tutorial. But let's cover the absolutely-critical, need-to-know bits:

  1. Some CSS properties are wayyy more expensive to animate than others. For example,

    css

    6 is a very expensive property because it affects layout. When an element's height shrinks, it causes a chain reaction; all of its siblings will also need to move up, to fill the space!

  2. Other properties, like

    css

    7, are somewhat expensive to animate. They don't affect layout, but they do require a fresh coat of paint on every frame, which isn't cheap.

  3. Two properties —

    css

    8 and

    css

    9 — are very cheap to animate. If an animation currently tweaks a property like

    css

    0 or

    css

    1, it can be greatly improved by moving it to

    css

    8 (though it isn't always possible to achieve the exact same effect).

  4. Be sure to test your animations on the lowest-end device that your site/app targets. Your development machine is likely many times faster than it.

If you're interested in learning more about animation performance, I gave a talk on this subject at React Rally. It goes deep into this topic:

Hardware Acceleration

Depending on your browser and OS, you may have noticed a curious little imperfection in some of the earlier examples:

CSS animate position

Pay close attention to the letters. Notice how they appear to glitch slightly at the start and end of the transition, as if everything was locking into place?

This happens because of a hand-off between the computer's CPU and GPU. Let me explain.

When we animate an element using

css

8 and

css

9, the browser will sometimes try to optimize this animation. Instead of rasterizing the pixels on every frame, it transfers everything to the GPU as a texture. GPUs are very good at doing these kinds of texture-based transformations, and as a result, we get a very slick, very performant animation. This is known as “hardware acceleration”.

Here's the problem: GPUs and CPUs render things slightly differently. When the CPU hands it to the GPU, and vice versa, you get a snap of things shifting slightly.

We can fix this problem by adding the following CSS property:

css

css

5 is a property that allows us to hint to the browser that we're going to animate the selected element, and that it should optimize for this case.

In practice, what this means is that the browser will let the GPU handle this element all the time. No more handing-off between CPU and GPU, no more telltale “snapping into place”.

css

5 lets us be intentional about which elements should be hardware-accelerated. Browsers have their own inscrutable logic around this stuff, and I'd rather not leave it up to chance.

There's another benefit to hardware acceleration: we can take advantage of sub-pixel rendering.

Check out these two boxes. They shift down when you hover/focus them. One of them is hardware-accelerated, and the other one isn't.

Code Playground

Format code using Prettier

Reset code

HTMLCSS

<style>

.accelerated.box {

transition: transform 750ms;

will-change: transform;

background: slateblue;

}

.accelerated.box:hover,

.accelerated.box:focus {

transform: translateY(10px);

}

.janky.box {

transition: margin-top 750ms;

will-change: margin-top;

background: deeppink;

}

.janky.box:hover,

.janky.box:focus {

margin-top: 10px;

}

</style>

<div class="wrapper">

<button class="accelerated box"></button>

<button class="janky box"></button>

</div>

Result

Refresh results pane

Enable ‘tab’ key

It's maybe a bit subtle, depending on your device and your display, but one box moves much more smoothly than the other.

Properties like

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

7 can't sub-pixel-render, which means they need to round to the nearest pixel, creating a stepped, janky effect.

css

8, meanwhile, can smoothly shift between pixels, thanks to the GPU's anti-aliasing trickery.

Tradeoffs

Nothing in life comes free, and hardware acceleration is no exception.

By delegating an element's rendering to the GPU, it'll consume more video memory, a resource that can be limited, especially on lower-end mobile devices.

This isn't as big a deal as it used to be — I've done some testing on a Xiaomi Redmi 7A, a popular budget smartphone in India, and it seems to hold up just fine. Just don't broadly apply

css

5 to elements that won't move. Be intentional about where you use it.

Alternative properties

Hardware acceleration has been around for a long time—longer than the

css

5 property, in fact!

For a long time, it was accomplished by using a 3D transform, like

css

1. Even with a 0px value, the browser still hands it off to the GPU, since moving in 3D space is definitely a GPU strength. There's also

css

2.

When

css

5 came out, it was intended to give developers a proper, semantically-meaningful way to hint to the browser that an element should be optimized. In the early days, though,

css

5 had some problems.

Happily, it seems as though all of these issues have been resolved. I've done some testing, and have found that I get the best results across modern browsers with

css

5. But you should always do your own testing, to make sure that these techniques work on the devices and browsers you target.

UX touches

Action-driven motion

Let's take another look at our rising “Hello World” button.

As it stands, we have a "symmetrical" transition — the enter animation is the same as the exit animation:

  • When the mouse hovers over the element, it shifts up by 10 pixels over 250ms

  • When the mouse moves away, the element shifts down by 10 pixels over 250ms

A cute little detail is to give each action its own transition settings. For hover animations, I like to make the enter animation quick and snappy, while the exit animation can be a bit more relaxed and lethargic:

Code Playground

Format code using Prettier

Reset code

HTML

<button class="btn">

Hello World

</button>

<style>

.btn {

will-change: transform;

transition: transform 450ms;

}

.btn:hover {

transition: transform 125ms;

transform: translateY(-10px);

}

</style>

CSS

.btn {

width: 100px;

height: 100px;

border-radius: 50%;

border: none;

background: slateblue;

color: white;

font-size: 20px;

font-weight: 500;

line-height: 1;

}

Result

Refresh results pane

Enable ‘tab’ key

Another common example is modals. It can be useful for modals to enter with an

css

7 animation, and to exit with a quicker

css

9 animation:

This is a small detail, but it speaks to a much larger idea.

I believe most developers think in terms of states: for example, you might look at this situation and say that we have a “hover” state and a default state. Instead, what if we thought in terms of actions? We animate based on what the user is doing, thinking in terms of events, not states. We have a mouse-enter animation and a mouse-leave animation.

Tobias Ahlin shows how this idea can create next-level semantically-meaningful animations in his blog post, Meaningfun Motion with Action-Driven Animation.

Delays

Well, we've come pretty far in our quest to become proficient with CSS transitions, but there are a couple final details to go over. Let's talk about transition delays.

I believe that just about everyone has had this frustrating experience before:

CSS animate position

Image courtesy of Ben Kamens

As a developer, you can probably work out why this happens: the dropdown only stays open while being hovered! As we move the mouse diagonally to select a child, our cursor dips out-of-bounds, and the menu closes.

This problem can be solved in a rather elegant way without needing to reach for JS. We can use

css

8!

css

css

8 allows us to keep things status-quo for a brief interval. In this case, when the user moves their mouse outside

<style>

.accelerated.box {

transition: transform 750ms;

will-change: transform;

background: slateblue;

}

.accelerated.box:hover,

.accelerated.box:focus {

transform: translateY(10px);

}

.janky.box {

transition: margin-top 750ms;

will-change: margin-top;

background: deeppink;

}

.janky.box:hover,

.janky.box:focus {

margin-top: 10px;

}

</style>

<div class="wrapper">

<button class="accelerated box"></button>

<button class="janky box"></button>

</div>

0, nothing happens for 300ms. If their mouse re-enters the element within that 300ms window, the transition never takes place.

After 300ms elapses, the

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

9 kicks in normally, and the dropdown fades out over 400ms.

Why no shorthand?

So far, we've been using the

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

9 shorthand to bundle all our transition-related values together.

css

8 can also be used with the shorthand:

css

Show more

Doom flicker

When an element is moved up or down on hover, we need to be very careful we don't accidentally introduce a "doom flicker":

Warning: This GIF includes flickering motion that may potentially trigger seizures for people with photosensitive epilepsy.

Reveal

You may have noticed a similar effect on some of the demos on this page!

The trouble occurs when the mouse is near the element's boundary. The hover effect takes the element out from under the mouse, which causes it to fall back down under the mouse, which causes the hover effect to trigger again… many times a second.

How do we solve for this? The trick is to separate the trigger from the effect. Here's a quick example:

Code Playground

Format code using Prettier

Reset code

HTMLCSS

<button class="btn">

Hello World

</button>

<style>

.btn {

/*

All of the base styles have

moved to the “CSS” tab above.

*/

transition: transform 250ms;

}

.btn:hover {

transform: translateY(-10px);

}

</style>

2

Result

Refresh results pane

Enable ‘tab’ key

Our

<style>

.accelerated.box {

transition: transform 750ms;

will-change: transform;

background: slateblue;

}

.accelerated.box:hover,

.accelerated.box:focus {

transform: translateY(10px);

}

.janky.box {

transition: margin-top 750ms;

will-change: margin-top;

background: deeppink;

}

.janky.box:hover,

.janky.box:focus {

margin-top: 10px;

}

</style>

<div class="wrapper">

<button class="accelerated box"></button>

<button class="janky box"></button>

</div>

4 now has a new child,

<style>

.accelerated.box {

transition: transform 750ms;

will-change: transform;

background: slateblue;

}

.accelerated.box:hover,

.accelerated.box:focus {

transform: translateY(10px);

}

.janky.box {

transition: margin-top 750ms;

will-change: margin-top;

background: deeppink;

}

.janky.box:hover,

.janky.box:focus {

margin-top: 10px;

}

</style>

<div class="wrapper">

<button class="accelerated box"></button>

<button class="janky box"></button>

</div>

5. This span houses all of the cosmetic styles (background color, font stuff, etc).

When we mouse over the plain-jane button, it causes the child to peek out above. The button, however, is stationary.

Try uncommenting the

<style>

.accelerated.box {

transition: transform 750ms;

will-change: transform;

background: slateblue;

}

.accelerated.box:hover,

.accelerated.box:focus {

transform: translateY(10px);

}

.janky.box {

transition: margin-top 750ms;

will-change: margin-top;

background: deeppink;

}

.janky.box:hover,

.janky.box:focus {

margin-top: 10px;

}

</style>

<div class="wrapper">

<button class="accelerated box"></button>

<button class="janky box"></button>

</div>

6 to see exactly what's going on!

Respecting motion preferences

When I see a well-crafted animation on the web, I react with delight and glee. People are different, though, and some folks have a very different reaction: nausea and malaise.

I've written before about respecting “prefers-reduced-motion”, an OS-level setting users can toggle to express a preference for less motion. Let's apply those lessons here, by disabling animations for folks who request it:

css

This small tweak means that animations will resolve immediately for users who have gone into their system preferences and toggled a checkbox.

As front-end developers, we have a certain responsibility to ensure that our products aren't causing harm. This is a quick step we can perform to make our sites/apps friendlier and safer.

The bigger picture

CSS transitions are fundamental, but that doesn't mean they're easy. There's a surprising amount of depth to them; even in this long-winded blog post, I've had to cut some stuff out to keep it manageable!

Web animations are more important than most developers realize. A single transition here or there won't make or break an experience, but it adds up. In aggregate, well-executed animations can have a surprisingly profound effect on the overall user experience.

Transitions can make an app feel "real". They can offer feedback, and communicate in a more-visceral way than copy alone. They can teach people how to use your products. They can spark joy.

So, I have a confession to make: this tutorial was plucked straight from my CSS course, CSS for JavaScript Developers. If you found this tutorial helpful, you should know that this is only the tip of the iceberg!

My course is designed to give you confidence with CSS. We explore how the language really works, building up a mental model you can use to solve any layout/UI challenge.

It's not like any other course you've taken. It's built on the same tech stack as this blog, and so there's lots of rich interactive content, but there are also bite-sized videos, tons of exercises, and real-world-inspired projects where you can test your knowledge. There are even some mini-games!

Learn more and see if you'd benefit from it at https://css-for-js.dev:

CSS animate position

Finally, no interactive lesson is complete without a Sandbox Mode! Play with all the previous settings (and a couple new ones!) and create some generative art with this open-ended widget:

Can you animate position CSS?

Any CSS property that be transitioned can also be animated. Use animation-delay to pause before executing an animation, using the same time values as for duration. The animation-iteration-count property sets the number of times the animation plays, either as an integer or infinite.

How do I make an animate left to right in CSS?

To do that we need to change the transform around it's y-axis. Note that if we make it turn around only at 50% it will slowly turn around at the same time it's moving. So we need to specify how long the potato should be at -webkit-transform: rotateY(0deg); .

How do you animate up and down in CSS?

img.vert-move { -webkit-animation: mover 1s infinite alternate; animation: mover 1s infinite alternate; ... .
img.vert-move { -webkit-animation: mover 1s infinite alternate; ... .
@-webkit-keyframes mover { 0% { transform: translateY(0); } ... .
@keyframes mover { 0% { transform: translateY(0); }.

How do I animate a div from left to right?

EDIT I've achieve this by using 2 different methods :.
by changing the right property and with using a calc() to prevent to box to go outside your container..
Use a wrapper who have the width of your container minus the width of your box and use translateX property for your animation..