← back to Wavy Loading Animationscompare with ground truth →
model output
Claude Sonnet 4.6
Wavy Loading Animations
A 0.74T 0.26
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>Ink Spinner</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9 body {
10 background: #ebebeb;
11 display: flex;
12 justify-content: center;
13 align-items: flex-start;
14 padding-top: 80px;
15 height: 100vh;
16 }
17 .spinner-container {
18 position: relative;
19 width: 40px;
20 height: 40px;
21 }
22 canvas {
23 position: absolute;
24 top: 0;
25 left: 0;
26 }
27</style>
28</head>
29<body>
30<div class="spinner-container">
31 <canvas id="spinner" width="40" height="40"></canvas>
32</div>
33
34<script>
35const canvas = document.getElementById('spinner');
36const ctx = canvas.getContext('2d');
37const W = canvas.width;
38const H = canvas.height;
39const cx = W / 2;
40const cy = H / 2;
41const R = 14;
42
43let startTime = null;
44const cycleDuration = 1200; // ms per full cycle
45
46function easeInOut(t) {
47 return t < 0.5 ? 2*t*t : -1+(4-2*t)*t;
48}
49
50function drawInkArc(progress) {
51 ctx.clearRect(0, 0, W, H);
52
53 // progress goes 0 -> 1 over one cycle
54 // The arc sweeps from a wedge to a full circle
55 // Leading angle rotates continuously
56
57 const rotAngle = progress * Math.PI * 2;
58
59 // Draw multiple segments with varying opacity to create ink brush effect
60 const segments = 60;
61 const arcLength = Math.PI * 1.7; // ~300 degrees of arc visible
62
63 for (let i = 0; i < segments; i++) {
64 const t = i / segments;
65 const angle = rotAngle - t * arcLength;
66 const nextAngle = rotAngle - (t + 1/segments) * arcLength;
67
68 // Opacity: full at leading edge (t=0), fading at tail (t=1)
69 const opacity = Math.pow(1 - t, 0.5);
70
71 // Line width: thicker at leading edge
72 const lineWidth = 3.5 * (1 - t * 0.6) + 0.5;
73
74 // Color: dark at leading edge, lighter at tail
75 const darkness = Math.floor(20 + t * 180);
76
77 ctx.beginPath();
78 ctx.arc(cx, cy, R, angle, nextAngle, true);
79 ctx.strokeStyle = `rgba(${darkness}, ${darkness}, ${darkness}, ${opacity})`;
80 ctx.lineWidth = lineWidth;
81 ctx.lineCap = 'round';
82 ctx.stroke();
83 }
84
85 // Draw a solid dark blob at the leading edge
86 const leadX = cx + R * Math.cos(rotAngle);
87 const leadY = cy + R * Math.sin(rotAngle);
88
89 ctx.beginPath();
90 ctx.arc(leadX, leadY, 3, 0, Math.PI * 2);
91 ctx.fillStyle = 'rgba(10, 10, 10, 0.9)';
92 ctx.fill();
93}
94
95function drawInitialWedge(progress) {
96 // progress 0->1: draw expanding wedge
97 ctx.clearRect(0, 0, W, H);
98
99 const angle = progress * Math.PI * 0.8; // wedge opens up
100
101 ctx.beginPath();
102 ctx.moveTo(cx, cy);
103 ctx.arc(cx, cy, R * progress * 0.8 + R * 0.2, -Math.PI/2, -Math.PI/2 + angle);
104 ctx.closePath();
105 ctx.fillStyle = '#111';
106 ctx.fill();
107}
108
109function animate(timestamp) {
110 if (!startTime) startTime = timestamp;
111 const elapsed = timestamp - startTime;
112
113 // Phase 1: 0-400ms - wedge forms and transitions to arc
114 // Phase 2: 400ms+ - continuous spinning arc
115
116 if (elapsed < 400) {
117 const p = elapsed / 400;
118 drawInitialWedge(p);
119 } else {
120 const cycleElapsed = (elapsed - 400) % cycleDuration;
121 const progress = cycleElapsed / cycleDuration;
122 drawInkArc(progress);
123 }
124
125 requestAnimationFrame(animate);
126}
127
128requestAnimationFrame(animate);
129</script>
130</body>
131</html>