← back to Wavy Loading Animationscompare with ground truth →
model output
Claude Sonnet 4.6
Wavy Loading Animations
A 0.66T 0.17
1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<title>Loader Animation</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9
10 body {
11 background: #ebebeb;
12 display: flex;
13 justify-content: center;
14 align-items: flex-start;
15 padding-top: 60px;
16 height: 100vh;
17 }
18
19 .loader {
20 position: relative;
21 width: 80px;
22 height: 30px;
23 display: flex;
24 align-items: center;
25 justify-content: center;
26 }
27
28 canvas {
29 display: block;
30 }
31</style>
32</head>
33<body>
34<canvas id="c" width="1024" height="768"></canvas>
35
36<script>
37const canvas = document.getElementById('c');
38const ctx = canvas.getContext('2d');
39
40canvas.style.background = '#ebebeb';
41
42const cx = 512;
43const cy = 75;
44
45let t = 0;
46const CYCLE = 120; // frames per full cycle
47
48function easeInOut(t) {
49 return t < 0.5 ? 2*t*t : -1+(4-2*t)*t;
50}
51
52function drawDashes() {
53 // Horizontal dashes: series of small rectangles
54 ctx.fillStyle = '#888';
55 const dashW = 4;
56 const dashH = 2;
57 const gap = 2;
58 const count = 10;
59 const totalW = count * dashW + (count-1) * gap;
60 const startX = cx - totalW/2;
61 for (let i = 0; i < count; i++) {
62 ctx.fillRect(startX + i*(dashW+gap), cy - dashH/2, dashW, dashH);
63 }
64}
65
66function drawArrow(progress) {
67 // Triangle pointing right with dashes trailing left
68 ctx.fillStyle = '#666';
69 const tipX = cx + 10;
70 const midY = cy;
71 const h = 18;
72 const w = 18;
73
74 // Triangle
75 ctx.beginPath();
76 ctx.moveTo(tipX, midY);
77 ctx.lineTo(tipX - w, midY - h/2);
78 ctx.lineTo(tipX - w, midY + h/2);
79 ctx.closePath();
80 ctx.fill();
81
82 // Trailing dashes
83 ctx.fillStyle = '#888';
84 const dashW = 4;
85 const dashH = 2;
86 const gap = 3;
87 const count = 4;
88 for (let i = 0; i < count; i++) {
89 ctx.fillRect(tipX - w - 8 - i*(dashW+gap) - dashW, midY - dashH/2, dashW, dashH);
90 }
91}
92
93function drawBarrel(progress) {
94 // Barrel: vertical stripes forming a barrel/cylinder shape
95 const stripeCount = 9;
96 const maxH = 36;
97 const stripeW = 4;
98 const gap = 1;
99 const totalW = stripeCount * stripeW + (stripeCount-1) * gap;
100 const startX = cx - totalW/2 + 5;
101
102 for (let i = 0; i < stripeCount; i++) {
103 // Barrel shape: middle stripes taller
104 const mid = (stripeCount-1)/2;
105 const dist = Math.abs(i - mid) / mid;
106 const h = maxH * Math.sqrt(1 - dist*dist * 0.5);
107
108 // Color: lighter in middle, darker on edges
109 const lightness = Math.floor(160 + 30 * (1 - dist));
110 ctx.fillStyle = `rgb(${lightness},${lightness},${lightness})`;
111
112 const x = startX + i * (stripeW + gap);
113 ctx.fillRect(x, cy - h/2, stripeW, h);
114 }
115}
116
117function drawExpandedDashes() {
118 // Dashes spread out to left, bars on right
119 ctx.fillStyle = '#888';
120 const dashW = 4;
121 const dashH = 2;
122 const gap = 3;
123 const dashCount = 3;
124 const barCount = 8;
125 const barW = 4;
126 const barGap = 1;
127
128 // Bars on right side
129 const maxH = 36;
130 const totalBarW = barCount * barW + (barCount-1) * barGap;
131 const barStartX = cx - totalBarW/2 + 15;
132
133 for (let i = 0; i < barCount; i++) {
134 const mid = (barCount-1)/2;
135 const dist = Math.abs(i - mid) / (mid || 1);
136 const h = maxH * Math.sqrt(Math.max(0, 1 - dist*dist * 0.4));
137 const lightness = Math.floor(155 + 25 * (1 - dist));
138 ctx.fillStyle = `rgb(${lightness},${lightness},${lightness})`;
139 ctx.fillRect(barStartX + i*(barW+barGap), cy - h/2, barW, h);
140 }
141
142 // Dashes on left
143 ctx.fillStyle = '#888';
144 for (let i = 0; i < dashCount; i++) {
145 ctx.fillRect(barStartX - 10 - i*(dashW+gap) - dashW, cy - dashH/2, dashW, dashH);
146 }
147}
148
149function render() {
150 ctx.clearRect(0, 0, canvas.width, canvas.height);
151
152 const phase = (t % CYCLE) / CYCLE;
153
154 // Phase 0.0 - 0.15: dashes
155 // Phase 0.15 - 0.35: transition to arrow
156 // Phase 0.35 - 0.5: arrow
157 // Phase 0.5 - 0.65: transition to barrel
158 // Phase 0.65 - 0.85: barrel with dashes
159 // Phase 0.85 - 1.0: back to dashes
160
161 if (phase < 0.15) {
162 drawDashes();
163 } else if (phase < 0.35) {
164 drawArrow((phase - 0.15) / 0.2);
165 } else if (phase < 0.5) {
166 drawArrow(1);
167 } else if (phase < 0.65) {
168 drawBarrel((phase - 0.5) / 0.15);
169 } else if (phase < 0.85) {
170 drawExpandedDashes();
171 } else {
172 drawArrow(1 - (phase - 0.85) / 0.15);
173 }
174
175 t++;
176 requestAnimationFrame(render);
177}
178
179// Better interpretation: the animation loops through 3 states
180// Let's use a simpler direct approach based on what we see
181
182let startTime = null;
183const PERIOD = 1200; // ms per cycle
184
185function renderTime(ts) {
186 if (!startTime) startTime = ts;
187 const elapsed = (ts - startTime) % PERIOD;
188 const phase = elapsed / PERIOD;
189
190 ctx.clearRect(0, 0, canvas.width, canvas.height);
191
192 if (phase < 0.25) {
193 // Dashes
194 drawDashes();
195 } else if (phase < 0.42) {
196 // Arrow
197 drawArrow(1);
198 } else if (phase < 0.58) {
199 // Barrel
200 drawBarrel(1);
201 } else if (phase < 0.75) {
202 // Expanded dashes (bars + dashes)
203 drawExpandedDashes();
204 } else if (phase < 0.88) {
205 // Arrow again
206 drawArrow(1);
207 } else {
208 // Back to barrel
209 drawBarrel(1);
210 }
211
212 requestAnimationFrame(renderTime);
213}
214
215requestAnimationFrame(renderTime);
216</script>
217</body>
218</html>