CSS Motion Paths

Animación de trayectos o rutas con CSS


En CSS existen los denominados Motion Paths, o lo que es lo mismo, trayectos animados. Mediante esta característica podemos crear trayectos, rutas o caminos animados, por donde un elemento puede pasar.

Para crearlos, utilizaremos una familia de propiedades CSS prefijadas por offset-:

Propiedad Descripción
offset-path Define un trayecto, ruta o camino, utilizando la función ray(), path() o url().
offset-distance Indica la posición actual en el trayecto.
offset-rotate Define la orientación del elemento del trayecto.
offset-position Define el punto de inicio del trayecto.
offset-anchor Define el punto de anclaje (punto de origen) del elemento que se mueve por el trayecto.
offset Propiedad de atajo de las anteriores.

El soporte de esta característica es buena en navegadores actuales, sin embargo, presta atención porque algunas características podrían no estar completamente soportadas:

Definir un trayecto

Para utilizar CSS Motion Paths, lo primero que tenemos que hacer es definir un trayecto mediante la propiedad offset-path. Este trayecto definirá la zona por la que se puede mover el elemento al que se lo apliques. En nuestro caso, vamos a imaginar que tenemos un elemento .container que contiene un elemento .box, que es el que se va a mover por nuestro trayecto:

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  background: #aaa;
  min-height: 400px;
}

.box {
  width: 50px;
  height: 50px;
  background: indigo;
  animation: move 2s alternate infinite;
}

@keyframes move {
  0% { offset-distance: 0%; }
  100% { offset-distance: 100%; }
}
<div class="container">
  <div class="box"></div>
</div>

En este código no hacemos nada extraño. En el padre, establecemos un flex centrado, color de fondo y un alto mínimo para que se vea. En los elementos hijos .box un tamaño, un color de fondo para distinguirlo. También aplicamos una animación que aún no hace nada, pero que explicaremos más tarde. De momento, sólo tenemos que saber que la animación se moverá desde el principio del trayecto hasta el final.

La propiedad offset-path nos permite definir un trayecto de varias formas:

  • Mediante una función ray() que define un segmento en una dirección de ángulo concreta.
  • Mediante una función path() donde definimos un trayecto SVG.
  • Mediante una función url() donde indicamos un archivo SVG que contiene un trayecto.

Esto se traduce a los valores que puede tomar la propiedad offset-path:

Valores Descripción
none No establece ningún trayecto. Es el valor por defecto.
ray() Indica un segmento en dirección con tamaño .
ray( contain) Idem, pero manteniendo el elemento dentro del contenedor.
path() Define un trayecto SVG.
url() Define un SVG con el id de un trayecto path con el atributo d.

Veamos cada una de ellas a continuación.

Usando trayectos con ray()

La función ray() nos permite definir un trayecto como una porción de un círculo, estableciendo un ángulo como hacíamos con los gradientes cónicos.

Por ejemplo, asumamos que tenemos 3 cajas .box dentro de un .container. Observa el siguiente fragmento de código que añadiremos al anterior donde damos estilo a cada uno de los elementos hijos .box:

.box-1 {
  background: indigo; /* violeta */
  offset-path: ray(90deg closest-side);
}
.box-2 {
  background: crimson; /* rojo */
  offset-path: ray(180deg closest-side);
}
.box-3 {
  background: deeppink; /* rosa */
  offset-path: ray(245deg closest-side);
}

En cada uno de estos elementos hijos .box, hemos definido que se mueva en una dirección indicada por la función ray(), que indica el ángulo de dirección e incluso el tamaño que tendrá con palabras clave como closest-side, closest-corner, farthest-side, farthest-corner o sides:

La propiedad offset-path con la función ray()

Observa que las cajas .box quedan ligeramente fuera del círculo rojo. Si añadimos la palabra clave contain como último parámetro de la función ray(), conseguiremos que se ajusten al interior del círculo. Quedaría como podemos ver en la siguiente demo:

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  background: #aaa;
  min-height: 400px;
}

.box {
  width: 50px;
  height: 50px;
  animation: move 2s alternate infinite;
}

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

.box-1 {
  background: indigo; /* violeta */
  offset-path: ray(90deg closest-side);
}
.box-2 {
  background: crimson; /* rojo */
  offset-path: ray(180deg closest-side);
}
.box-3 {
  background: deeppink; /* rosa */
  offset-path: ray(245deg closest-side);
}
<div class="container">
  <div class="box box-1"></div>
  <div class="box box-2"></div>
  <div class="box box-3"></div>
</div>

Si no sabes como funcionan los parámetros de mencionados anteriormente, echa un vistazo a formas y tamaños del gradiente radial, ya que lo explicamos ahí más detalladamente.

Usando trayectos con path()

Otro caso, quizás el más utilizado, es el de definir trayectos con la función path(), que no es más que una especie de mezcla entre CSS y SVG. Con la función path() se puede declarar en un el trayecto o ruta que normalmente se declarar en un SVG mediante el atributo d de una etiqueta <path>. De esta forma, puedes definir trayectos muy específicos, con líneas, curvas y formas mucho más complejas (o imposibles) de hacer en HTML.

