Last of the twelve animations.
Loading Animations (13 part series)
Animation
It's an animation of a waving flag.
Result:
Introduction
I created this animation in SVG, following a structure similar to the previous ones:
<!-- size definitions --><svg> <!-- background --> <rect></rect> <!-- twelve moving circles --> <circle> <!-- animation that moves the circle --> <animateMotion></animateMotion> </circle></svg>How it works
I have omitted some elements and attributes in the following blocks to keep the explanation more concise.
Size
I kept the same size used in the previous animations.
Result
Code
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200"> <rect width="199" height="199" x="0.5" y="0.5" fill="white" stroke="lightgray" strokeWidth="1" rx="6" /></svg>Path
The animation uses twelve paths arranged in 3 rows and 4 columns. I calculated them as follows:
const WIDTH = 200;const HEIGHT = 200;const MARGIN_X = WIDTH * 0.31;const MARGIN_Y = HEIGHT * 0.37;const ROW_COUNT = 3;const COL_COUNT = 4;const RADIUS_X = WIDTH * 0.04;const RADIUS_Y = HEIGHT * 0.04;const ROW_GAP = (HEIGHT - 2 * MARGIN_Y) / (ROW_COUNT - 1);const COL_GAP = (WIDTH - 2 * MARGIN_X) / (COL_COUNT - 1);const arcs = [];for (let row = 0; row < ROW_COUNT; row++) { const cy = MARGIN_Y + row * ROW_GAP; for (let col = 0; col < COL_COUNT; col++) { const cx = MARGIN_X + col * COL_GAP; const path = `M ${cx} ${cy - RADIUS_Y} ` + `A ${RADIUS_X} ${RADIUS_Y} 0 1 1 ${cx} ${cy + RADIUS_Y} ` + `A ${RADIUS_X} ${RADIUS_Y} 0 1 1 ${cx} ${cy - RADIUS_Y}`; arcs.push(path); }}/*[ 'M 62 66 A 8 8 0 1 1 62 82 A 8 8 0 1 1 62 66', 'M 87.33333333333333 66 A 8 8 0 1 1 87.33333333333333 82 A 8 8 0 1 1 87.33333333333333 66', 'M 112.66666666666666 66 A 8 8 0 1 1 112.66666666666666 82 A 8 8 0 1 1 112.66666666666666 66', 'M 138 66 A 8 8 0 1 1 138 82 A 8 8 0 1 1 138 66', 'M 62 92 A 8 8 0 1 1 62 108 A 8 8 0 1 1 62 92', 'M 87.33333333333333 92 A 8 8 0 1 1 87.33333333333333 108 A 8 8 0 1 1 87.33333333333333 92', 'M 112.66666666666666 92 A 8 8 0 1 1 112.66666666666666 108 A 8 8 0 1 1 112.66666666666666 92', 'M 138 92 A 8 8 0 1 1 138 108 A 8 8 0 1 1 138 92', 'M 62 118 A 8 8 0 1 1 62 134 A 8 8 0 1 1 62 118', 'M 87.33333333333333 118 A 8 8 0 1 1 87.33333333333333 134 A 8 8 0 1 1 87.33333333333333 118', 'M 112.66666666666666 118 A 8 8 0 1 1 112.66666666666666 134 A 8 8 0 1 1 112.66666666666666 118', 'M 138 118 A 8 8 0 1 1 138 134 A 8 8 0 1 1 138 118',]*/The center of the arc is defined in lines 19 and 22. It is the initial margin plus the offset based on the current line and column. They were positioned as follows:
Lines 25 and 26 define the first arc (the first half of the circle). They were drawn as follows:
Line 27 defines the second arc (second half of the circle). They were drawn as follows:
Result
Code
<svg> <rect></rect> <path fill="none" stroke="lightgray" stroke-width="1" d="M 62 66 A 8 8 0 1 1 62 82 A 8 8 0 1 1 62 66" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 87.33333333333333 66 A 8 8 0 1 1 87.33333333333333 82 A 8 8 0 1 1 87.33333333333333 66" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 112.66666666666666 66 A 8 8 0 1 1 112.66666666666666 82 A 8 8 0 1 1 112.66666666666666 66" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 138 66 A 8 8 0 1 1 138 82 A 8 8 0 1 1 138 66" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 62 92 A 8 8 0 1 1 62 108 A 8 8 0 1 1 62 92" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 87.33333333333333 92 A 8 8 0 1 1 87.33333333333333 108 A 8 8 0 1 1 87.33333333333333 92" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 112.66666666666666 92 A 8 8 0 1 1 112.66666666666666 108 A 8 8 0 1 1 112.66666666666666 92" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 138 92 A 8 8 0 1 1 138 108 A 8 8 0 1 1 138 92" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 62 118 A 8 8 0 1 1 62 134 A 8 8 0 1 1 62 118" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 87.33333333333333 118 A 8 8 0 1 1 87.33333333333333 134 A 8 8 0 1 1 87.33333333333333 118" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 112.66666666666666 118 A 8 8 0 1 1 112.66666666666666 134 A 8 8 0 1 1 112.66666666666666 118" ></path> <path fill="none" stroke="lightgray" stroke-width="1" d="M 138 118 A 8 8 0 1 1 138 134 A 8 8 0 1 1 138 118" ></path></svg>Circles and their animations
Animations that follow the path
Each circle uses an animateMotion element to follow the defined path. The only attribute that varies between circles is begin, which defines a delay for each circle to start its animation.
The circle elements share the following attributes:
fill="black": circle fill colorr="8": circle radius
The animateMotion elements share the following attributes:
dur="1100ms": total animation durationrepeatCount="indefinite": the animation repeats indefinitely
I calculated the start delay (begin) of each circle as follows:
const ROW_COUNT = 3;const COL_COUNT = 4;const DURATION = 1_100;const ROW_DELAY = DURATION * 0.08;const COL_DELAY = DURATION * 0.08;const begins = [];for (let row = 0; row < ROW_COUNT; row++) { const yBegin = row * ROW_DELAY; for (let col = 0; col < COL_COUNT; col++) { const xBegin = yBegin + col * COL_DELAY; const begin = `-${xBegin}ms`; begins.push(begin); }}/*[ '-0ms', '-88ms', '-176ms', '-264ms', '-88ms', '-176ms', '-264ms', '-352ms', '-176ms', '-264ms', '-352ms', '-440ms',]*/Lines 11 and 14 define the delays for the rows and columns. Each shift to the right and down adds delay. Since the delays are equal, the circles on the antidiagonal (top right corner to bottom left corner) have the same delay. For example:
Result
Code
<svg> <rect></rect> <path></path> <path></path> <path></path> <path></path> <path></path> <path></path> <path></path> <path></path> <path></path> <path></path> <path></path> <path></path> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 62 66 A 8 8 0 1 1 62 82 A 8 8 0 1 1 62 66 " begin="-0ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 87.33333333333333 66 A 8 8 0 1 1 87.33333333333333 82 A 8 8 0 1 1 87.33333333333333 66 " begin="-88ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 112.66666666666666 66 A 8 8 0 1 1 112.66666666666666 82 A 8 8 0 1 1 112.66666666666666 66 " begin="-176ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 138 66 A 8 8 0 1 1 138 82 A 8 8 0 1 1 138 66 " begin="-264ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 62 92 A 8 8 0 1 1 62 108 A 8 8 0 1 1 62 92 " begin="-88ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 87.33333333333333 92 A 8 8 0 1 1 87.33333333333333 108 A 8 8 0 1 1 87.33333333333333 92 " begin="-176ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 112.66666666666666 92 A 8 8 0 1 1 112.66666666666666 108 A 8 8 0 1 1 112.66666666666666 92 " begin="-264ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 138 92 A 8 8 0 1 1 138 108 A 8 8 0 1 1 138 92 " begin="-352ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 62 118 A 8 8 0 1 1 62 134 A 8 8 0 1 1 62 118 " begin="-176ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 87.33333333333333 118 A 8 8 0 1 1 87.33333333333333 134 A 8 8 0 1 1 87.33333333333333 118 " begin="-264ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 112.66666666666666 118 A 8 8 0 1 1 112.66666666666666 134 A 8 8 0 1 1 112.66666666666666 118 " begin="-352ms" repeatCount="indefinite" ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" path="M 138 118 A 8 8 0 1 1 138 134 A 8 8 0 1 1 138 118 " begin="-440ms" repeatCount="indefinite" ></animateMotion> </circle></svg>Conclusion
The December animation concludes the series by revisiting many of the concepts explored throughout the year. Using simple paths, organized in a grid, along with small temporal shifts, to suggest organic movement. By distributing these delays across rows and columns, the movement propagates through the grid, creating a continuous and interesting visual rhythm to observe.
With this, I close this cycle of experimentation. More than the final results, the value of this series lies in the process of better understanding SVG, testing ideas, and documenting each step along the way.
Result
Code
<svg id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200"> <rect id="rect" width="199" height="199" rx="6" x="0.5" y="0.5" fill="white" stroke-width="1" stroke="lightgray" ></rect> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-0ms" repeatCount="indefinite" path="M 62 66 A 8 8 0 1 1 62 82 A 8 8 0 1 1 62 66 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-88ms" repeatCount="indefinite" path="M 87.33333333333333 66 A 8 8 0 1 1 87.33333333333333 82 A 8 8 0 1 1 87.33333333333333 66 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-176ms" repeatCount="indefinite" path="M 112.66666666666666 66 A 8 8 0 1 1 112.66666666666666 82 A 8 8 0 1 1 112.66666666666666 66 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-264ms" repeatCount="indefinite" path="M 138 66 A 8 8 0 1 1 138 82 A 8 8 0 1 1 138 66 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-88ms" repeatCount="indefinite" path="M 62 92 A 8 8 0 1 1 62 108 A 8 8 0 1 1 62 92 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-176ms" repeatCount="indefinite" path="M 87.33333333333333 92 A 8 8 0 1 1 87.33333333333333 108 A 8 8 0 1 1 87.33333333333333 92 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-264ms" repeatCount="indefinite" path="M 112.66666666666666 92 A 8 8 0 1 1 112.66666666666666 108 A 8 8 0 1 1 112.66666666666666 92 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-352ms" repeatCount="indefinite" path="M 138 92 A 8 8 0 1 1 138 108 A 8 8 0 1 1 138 92 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-176ms" repeatCount="indefinite" path="M 62 118 A 8 8 0 1 1 62 134 A 8 8 0 1 1 62 118 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-264ms" repeatCount="indefinite" path="M 87.33333333333333 118 A 8 8 0 1 1 87.33333333333333 134 A 8 8 0 1 1 87.33333333333333 118 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-352ms" repeatCount="indefinite" path="M 112.66666666666666 118 A 8 8 0 1 1 112.66666666666666 134 A 8 8 0 1 1 112.66666666666666 118 " ></animateMotion> </circle> <circle r="8" fill="black"> <animateMotion dur="1100ms" begin="-440ms" repeatCount="indefinite" path="M 138 118 A 8 8 0 1 1 138 134 A 8 8 0 1 1 138 118 " ></animateMotion> </circle></svg>