Facundo Corradini Frontend developer, CSS specialist, best cebador de mates ever.

CSS Motion Path: The end of GSAP?

4 min read 1270

A tutorial about CSS motion path.

CSS animations used to be quite limited.

Dealing with anything other than a very basic effect usually meant long and complex @keyframes declarations.

Furthermore, moving elements through a path required a masterful use of simultaneous translation and rotation: exactly the kind of thing that browser discrepancies would turn into nothing short of a nightmare.

To fix that hassle, developers have been turning to JS solutions since the dawn of web animation (or at least, since we decided to drop Flash, but I like to pretend that Flash never happened.)

Back in the day, moving things was one of the great uses for the good ol’ jQuery, but things could get resource-intensive in a pinch.

Then GSAP came out and gave us unlimited animation power with far better performance, which quickly turned it into an industry standard.

But CSS has been progressing too, and the recent release of Firefox 72 and Chromium-powered Edge means we can start implementing the CSS Motion Path specification that comes to solve exactly these kind of scenarios.

But does this mean we can get away with pure CSS animations and dump GSAP?

Positioning elements through a path

The core of the Motion Path Module is the offset-path property. It takes a path() function as its value, allowing us to define an SVG path for elements to be positioned through.

  offset-path: path('M 0 100 L 200 150 L 300 150');

If you ever used CSS clip-path, this should look familiar. Essentially, it defines the points that the line goes through and the different ways it gets there.

Next up, the offset-distance property allows us to define the position of the element on its offset-path. It can take whatever CSS <length> unit (%, px, etc).

In most cases, using percentages will be the better approach.

For instance, the following code will position an element right at the middle of its offset-path.

  offset-distance: 50%;

Here’s an example with various elements positioned throughout a path:

Motion Path #1 – Positioning elements through a path

No Description


Using the path for animation

Despite what the name seems to imply, there’s no motion involved when using the properties of the Motion Path module on themselves: that part is still handled by animating the different motion path properties via transitions, CSS animations, or the Web Animations API.

Therefore, to actually move the element on the path we can use a @keyframes declaration that shifts the offset-distance.

  offset-path: path('M0,0 C40,160 60,160 100,0');
  animation: move 2000ms;
