offset-rotate, offset-anchor y el orden de las transformaciones
Orientar el elemento con la tangente, elegir qué punto suyo viaja por el trazado, y saber exactamente en qué punto del pipeline de transformaciones entra el recorrido.
Con offset-path y offset-distance ya sabes colocar un elemento sobre una curva, pero un elemento colocado no es un elemento animado: falta decidir hacia dónde mira y qué parte de él es la que realmente viaja. Esas dos decisiones son offset-rotate y offset-anchor, y las dos se resuelven en una sola matriz que el motor inserta en un lugar muy concreto de la cadena de transformaciones. Saber cuál es ese lugar es lo que separa componer recorridos con transformaciones de pelearse con ellas.
- Orientar un elemento con la tangente del trazado y desviarlo un ángulo fijo.
- Elegir el punto de anclaje correcto y explicar de dónde saca su valor por defecto.
- Situar la transformación de desplazamiento en el orden de composición de CSS.
- Combinar un recorrido con
rotate,scaleytransformsin que se estorben.
offset-rotate: seguir la tangente o no seguirla
El valor inicial es auto, y por eso mucha gente descubre esta propiedad al preguntarse por qué su icono gira solo. auto rota el elemento el ángulo de la dirección del trazado en cada punto, medido respecto al eje x positivo. Es lo correcto para un avión, un coche o una flecha; es un desastre para una etiqueta de texto, que acabará boca abajo en la mitad inferior de una circunferencia.
Los valores son cuatro combinaciones:
/* Sigue la tangente. Es el valor inicial. */
.a { offset-rotate: auto; }
/* Sigue la tangente y ademas se desvia 90 grados. */
.b { offset-rotate: auto 90deg; }
/* Como auto pero mirando al lado contrario. Equivale a auto 180deg. */
.c { offset-rotate: reverse; }
/* Angulo constante: el elemento no gira al recorrer. */
.d { offset-rotate: 0deg; }
offset-rotate: 0deg es el que necesitas para que el elemento se traslade sin girar, y no es lo mismo que quitar la propiedad: quitarla la devuelve a auto. Este es el error más frecuente del módulo entero. Una tarjeta que recorre un arco y se inclina “sin motivo” siempre es un offset-rotate que nadie escribió.
La combinación auto <angle> es la que hace útil el conjunto. Los iconos rara vez apuntan a la derecha en su forma original: un avión dibujado apuntando hacia arriba necesita auto -90deg para que la punta coincida con la tangente. En vez de rotar el SVG en el editor, se corrige aquí y queda documentado.
.avion {
offset-path: url("#ruta");
/* El icono apunta hacia arriba en su sistema local. */
offset-rotate: auto -90deg;
animation: volar 6s linear infinite;
}
Como offset-rotate es interpolable, también puedes animarla por su cuenta: un objeto que recorre un trazado mientras gira sobre sí mismo es offset-distance y offset-rotate animándose a ritmos distintos, sin ninguna transformación de por medio.
offset-anchor: qué punto del elemento viaja
Un elemento tiene área; el trazado es una línea. offset-anchor decide qué punto del elemento es el que se pega a la línea. Acepta un <position> completo, con la misma gramática que background-position, o auto.
El valor por defecto es auto, y aquí está el detalle que la documentación resumida suele omitir: auto no significa “el centro”. Significa que el punto de anclaje es el que indique transform-origin, resuelto como posición contra la caja de referencia del elemento. Como transform-origin vale 50% 50% por defecto, el efecto habitual es el centro, pero en cuanto alguien toca transform-origin para otra cosa —un scale desde una esquina, por ejemplo— el punto de anclaje se mueve con él sin que nadie lo haya pedido. Cuando el anclaje importe, decláralo explícitamente.
/* La punta inferior del marcador es la que toca el trazado. */
.pin {
width: 20px;
height: 28px;
offset-path: url("#ruta");
offset-anchor: 50% 100%;
offset-rotate: 0deg;
}
Ese ejemplo es el patrón del marcador de mapa: el pin no gira, y lo que sigue la ruta es su punta, no su centro. Con offset-anchor: auto el pin flotaría medio cuerpo por encima de la línea.
Hay un caso especial: cuando offset-path es none, offset-anchor: auto toma su valor de offset-position en vez de transform-origin. Es coherente con el resto del sistema —sin trazado, lo único que hay es un punto— y explica que offset-position y offset-anchor funcionen juntos como un mecanismo de colocación puro, sin recorrido:
/* Colocar la esquina superior izquierda del elemento
en el punto 30% 70% del bloque contenedor, sin trazado. */
.marca {
offset-position: 30% 70%;
offset-anchor: left top;
}
Además de su papel como punto de partida, offset-position rellena el at <position> que omitas en circle(), ellipse() y ray(). Es decir, offset-path: circle(100px) con offset-position: 20% 40% centra la circunferencia en ese punto. Permite tener una regla común con la forma y variar solo el centro por elemento, que es el mismo patrón de reutilización que en ray().
Dónde entra el recorrido en la cadena de transformaciones
CSS tiene hoy cinco maneras de transformar un elemento y todas se combinan en una única matriz. El orden está definido por especificación y no es negociable:
flowchart TB a[Posicion que da el layout] --> b[Propiedad translate] b --> c[Propiedad rotate] c --> d[Propiedad scale] d --> e[Transformacion de desplazamiento del recorrido] e --> f[Propiedad transform] f --> g[Matriz final que se pinta] style e fill:#cba6f7,color:#11111b style f fill:#f9e2af,color:#11111b style g fill:#a6e3a1,color:#11111b
Las tres propiedades individuales —translate, rotate y scale— se aplican antes que el recorrido. La propiedad transform se aplica después. Esa asimetría es la herramienta más útil de toda la lección, porque te da dos ranuras con semánticas distintas para el mismo elemento.
Lo que pongas en rotate o en scale transforma el elemento en su sistema local, antes de que el recorrido lo coloque y lo oriente. Un rotate: 45deg es una inclinación propia del objeto que se suma a la orientación que le imponga la tangente. Un scale: 1.4 lo hace más grande sin tocar el recorrido, porque el recorrido se calcula después y no le afecta el tamaño.
Lo que pongas en transform se aplica después del recorrido, en el sistema ya trasladado y rotado por él. Un transform: translateX(20px) desplaza el elemento veinte píxeles en la dirección de la tangente, no hacia la derecha de la pantalla, porque el recorrido ya ha rotado el sistema de coordenadas. Eso es exactamente lo que quieres para separar un elemento de la línea de forma perpendicular a ella:
/* La etiqueta viaja por el trazado pero flota 24px
por encima de la linea, siempre perpendicular a ella. */
.etiqueta {
offset-path: url("#ruta");
offset-rotate: auto;
transform: translateY(-24px);
}
Con offset-rotate: auto el eje y local apunta perpendicular al trazado en cada punto, así que ese translateY sigue la curva como si la línea tuviera grosor. Escribir lo mismo sin el módulo exige recalcular la normal en cada frame.
El mismo mecanismo explica la receta para animar un recorrido y una escala a la vez sin que se peleen: pon el recorrido donde está y usa la propiedad individual scale para el tamaño, no transform: scale(). Al ir antes en la cadena, la escala no distorsiona el desplazamiento; al ser una propiedad independiente, no compite con transform por el mismo valor y no hay que recomponer la cadena entera en cada keyframe.
.burbuja {
offset-path: circle(120px at 50% 50%);
offset-rotate: 0deg;
animation: girar 5s linear infinite, latir 1.2s ease-in-out infinite alternate;
}
@keyframes girar { to { offset-distance: 100%; } }
@keyframes latir { to { scale: 1.35; } }
Dos animaciones sobre dos propiedades distintas, sin conflicto, sin recalcular nada. Antes de que scale fuera una propiedad propia esto exigía una sola animación que escribiera la cadena transform completa en cada keyframe.
Un offset-path distinto de none tiene todos los efectos secundarios de un transform: crea contexto de apilamiento, crea bloque contenedor para descendientes position: fixed, y afecta a cómo se resuelve z-index dentro del subárbol. Es información que el nombre de la propiedad no sugiere en absoluto y produce bugs que nadie relaciona con el recorrido. El síntoma clásico: un tooltip position: fixed dentro de un elemento con recorrido deja de posicionarse respecto al viewport y empieza a hacerlo respecto al elemento, con lo que aparece desplazado y se mueve con él. La regla práctica es tratar cualquier elemento con offset-path como si tuviera transform, porque a efectos de layout lo tiene: no metas dentro nada que necesite escapar del contexto, y saca los overlays a un portal.
Construye un indicador que recorra el borde redondeado de una tarjeta usando offset-path: border-box, con una cola de tres puntos que le sigan a distancia constante. Los tres puntos deben compartir todo salvo un animation-delay negativo, y ninguno debe girar. Después añade a la cabeza un transform: translateY() para que se despegue de la línea y comprueba que la separación es perpendicular al borde en todo el recorrido, incluidas las esquinas.