Por ejemplo, observa este fragmento de código, donde a parte del tamaño, y animación que ya teníamos, aplicamos una función path() en la propiedad offset-path:

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  background: #aaa;
  min-height: 400px;
}

.box {
  width: 50px;
  height: 50px;
  animation: move 2s alternate infinite;
  background: indigo url("robot.png");
  background-size: 50px;
  offset-path: path("m 0 0 h 150 v 100");
}

@keyframes move {
  0% { offset-distance: 0%; }
  100% { offset-distance: 100%; }
}
<div class="container">
  <div class="box"></div>
</div>

Veamos esos cambios detalladamente:

  • En lugar de cuadrados de colores, vamos a establecer una imagen de un robot de fondo.
  • Le aplicamos el mismo tamaño del contenedor con el background-size.
  • Creamos un trayecto m 0 0 h 150 v 100.

Ese trayecto significa que lo colocamos en las coordenadas 0x0, que lo movemos 150px en horizontal y que lo movemos 100px en vertical. Puedes comprobar este trayecto con un editor SVG o con este editor online llamado SVG Path Editor. Hemos creado un trayecto desde la posición original del .box hasta el final del trayecto definido. El resultado es el siguiente:

La propiedad offset-path con la función path()

Gracias a la animación, que analizaremos más adelante, la caja .box se moverá desde su posición inicial hasta el final del trayecto, de forma repetitiva.

Usando trayectos con url()

El caso de la función url() es exactamente el mismo que el del caso anterior, con algunas diferencias:

  • La función url() define el fichero de imágen SVG y el id a usar: url("image.svg#pathname").
  • El navegador buscará la ruta y la imagen indicada. En el ejemplo: image.svg.
  • Una vez abierto este recurso, buscará un <path> con el id indicado. En el ejemplo: id="pathname".
  • Utilizará el trayecto definido en el atributo d de esa etiqueta SVG.

Posición actual en el trayecto

La propiedad offset-distance nos permite indicar el tamaño o porcentaje recorrido en el trayecto desde la posición inicial hasta la posición final. En los ejemplos anteriores la hemos utilizado en la animación, es la propiedad que se encarga de mover el elemento a través del trayecto.

Lo más cómodo es establecer la distancia con un porcentaje:

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

En este ejemplo, establecemos que la animación empieza con el elemento al inicio del recorrido (0%) y termina al final del recorrido (100%).

Rotación del elemento

La propiedad offset-rotate es una propiedad que se encarga de definir como vamos a rotar el elemento respecto a su trayecto. Por defecto, el valor utilizado en esta propiedad es auto, por lo que veremos que al añadir un trayecto, el elemento se puede ver rotado.

Por ejemplo, si añadimos la propiedad offset-rotate con el valor 0deg, estaremos indicando al navegador que a ese elemento no queremos que se aplique ningún tipo de rotación.

Los valores que puede tomar son los siguientes:

Valor Descripción
auto El valor auto es el valor por defecto. El elemento rota según el trayecto definido.
auto Prefijado del valor auto, se le suma el ángulo indicado en .
reverse El valor reverse es justo el inverso al valor auto.
reverse Idem a auto , pero justo el valor inverso.
Se establece exactamente el ángulo de rotación de .

Veamos un ejemplo utilizando la propiedad offset-rotate en la animación definida:

@keyframes move {
  0% {
    offset-distance: 0%;
    offset-rotate: 90deg;
  }
  100% {
    offset-distance: 100%;
    offset-rotate: 0deg;
  }
}

Punto de anclaje

Mediante la propiedad offset-anchor podemos establecer un punto de origen para el elemento que se mueve en el trayecto. Por ejemplo, observa la siguiente imagen y ten en cuenta los valores indicados de la propiedad a la derecha (valor de eje X, valor de eje Y).

  • El primero es 100% 100%: El punto que se moverá por el trayecto es la esquina inferior-derecha.
  • El segundo es 50% 50%: El punto que se moverá por el trayecto es el centro de la imagen.
  • El tercero es 50% 0%: El punto que se moverá por el trayecto es la antena del robot.

La propiedad offset-anchor

Aunque aparentemente el soporte de esta característica es bastante pobre, parece que Chrome está comenzando a soportarlo en recientes versiones:

Punto de inicio en el trayecto

Por otro lado, la propiedad offset-position nos permite establecer la posición inicial donde comenzará el punto inicial del trayecto. Esto puede ser útil para posicionar los trayectos, en el caso de que no se ajusten exactamente a la posición de entrada que se define en el elemento.

Valor Descripción
auto El valor auto es el valor por defecto.
Indicamos la distancia a mover la posición inicial del trayecto.

Ten en cuenta que de tener el elemento con position a static, los valores de offset-position no se tendrán en cuenta y serán ignorados.

Atajo: La propiedad offset

Como muchas otras propiedades CSS, podemos utilizar la propiedad de atajo offset que nos permiten escribir todas las propiedades individuales que se necesiten en una misma propiedad:

.box {
  /* offset: <position> <path> <distance> <rotate> / <anchor>; */

  offset: auto path("m 0 0 h 100 v 200") 0% 45deg / 50% 50%;
  offset: ray(45deg closest-side) / 40px 20px;
}

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev