Back to Components
Scroll-Based CSS Animation Showcase | Seamless Motion Effects Without JavaScript
Component

Scroll-Based CSS Animation Showcase | Seamless Motion Effects Without JavaScript

CodewithLord
October 29, 2025

This project demonstrates the power of modern CSS scroll-driven animations—no JavaScript required.

🧠 Description

This project demonstrates the power of modern CSS scroll-driven animations—no JavaScript required. Using CSS features like @property, animation-timeline, and clip-path, it creates a smooth, interactive storytelling experience that unfolds as the user scrolls. Each section animates dynamically — cards rotate, shapes morph, and progress indicators fill up — proving that complex motion and interactivity can be achieved purely through CSS. It’s a perfect example of creative front-end design powered entirely by declarative animations.


💻 HTML Code


1<!DOCTYPE html> 2<html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <title>Chrome: CSS Scroll Based Animations</title> 7 <link rel='stylesheet' href='//unpkg.com/open-props@1.7.16/open-props.min.css'> 8<link rel='stylesheet' href='//unpkg.com/open-props@1.7.16/normalize.min.css'><link rel="stylesheet" href="style.css"> 9 10 </head> 11 12 <body> 13 <div class="container"> 14 <div class="hero"> 15 <h1 class="hero__title">Scroll animations with CSS</h1> 16 17 <div class="hero__payoff">You don't need JS for everything</div> 18 </div> 19 20 <div class="divider-v"> 21 <section class="section"> 22 <div class="calendar-container"> 23 <div class="calendar-wide"> 24 <div class="calendar"></div> 25 </div> 26 <div class="calendar-mobile"> 27 <div class="calendar"></div> 28 </div> 29 </div> 30 </section> 31 32 <section class="section panel"> 33 <div class="split-h"> 34 <div class="grid-center"> 35 <div class="panel__content"> 36 <h3>Enrich your product page</h3> 37 <p> 38 Look at those cards. They animate in from the bottom and with 39 some magic they also rotate. Isn't that amazing? With some 40 fancy clip-path it should not overflow the bottom. 41 </p> 42 </div> 43 </div> 44 45 <div> 46 <div class="cards"> 47 <div class="card"><div class="calendar"></div></div> 48 <div class="card"><div class="calendar"></div></div> 49 <div class="card"><div class="calendar"></div></div> 50 <div class="card"><div class="calendar"></div></div> 51 </div> 52 </div> 53 </div> 54 </section> 55 56 <section class="section panel panel--no-overflow"> 57 <div class="split-h"> 58 <div> 59 <div class="cover-image"> 60 <img 61 class="test" 62 src="//pimskie.dev/public/assets/awesome.jpg" 63 alt="Awesome image" 64 /> 65 </div> 66 </div> 67 68 <div class="grid-center"> 69 <div class="panel__content"> 70 <h3>Animate anything</h3> 71 72 <p> 73 Like here. Using a cool <code>clip-path</code> with a 74 <code>shape()</code> function. It has 3 stages, animating from 75 one to the other using an animation-range. 76 </p> 77 78 <p> 79 Lorem CSSum is simply placeholder styling used to demonstrate 80 cascading style sheets in mockups. This dummy text does not 81 explain real CSS concepts but helps fill space while designing 82 layouts. 83 </p> 84 </div> 85 </div> 86 </div> 87 </section> 88 89 <section class="section panel"> 90 <div class="split-h"> 91 <div class="panel__content"> 92 <h3>Animate custom properties</h3> 93 94 <p> 95 Leverage the usage of <code>@property</code> to animate a custom 96 property, bound to the animation timeline. 97 </p> 98 99 <p> 100 This faux paragraph about CSS serves no real purpose other than 101 to act as a stylistic placeholder in your design mockup 102 </p> 103 </div> 104 105 <div class="grid-center"> 106 <div class="spinner"> 107 <div class="spinner__progress"></div> 108 </div> 109 </div> 110 </div> 111 </section> 112 113 <section class="section"> 114 <h2 class="section__title">What people are saying</h2> 115 <div class="popup-container"> 116 <div class="review"> 117 <h3>Myself</h3> 118 <p> 119 This SAAS platform revolutionized our workflow! What took 3-4 120 hours now takes 30 minutes. We could fire 90% of our frontend 121 developers. 122 </p> 123 </div> 124 125 <div class="review"> 126 <h3>Marcus Rodriguez - My friend</h3> 127 <p> 128 Wow... just.. wow. After one week, I increased productivity by 129 400%! The automation features are pure magic. Dreaded tasks now 130 happen seamlessly in the background. 131 </p> 132 </div> 133 134 <div class="review"> 135 <h3>Jennifer Park - Operations Manager</h3> 136 <p> 137 This product stripped away my artistic mystique and replaced it 138 with boring competence. I traded my fascinating inability to 139 adult for the tragic skill of actually finishing things. 140 </p> 141 </div> 142 </div> 143 </section> 144 145 <footer class="footer"></footer> 146 </div> 147</div> 148 149 </body> 150 151</html> 152

