Description
This project showcases an animated hourglass preloader created entirely with SVG and CSS, without any JavaScript.
The animation simulates realistic sand flow, glass flipping, glare highlights, and circular motion strokes—making it ideal for loading screens, splash screens, and creative UI transitions.
Key highlights:
- Pure SVG-based vector animation
- CSS-driven timing and easing
- Sand grain, mound, and drop simulation
- Rotating circular motion indicators
- Automatic dark/light theme adaptation
- Scalable and resolution-independent design
The animation loops seamlessly and is optimized for modern browsers.
HTML
1<!DOCTYPE html>
2<html lang="en" >
3<head>
4 <meta charset="UTF-8">
5 <title>Hourglass Preloader | @coding.stella</title>
6 <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><link rel="stylesheet" href="./style.css">
7
8</head>
9<body>
10 <svg
11 class="hourglass"
12 viewBox="0 0 56 56"
13 width="56px"
14 height="56px"
15 role="img"
16 aria-label="Hourglass being flipped clockwise and circled by three white curves fading in and out"
17 >
18 <clipPath id="sand-mound-top">
19 <path
20 class="hourglass__sand-mound-top"
21 d="M 14.613 13.087 C 15.814 12.059 19.3 8.039 20.3 6.539 C 21.5 4.789 21.5 2.039 21.5 2.039 L 3 2.039 C 3 2.039 3 4.789 4.2 6.539 C 5.2 8.039 8.686 12.059 9.887 13.087 C 11 14.039 12.25 14.039 12.25 14.039 C 12.25 14.039 13.5 14.039 14.613 13.087 Z"
22 />
23 </clipPath>
24 <clipPath id="sand-mound-bottom">
25 <path
26 class="hourglass__sand-mound-bottom"
27 d="M 14.613 20.452 C 15.814 21.48 19.3 25.5 20.3 27 C 21.5 28.75 21.5 31.5 21.5 31.5 L 3 31.5 C 3 31.5 3 28.75 4.2 27 C 5.2 25.5 8.686 21.48 9.887 20.452 C 11 19.5 12.25 19.5 12.25 19.5 C 12.25 19.5 13.5 19.5 14.613 20.452 Z"
28 />
29 </clipPath>
30 <g transform="translate(2,2)">
31 <g
32 fill="none"
33 stroke="hsl(0,0%,100%)"
34 stroke-dasharray="153.94 153.94"
35 stroke-dashoffset="153.94"
36 stroke-linecap="round"
37 transform="rotate(-90,26,26)"
38 >
39 <circle
40 class="hourglass__motion-thick"
41 stroke-width="2.5"
42 cx="26"
43 cy="26"
44 r="24.5"
45 transform="rotate(0,26,26)"
46 />
47 <circle
48 class="hourglass__motion-medium"
49 stroke-width="1.75"
50 cx="26"
51 cy="26"
52 r="24.5"
53 transform="rotate(90,26,26)"
54 />
55 <circle
56 class="hourglass__motion-thin"
57 stroke-width="1"
58 cx="26"
59 cy="26"
60 r="24.5"
61 transform="rotate(180,26,26)"
62 />
63 </g>
64 <g class="hourglass__model" transform="translate(13.75,9.25)">
65 <!-- glass -->
66 <path
67 fill="hsl(var(--hue),90%,85%)"
68 d="M 1.5 2 L 23 2 C 23 2 22.5 8.5 19 12 C 16 15.5 13.5 13.5 13.5 16.75 C 13.5 20 16 18 19 21.5 C 22.5 25 23 31.5 23 31.5 L 1.5 31.5 C 1.5 31.5 2 25 5.5 21.5 C 8.5 18 11 20 11 16.75 C 11 13.5 8.5 15.5 5.5 12 C 2 8.5 1.5 2 1.5 2 Z"
69 />
70 <!-- sand -->
71 <g stroke="hsl(35,90%,90%)" stroke-linecap="round">
72 <line
73 class="hourglass__sand-grain-left"
74 stroke-width="1"
75 stroke-dasharray="0.25 33.75"
76 x1="12"
77 y1="15.75"
78 x2="12"
79 y2="20.75"
80 />
81 <line
82 class="hourglass__sand-grain-right"
83 stroke-width="1"
84 stroke-dasharray="0.25 33.75"
85 x1="12.5"
86 y1="16.75"
87 x2="12.5"
88 y2="21.75"
89 />
90 <line
91 class="hourglass__sand-drop"
92 stroke-width="1"
93 stroke-dasharray="0.5 107.5"
94 x1="12.25"
95 y1="18"
96 x2="12.25"
97 y2="31.5"
98 />
99 <line
100 class="hourglass__sand-fill"
101 stroke-width="1.5"
102 stroke-dasharray="54 54"
103 x1="12.25"
104 y1="14.75"
105 x2="12.25"
106 y2="31.5"
107 />
108 <line
109 class="hourglass__sand-line-left"
110 stroke="hsl(35,90%,83%)"
111 stroke-width="1"
112 stroke-dasharray="1 107"
113 x1="12"
114 y1="16"
115 x2="12"
116 y2="31.5"
117 />
118 <line
119 class="hourglass__sand-line-right"
120 stroke="hsl(35,90%,83%)"
121 stroke-width="1"
122 stroke-dasharray="12 96"
123 x1="12.5"
124 y1="16"
125 x2="12.5"
126 y2="31.5"
127 />
128 <!-- mounds -->
129 <g fill="hsl(35,90%,90%)" stroke-width="0">
130 <path
131 clip-path="url(#sand-mound-top)"
132 d="M 12.25 15 L 15.392 13.486 C 21.737 11.168 22.5 2 22.5 2 L 2 2.013 C 2 2.013 2.753 11.046 9.009 13.438 L 12.25 15 Z"
133 />
134 <path
135 clip-path="url(#sand-mound-bottom)"
136 d="M 12.25 18.5 L 15.392 20.014 C 21.737 22.332 22.5 31.5 22.5 31.5 L 2 31.487 C 2 31.487 2.753 22.454 9.009 20.062 Z"
137 />
138 </g>
139 </g>
140 <!-- glass glare -->
141 <g fill="none" opacity="0.7" stroke-linecap="round" stroke-width="2">
142 <path
143 class="hourglass__glare-top"
144 stroke="hsl(0,0%,100%)"
145 d="M 19.437 3.421 C 19.437 3.421 19.671 6.454 17.914 8.846 C 16.157 11.238 14.5 11.5 14.5 11.5"
146 />
147 <path
148 class="hourglass__glare-bottom"
149 stroke="hsla(0,0%,100%,0)"
150 d="M 19.437 3.421 C 19.437 3.421 19.671 6.454 17.914 8.846 C 16.157 11.238 14.5 11.5 14.5 11.5"
151 transform="rotate(180,12.25,16.75)"
152 />
153 </g>
154 <!-- caps -->
155 <rect fill="hsl(var(--hue),90%,50%)" width="24.5" height="2" />
156 <rect
157 fill="hsl(var(--hue),90%,57.5%)"
158 rx="0.5"
159 ry="0.5"
160 x="2.5"
161 y="0.5"
162 width="19.5"
163 height="1"
164 />
165 <rect fill="hsl(var(--hue),90%,50%)" y="31.5" width="24.5" height="2" />
166 <rect
167 fill="hsl(var(--hue),90%,57.5%)"
168 rx="0.5"
169 ry="0.5"
170 x="2.5"
171 y="32"
172 width="19.5"
173 height="1"
174 />
175 </g>
176 </g>
177 </svg>
178
179</body>
180</html>
🎨 CSS Code
1* { 2 border: 0; 3 box-sizing: border-box; 4 margin: 0; 5 padding: 0; 6} 7 8:root { 9 --hue: 223; 10 --bg: hsl(var(--hue), 90%, 70%); 11 --fg: hsl(var(--hue), 90%, 10%); 12 --primary: hsl(var(--hue), 90%, 50%); 13 --trans-dur: 0.3s; 14 font-size: clamp(1rem, 0.95rem + 0.25vw, 1.25rem); 15} 16 17body { 18 background-color: var(--bg); 19 color: var(--fg); 20 display: flex; 21 font: 1em/1.5 sans-serif; 22 height: 100vh; 23 transition: 24 background-color var(--trans-dur), 25 color var(--trans-dur); 26} 27 28.hourglass { 29 --dur: 2s; 30 display: block; 31 margin: auto; 32 width: 14em; 33 height: auto; 34} 35.hourglass__glare-top, 36.hourglass__glare-bottom, 37.hourglass__model, 38.hourglass__motion-thick, 39.hourglass__motion-medium, 40.hourglass__motion-thin, 41.hourglass__sand-drop, 42.hourglass__sand-fill, 43.hourglass__sand-grain-left, 44.hourglass__sand-grain-right, 45.hourglass__sand-line-left, 46.hourglass__sand-line-right, 47.hourglass__sand-mound-top, 48.hourglass__sand-mound-bottom { 49 animation-duration: var(--dur); 50 animation-timing-function: cubic-bezier(0.83, 0, 0.17, 1); 51 animation-iteration-count: infinite; 52} 53.hourglass__glare-top { 54 animation-name: glare-top; 55} 56.hourglass__glare-bottom { 57 animation-name: glare-bottom; 58} 59.hourglass__model { 60 animation-name: hourglass-flip; 61 transform-origin: 12.25px 16.75px; 62} 63.hourglass__motion-thick, 64.hourglass__motion-medium, 65.hourglass__motion-thin { 66 transform-origin: 26px 26px; 67} 68.hourglass__motion-thick { 69 animation-name: motion-thick; 70} 71.hourglass__motion-medium { 72 animation-name: motion-medium; 73} 74.hourglass__motion-thin { 75 animation-name: motion-thin; 76} 77.hourglass__sand-drop { 78 animation-name: sand-drop; 79} 80.hourglass__sand-fill { 81 animation-name: sand-fill; 82} 83.hourglass__sand-grain-left { 84 animation-name: sand-grain-left; 85} 86.hourglass__sand-grain-right { 87 animation-name: sand-grain-right; 88} 89.hourglass__sand-line-left { 90 animation-name: sand-line-left; 91} 92.hourglass__sand-line-right { 93 animation-name: sand-line-right; 94} 95.hourglass__sand-mound-top { 96 animation-name: sand-mound-top; 97} 98.hourglass__sand-mound-bottom { 99 animation-name: sand-mound-bottom; 100 transform-origin: 12.25px 31.5px; 101} 102 103/* Dark theme */ 104@media (prefers-color-scheme: dark) { 105 :root { 106 --bg: hsl(var(--hue), 90%, 10%); 107 --fg: hsl(var(--hue), 90%, 90%); 108 } 109} 110/* Animation */ 111@keyframes hourglass-flip { 112 from { 113 transform: translate(13.75px, 9.25px) rotate(-180deg); 114 } 115 24%, 116 to { 117 transform: translate(13.75px, 9.25px) rotate(0); 118 } 119} 120@keyframes glare-top { 121 from { 122 stroke: rgba(255, 255, 255, 0); 123 } 124 24%, 125 to { 126 stroke: white; 127 } 128} 129@keyframes glare-bottom { 130 from { 131 stroke: white; 132 } 133 24%, 134 to { 135 stroke: rgba(255, 255, 255, 0); 136 } 137} 138@keyframes motion-thick { 139 from { 140 animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0); 141 stroke: rgba(255, 255, 255, 0); 142 stroke-dashoffset: 153.94; 143 transform: rotate(0.67turn); 144 } 145 20% { 146 animation-timing-function: cubic-bezier(0.33, 1, 0.67, 1); 147 stroke: white; 148 stroke-dashoffset: 141.11; 149 transform: rotate(1turn); 150 } 151 40%, 152 to { 153 stroke: rgba(255, 255, 255, 0); 154 stroke-dashoffset: 153.94; 155 transform: rotate(1.33turn); 156 } 157} 158@keyframes motion-medium { 159 from, 160 8% { 161 animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0); 162 stroke: rgba(255, 255, 255, 0); 163 stroke-dashoffset: 153.94; 164 transform: rotate(0.5turn); 165 } 166 20% { 167 animation-timing-function: cubic-bezier(0.33, 1, 0.67, 1); 168 stroke: white; 169 stroke-dashoffset: 147.53; 170 transform: rotate(0.83turn); 171 } 172 32%, 173 to { 174 stroke: rgba(255, 255, 255, 0); 175 stroke-dashoffset: 153.94; 176 transform: rotate(1.17turn); 177 } 178} 179@keyframes motion-thin { 180 from, 181 4% { 182 animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0); 183 stroke: rgba(255, 255, 255, 0); 184 stroke-dashoffset: 153.94; 185 transform: rotate(0.33turn); 186 } 187 24% { 188 animation-timing-function: cubic-bezier(0.33, 1, 0.67, 1); 189 stroke: white; 190 stroke-dashoffset: 134.7; 191 transform: rotate(0.67turn); 192 } 193 44%, 194 to { 195 stroke: rgba(255, 255, 255, 0); 196 stroke-dashoffset: 153.94; 197 transform: rotate(1turn); 198 } 199} 200@keyframes sand-drop { 201 from, 202 10% { 203 animation-timing-function: cubic-bezier(0.12, 0, 0.39, 0); 204 stroke-dashoffset: 1; 205 } 206 70%, 207 to { 208 stroke-dashoffset: -107; 209 } 210} 211@keyframes sand-fill { 212 from, 213 10% { 214 animation-timing-function: cubic-bezier(0.12, 0, 0.39, 0); 215 stroke-dashoffset: 55; 216 } 217 70%, 218 to { 219 stroke-dashoffset: -54; 220 } 221} 222@keyframes sand-grain-left { 223 from, 224 10% { 225 animation-timing-function: cubic-bezier(0.12, 0, 0.39, 0); 226 stroke-dashoffset: 29; 227 } 228 70%, 229 to { 230 stroke-dashoffset: -22; 231 } 232} 233@keyframes sand-grain-right { 234 from, 235 10% { 236 animation-timing-function: cubic-bezier(0.12, 0, 0.39, 0); 237 stroke-dashoffset: 27; 238 } 239 70%, 240 to { 241 stroke-dashoffset: -24; 242 } 243} 244@keyframes sand-line-left { 245 from, 246 10% { 247 animation-timing-function: cubic-bezier(0.12, 0, 0.39, 0); 248 stroke-dashoffset: 53; 249 } 250 70%, 251 to { 252 stroke-dashoffset: -55; 253 } 254} 255@keyframes sand-line-right { 256 from, 257 10% { 258 animation-timing-function: cubic-bezier(0.12, 0, 0.39, 0); 259 stroke-dashoffset: 14; 260 } 261 70%, 262 to { 263 stroke-dashoffset: -24.5; 264 } 265} 266@keyframes sand-mound-top { 267 from, 268 10% { 269 animation-timing-function: linear; 270 transform: translate(0, 0); 271 } 272 15% { 273 animation-timing-function: cubic-bezier(0.12, 0, 0.39, 0); 274 transform: translate(0, 1.5px); 275 } 276 51%, 277 to { 278 transform: translate(0, 13px); 279 } 280} 281@keyframes sand-mound-bottom { 282 from, 283 31% { 284 animation-timing-function: cubic-bezier(0.61, 1, 0.88, 1); 285 transform: scale(1, 0); 286 } 287 56%, 288 to { 289 transform: scale(1, 1); 290 } 291}
How It Works
- SVG as the Animation Engine
The entire loader is vector-based
Shapes, sand, and strokes are individual SVG elements
Clip paths simulate sand mounds
- CSS-Only Animation Logic
stroke-dasharray and stroke-dashoffset animate sand flow
Rotation simulates flipping of the hourglass
Cubic-bezier easing creates natural motion
- Sand Simulation
Falling sand grains use dashed strokes
Sand fill lines move downward
Top mound shrinks while bottom mound grows
- Motion Feedback
Three rotating circular strokes act as progress indicators
Different stroke widths create depth
Key Features
⏳ Pure SVG + CSS (no JavaScript)
🎨 Theme-aware color variables
🔁 Seamless infinite loop
📐 Fully scalable & responsive
🌙 Dark mode support
⚡ Lightweight and performant
Use Cases
Website preloaders
App splash screens
Portfolio micro-interactions
Creative UI loaders
Modern SaaS loading states
