Shots in this scene
Motion Along a Path
In one line
offset-path gives an element a track, offset-distance says how far along it is, and offset-rotate turns it to face the way it travels. The path's numbers are in CSS pixels for an HTML element and in user units for an SVG shape.
Think of it like this
A toy train on a track. You do not tell the train its x and y at every moment. You say how far along the track it is, and the track decides where it is and which way it faces, even round bends.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- offset-path
- The track an element follows, written as
path("…")with the same text as an SVGdattribute, orurl(#id)for an SVG shape. - offset-distance
- How far along the track the element is, from
0%(the start) to100%(the end of an open path). - offset-rotate
- How the element turns on the track:
autofaces along the path, an angle keeps a fixed turn,auto 90degcombines both. - tangent
- The direction a curve points at one spot.
offset-rotate: autoturns the element to the tangent. - open path
- A path that does not end where it started. Past
100%an element on it stays at the end.
Step by step
01Read where the dots sit
"An HTML dot and an SVG circle" prints three points: the middle of the route on screen (183, 47), the SVG circle at 50% (184, 48), and the HTML dot at 50% (91, 24). The script sets offset-distance: 50% on both, reads the boxes, and clears it again.
02Ride it
Press Ride. Two offset-distance tracks run for 2000 ms. Pause at 1000 ms and step: the orange circle travels along the line, and the blue HTML dot travels along the same shape at half the size, in the top-left corner.
03Turn the arrow
In "An arrow on a path that crosses itself" press Ride and pause at 1500 ms: the arrow sits on the upward leg, pointing up. Press Turn and ride again to see 0deg and auto 90deg: both point right at that spot.
04Check the crossing
The console prints the path length 306.27, the two distances 18.5% and 81.5%, and the same screen point for both blue dots: (71, 61).
Try it yourself
- 1
Fix the HTML dot's scale
In the first example change the SVG to
width="150" height="60"and the.wrapto the same size. The drawing is no longer scaled, so the HTML dot and the circle both land within one pixel of the middle of the route: (91, 24) and (92, 25). Change the script's two* 2to* 1to print the middle at the same scale (the console line still says 2x, but it now prints91, 24). - 2
Overshoot
In the second example change the
100%in the Ride animation to150%and ride. Pause at 2900 ms: the arrow is at the end of the path, pointing down and to the left along the last leg. It does not wrap round. - 3
Slow it to a crawl at the crossing
Pause the second example and step through 18.5% and 81.5% of the ride: the arrow passes the same point twice, once heading down-right and once heading down-left. Nothing in the animation knows it is the same point.
Code & diagrams
The same path text on both. The drawing is shown at twice its size. The HTML dot rides in unscaled pixels.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
middle of the route on screen: 183, 47 (the drawing is shown at 2x)
SVG circle at 50%: 184, 48
HTML dot at 50%: 91, 24Press Ride, then use Turn to try `auto`, `0deg` and `auto 90deg`.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
path length 306.27, crossing reached at 18.5% and again at 81.5%
first pass at 71, 61, second pass at 71, 61Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Break the path text
In the second example change #arrow's offset-path: path("M30 20 …") to path("L30 20 L110 100 L110 20 L30 100"), with no M.
Myth vs fact
Myth
The path inside offset-path scales with the SVG, like the path I can see.
Fact
Only on an SVG shape. An HTML element reads CSS pixels, so a drawing shown at 2× leaves it at half the distance (measured: (91, 24) instead of (183, 47)).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The track itself is not drawn by
offset-path. If the route should be visible, draw the same path as a normal stroke (Topic 13.2) and make the two share one string, so they cannot drift apart. - ▸
An
offset-distancetrack is amber: measured in headless Chromium, it repaints every frame but needs no layout. Cheap for one dot; check the track for a hundred. - ▸
A dot that rides a route that is also drawing should sit at an
offset-distanceequal to the draw progress. Both are length fractions, so one scroll timeline can drive both (Phase 12).
Remember this
- 1
The problem this solves: moving something along a curve used to need JavaScript maths or a chain of keyframes. Three properties do it:
offset-path(the track),offset-distance(how far along, from0%to100%of the track's length) andoffset-rotate(which way the element faces). You animate onlyoffset-distance. - 2
**
offset-path: path("M10 50 C40 5 …")** takes the same path text as an SVGdattribute. Reuse the text of the drawn route so the element rides the line you see. On an SVG shape Chromium also acceptsoffset-path: url(#route): in a test the dot sat 0.1 units from the real midpoint. - 3
The coordinate trap. For an HTML element the path's numbers are CSS pixels. For an SVG shape they are user units, so they scale with the
viewBox. In the first example the same path text is used on both, with the drawing shown at 2×. The middle of the route is at (183, 47) on screen, the SVG circle at 50% sat at (184, 48), and the HTML dot at (91, 24): half, because its path was not scaled. Over a scaled drawing, scale the numbers yourself, or ride an SVG shape inside the drawing. - 4
A shape is placed by its own (0, 0). An SVG shape rides with its coordinate origin on the path, so draw it around (0, 0): the circle has no
cxand the arrow isM-8 -6 L10 0 L-8 6 Z. An HTML element is placed by its centre (its transform origin) by default. - 5
**
offset-rotate.**autoturns the element so its x axis points along the path.0degkeeps its own angle.auto 90degis auto plus 90 degrees, for a shape drawn pointing up. On the upward leg of the second example,autopoints the arrow up, and both0degandauto 90degpoint it right. - 6
Crossing paths and the ends.
offset-distancecounts length, not position, so a path that crosses itself is no problem: the crossing of the second example's X shape is reached at 18.5 percent and again at 81.5 percent, and two dots placed at those distances sat at the same screen point, (71, 61). On an open path a distance above100%stays at the end (tryIt 2). - 7
A wrong path fails silently. A path text that does not parse makes the whole declaration invalid, and the browser drops it without a message: the arrow stays at the top-left corner, although the timeline still lists an
offset-distancetrack (breakIt).
Explain it without notes
Why does an HTML dot miss the route when the SVG is scaled, but an SVG circle does not?
What does offset-rotate: auto do, and when do you use auto 90deg?
Practice
Make a small circle #bike ride the route #route forever in 4 seconds, linear, and stop for reduced motion at the end of the route.
A paper plane drawn pointing up should follow a curve and face the way it flies. What do you write?
Done when you can
You can animate an element along a path with only
offset-distanceYou can say why an HTML element misses a scaled drawing
You can pick
auto, a fixed angle orauto 90degYou know a bad path is ignored without an error