The HTML sets up the structure of a visually engaging scrolling webpage divided into multiple sections. The top “hero” area introduces the topic with a title and subtitle, encouraging users to scroll. Below, several content sections demonstrate different animation effects — a calendar component, animated cards, morphing images, rotating progress indicators, and review panels. Each section uses semantic HTML tags like (section), (div), (h3), and (p) to define clear content regions. The layout is designed for progressive storytelling, where each scroll reveals new interactive visual behavior. No JavaScript is used; all effects are triggered via CSS scroll-based animation timelines.

CSS Code

1@property --card-rotation-progress { 2 syntax: "<number>"; 3 inherits: true; 4 initial-value: 0; 5} 6 7@property --radial-progress { 8 syntax: "<number>"; 9 inherits: true; 10 initial-value: 0; 11} 12 13:where(h3) { 14 margin-block-end: var(--size-3); 15} 16 17.container { 18 max-width: 80rem; 19 padding-inline: var(--size-5); 20 margin-block: 2rem; 21 margin-inline: auto; 22} 23 24.hero { 25 container-type: inline-size; 26 min-height: 60vh; 27 28 display: flex; 29 flex-direction: column; 30 align-items: center; 31 justify-content: center; 32 gap: var(--size-5); 33 34 .hero__title { 35 font-size: var(--font-size-fluid-3); 36 text-align: center; 37 38 background-image: var(--gradient-10); 39 background-clip: text; 40 color: transparent; 41 42 @container (width > 40rem) { 43 font-size: calc(var(--font-size-8) * 1.5); 44 } 45 } 46 47 .hero__payoff { 48 font-size: var(--font-size-fluid-1); 49 text-align: center; 50 font-weight: var(--font-weight-9); 51 } 52} 53 54.grid-center { 55 display: grid; 56 place-items: center; 57} 58 59.section { 60 container-type: inline-size; 61 position: relative; 62 63 .section__title { 64 margin-block: 0 var(--size-fluid-2); 65 } 66} 67 68.calendar-container { 69 overflow: hidden; 70 position: relative; 71 perspective: 1000px; 72} 73 74.calendar-wide { 75 transform-style: preserve-3d; 76 width: 100%; 77 aspect-ratio: 2/1; 78 79 animation: calendarLargeAppear 1ms linear, calendarLargeFadeIn 1s ease-out; 80 animation-timeline: scroll(root), auto; 81 animation-range: -10% 20%; 82} 83 84.calendar-mobile { 85 width: 22rem; 86 aspect-ratio: 1/1.5; 87 position: absolute; 88 inset: auto 5% 3% auto; 89 z-index: 1; 90 opacity: 0; 91 92 animation: calendarSmallAppear 1ms cubic-bezier(0.17, 0.67, 0.34, 1.36) 93 forwards; 94 animation-timeline: scroll(root); 95 animation-range: 5% 25%; 96} 97 98.calendar { 99 width: 100%; 100 aspect-ratio: 1/1.5; 101 102 background: var(--blue-1); 103 border-radius: var(--radius-3); 104 border: 4px solid var(--gray-0); 105 box-shadow: var(--shadow-3); 106 107 @container (width > 40rem) { 108 width: 100%; 109 height: 100%; 110 aspect-ratio: initial; 111 } 112} 113 114.panel { 115 box-shadow: var(--shadow-3); 116 background: var(--gray-1); 117 118 border-radius: var(--radius-3); 119 color: var(--gray-8); 120 121 code { 122 color: var(--text-1); 123 } 124} 125 126.panel--no-overflow { 127 overflow: clip; 128} 129 130.panel__content { 131 padding: var(--size-fluid-4); 132} 133 134.divider-v { 135 display: grid; 136 gap: var(--size-fluid-6); 137} 138 139.split-h { 140 display: grid; 141 gap: 1rem; 142 143 @container (width > 48.7rem) { 144 grid-template-columns: repeat(2, 1fr); 145 } 146} 147 148.cards { 149 container-type: inline-size; 150 151 position: relative; 152 height: 50vh; 153 width: min(100%, 350px); 154 155 clip-path: polygon(-50% -50%, 400% -50%, 150% 100%, -80% 100%); 156 157 margin-inline: auto; 158 margin-block-start: -20%; 159} 160 161.card { 162 --index: sibling-index(); 163 --count: sibling-count(); 164 165 --angle-min: -45; 166 --angle-max: 45; 167 --angle-diff: calc(var(--angle-max) - var(--angle-min)); 168 --angle-step: calc(var(--angle-diff) / (var(--count) + 1)); 169 --angle: calc(var(--angle-min) + var(--angle-step) * var(--index)); 170 --angle-deg: calc((var(--angle) * var(--card-rotation-progress)) * 1deg); 171 172 width: 100%; 173 174 position: absolute; 175 transform-origin: bottom center; 176 rotate: var(--angle-deg); 177 translate: 0 50%; 178 179 animation: cardUp 1ms ease-out both, cardRotate 1ms ease-out forwards; 180 animation-timeline: view(block); 181 animation-range: entry 100% contain 50%; 182} 183 184.popup-container { 185 display: grid; 186 gap: 1rem; 187 188 @container (width > 40rem) { 189 grid-template-columns: repeat(3, 1fr); 190 } 191} 192 193.cover-image { 194 clip-path: shape( 195 from 100% 0, 196 line to 90% 0%, 197 curve to 90% 100% with 50% 10% / 50% 90%, 198 line to 100% 100%, 199 close 200 ); 201 202 animation: imageShape 1ms linear forwards; 203 animation-timeline: view(block); 204 animation-range: entry 60% contain 35%; 205} 206 207.review { 208 --index: sibling-index(); 209 --count: sibling-count(); 210 211 --entry-start: 20%; 212 --entry-step: calc((100% - var(--entry-start)) / (var(--count) + 1)); 213 --entry: calc(var(--entry-start) + var(--index) * var(--entry-step)); 214 215 background: var(--gray-1); 216 width: 100%; 217 border-radius: var(--radius-3); 218 padding: var(--size-fluid-4); 219 220 color: var(--gray-8); 221 222 animation: reviewUp 1ms ease-out both; 223 animation-timeline: view(block); 224 animation-range: entry var(--entry) contain 50%; 225 226 h3 { 227 font-size: var(--font-size-fluid-1); 228 margin-block-end: var(--size-fluid-3); 229 } 230} 231 232.spinner { 233 --progress: calc(var(--radial-progress) * 360deg); 234 --border-width: 10px; 235 236 position: relative; 237 238 width: 10rem; 239 aspect-ratio: 1/1; 240 border-radius: 50%; 241 border: 10px solid var(--gray-0); 242 243 display: grid; 244 place-items: center; 245 246 box-shadow: var(--shadow-3); 247 248 animation: radialProgress 1ms ease-out forwards; 249 animation-timeline: view(block); 250 animation-range: entry 100% contain 90%; 251} 252 253.spinner__progress { 254 position: absolute; 255 inset: 0; 256 257 border-radius: 50%; 258 259 width: 100%; 260 aspect-ratio: 1; 261 background: conic-gradient( 262 from 0deg, 263 #8b5cf6 0deg, 264 #8b5cf6 var(--progress, 0deg), 265 transparent var(--progress, 0deg) 266 ); 267 268 mask: radial-gradient( 269 circle, 270 transparent 0, 271 transparent calc(50% - 5px), 272 black calc(50% - var(--border-width)), 273 black 100% 274 ); 275} 276 277.spinner::before { 278 --progress-fixed: calc(var(--radial-progress) * 100); 279 counter-reset: variable var(--progress-fixed); 280 content: "" counter(variable) "%"; 281 282 position: relative; 283 z-index: 10; 284 285 font-size: 1.5rem; 286 font-weight: var(--font-weight-7); 287} 288 289.footer { 290 height: 50dvh; 291} 292 293p + p { 294 margin-block: var(--size-fluid-2) 0; 295} 296 297@keyframes calendarLargeAppear { 298 from { 299 transform: translateZ(-40rem) rotateX(45deg); 300 } 301 to { 302 transform: translateZ(0rem) rotateX(0deg); 303 } 304} 305 306@keyframes calendarLargeFadeIn { 307 from { 308 opacity: 0; 309 } 310 to { 311 opacity: 1; 312 } 313} 314 315@keyframes calendarSmallAppear { 316 from { 317 opacity: 0; 318 transform: translateX(50%); 319 } 320 to { 321 opacity: 1; 322 transform: translateX(0%); 323 } 324} 325 326@keyframes cardUp { 327 from { 328 translate: 0 80%; 329 } 330 to { 331 translate: 0 20%; 332 } 333} 334 335@keyframes cardRotate { 336 from { 337 --card-rotation-progress: 0; 338 } 339 to { 340 --card-rotation-progress: 1; 341 } 342} 343 344@keyframes radialProgress { 345 from { 346 --radial-progress: 0; 347 } 348 to { 349 --radial-progress: 1; 350 } 351} 352 353@keyframes imageShape { 354 50% { 355 clip-path: shape( 356 from 100% 0, 357 line to 50% 0%, 358 curve to 50% 100% with -10% 10% / -10% 90%, 359 line to 100% 100%, 360 close 361 ); 362 } 363 364 100% { 365 clip-path: shape( 366 from 100% 0, 367 line to 0% 0%, 368 curve to 0% 100% with 0% 0% / 0% 100%, 369 line to 100% 100%, 370 close 371 ); 372 } 373} 374 375@keyframes reviewUp { 376 from { 377 translate: 0 40%; 378 } 379 to { 380 translate: 0 0%; 381 } 382} 383

The CSS is the heart of this project, combining modern animation features to create motion tied directly to the user’s scroll position. It uses CSS custom properties (variables) such as --card-rotation-progress and --radial-progress, defined with @property for smooth transitions. Scroll-tied animations are implemented through animation-timeline and animation-range, linking animations to specific parts of the viewport. Components like calendars fade and slide into view, cards fan out and rotate dynamically, and images morph through shape transitions using clip-path with custom @keyframes. The radial progress spinner visually fills up as you scroll, demonstrating property-based animation of custom variables. Responsive design principles ensure the layout adjusts beautifully across devices, while CSS container queries adapt the layout based on section width. The entire experience feels fluid, synchronized, and futuristic — all powered solely by CSS.

Love this component?

Explore more components and build amazing UIs.

View All Components