@keyframes move {
  100% {
    offset-distance: 100%;

Motion Path #2: animating offset-distance

dot { offset-path: path(‘M 0 0 Q 150 500 300 0’); animation: move 4s cubic-bezier(0.78, 0, 0.17, 0.99) infinite alternate; } @keyframes move{ 100%{ offset-distance: 100%; } } .dot{ background: lightblue; width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; border: 2px solid slateblue; border-radius: 5px; } section{ position: relative; width: 300px; height: 300px; margin: auto; } svg { position: absolute; stroke-width: 2px; } body{ background: Azure; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; overflow-x: hidden; } *{ box-sizing: border-box; }


Controlling the element’s rotation

We can also use the offset-rotate property to control how the element rotation should behave as it goes through its defined path.

More great articles from LogRocket:

The default auto value will make sure that the element always faces in the direction of the path, automatically rotating as needed. If we want to position the element through the path facing in a given direction, we can use a CSS <degree> value instead.

The following code will make sure that the element keeps it’s original orientation as it goes through the path.

  offset-rotate: 0deg;

Similar to what we did with offset-position, we can control the offset-rotate throughout the keyframe declaration and have the element direction adjust accordingly.

Keep in mind that for the rotation to smoothly transition, we must declare all of them in angles; changing from a degree to auto will result in jumpy motion.

Combining both, we can get results as follows:

Motion Path #3 – offset-distance + offset-rotate

moving-element { offset-path: path(‘M 0 100 L 200 150 L 300 150’); animation: move 4s ease-in-out infinite; } @keyframes move{ 0%{ offset-rotate: 15deg;} 30%{ offset-rotate: 15deg;} 60%{ offset-rotate: -20deg;} 100%{ offset-distance: 100%; offset-rotate: 0deg; } } .moving-element{ background: lightblue; width: 50px; height: 20px; display: flex; align-items: center; justify-content: center; border: 2px solid slateblue; border-radius: 5px; } section{ position: relative; width: 300px; height: 300px; margin: auto; } svg { position: absolute; stroke-width: 2px; } body{ background: Azure; overflow-x: hidden; } *{ box-sizing: border-box; }


Animating with :hover and transitions

Remember that we can also use simple transitions to animate the motion path properties

  offset-distance: 100%;
  offset-rotate: 360deg;

In the following example, all elements start positioned at the beginning of their path and are animated out on hover.

Motion Path #4 – Animating on hover

section:hover > .dot{ offset-distance: 100%; offset-rotate: 360deg; } .dot:nth-child(1){ offset-path: path(‘M 50 50 Q 0 50 0 0’); } .dot:nth-child(2){ offset-path: path(‘M 50 50 Q 50 0 100 0’); } .dot:nth-child(3){ offset-path: path(‘M 50 50 Q 100 50 100 100’); } .dot:nth-child(4){ offset-path: path(‘M 50 50 Q 50 100 0


Wrapping text through a path

If we try to apply the motion path properties to a text element, we’ll see that the whole text block is treated as a single piece. If what we want is for the text to wrap around the path (and potentially be animated through it) we need to make each letter behave as an individual element.

The first approach to this is actually splitting the text with an utility such as splitting.js. While this works great, it will pollute the dom with a <span> for each letter, and result in screen readers spelling out the words.

The screen-reader-friendly option is to use an actual SVG text with a textPath element.

Motion Path #5 – Wrapping text through a path

Add External Stylesheets/Pens Any URLs added here will be added as s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension. JavaScript Preprocessor Babel includes JSX processing.


Animating the path itself

Just like the fun stuff we do with clip-path, the path declaration of an offset-path can be animated.

To do so, we must make sure to have the same amount of nodes in every step of the animation for the browser to be able to smoothly transition between them.

If we provide different amounts of nodes in any step, the browser won’t be able to guess the in-betweens and will simply jump from one step to the next without a transition.

Motion Path #6 – Animating the path itself

dot { offset-path: path(‘M 0 100 Q 100 0 200 100 Q 300 200 400 100’); position: absolute; offset-rotate: 0deg; animation: move 3s ease-in-out infinite alternate; } @keyframes move{ 100%{ offset-path: path(‘M 0 100 Q 100 200 200 100 Q 300 0 400 100’) } } .dot:nth-of-type(2) { offset-distance: 20%;


Upcoming features and improvements

The browsers’ current implementation of offset-path only allows us to declare a path() function for the elements to follow. According to the working draft of the spec, we should be able to use a <basic-shape> (such as circle, polygon, etc) too. So we can probably expect those in the near future.

It will also allow us to use an SVG path id as the value (e.g. offset-path: url(#my-path)), which will help us animate things through a drawn path in our HTML and let it scale accordingly.

There’s also an additional property that only Firefox has implemented so far: the offset-anchor property allows us to define the anchoring point of the element relative to it’s offset-path.

It’s default setting is 50 %/50 %, which means the element is centered on the path. By changing the offset-anchor, we can manipulate which part of the element stays fixed to the path, similarly to what we do with transform-origin.

A word on accessibility

I’ll be the first to admit that it’s tempting to use this new tool everywhere, but animations should be used purposely and responsibly.

Animations can trigger nausea in people with vestibular disorders, be distracting for anyone with attention disorders, or simply annoying for some users that might prefer to disable it.

So consider implementing this in a prefers-reduced-motion media query, to keep those users safe.

  offset-path: path('M0,0 C40,160 60,160 100,0');
  animation: move 2s ease-out;

@keyframes move {
  100% {
    offset-distance: 100%;

@media screen and (prefers-reduced-motion: reduce){
    animation-duration: 1ms; /* takes it immediately to the ending position */


The Motion Path module has just made CSS animations a hundred times more powerful, with many use cases that we’re just beginning to discover.

But it still has some downsides: for starters, and to address the elephant in the room, Safari doesn’t support the spec yet, which can be a deal breaker for compatibility.

It also doesn’t change the quirks of CSS animation itself, such as the complexity of writing and maintaining keyframes (specially when we need to chain and synchronize them together), and the limitation to two bezier handlers for their easing functions, which make things like bounce effects much more difficult to create and maintain than in external libraries that support multiple beziers.

So GSAP is here to stay, at least for a good while.

What Motion Path does, in my opinion, is moving the threshold at which we would switch from CSS to JS for animations quite a bit.

Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.LogRocket Dashboard Free Trial Bannerhttps://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web and mobile apps — Start monitoring for free.

Facundo Corradini Frontend developer, CSS specialist, best cebador de mates ever.

Leave a Reply