├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── public └── images │ ├── i1.jpg │ ├── i2.jpg │ ├── i3.jpg │ └── i4.jpg ├── src ├── components │ ├── canvas.ts │ ├── media.ts │ └── scroll.ts ├── lenis.css ├── main.ts ├── shaders │ ├── fragment.glsl │ └── vertex.glsl ├── style.css └── types │ └── types.ts ├── tsconfig.json └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + TS 8 | 9 | 10 |
11 |
12 |
13 |
14 | 15 |
16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onscroll-grid-reveal", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "onscroll-grid-reveal", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "gsap": "^3.12.5", 12 | "lenis": "^1.1.9", 13 | "three": "^0.167.1", 14 | "vite-plugin-glsl": "^1.3.0" 15 | }, 16 | "devDependencies": { 17 | "@types/three": "^0.167.1", 18 | "typescript": "^5.2.2", 19 | "vite": "^5.3.4" 20 | } 21 | }, 22 | "node_modules/@darkroom.engineering/tempus": { 23 | "version": "0.0.46", 24 | "resolved": "https://registry.npmjs.org/@darkroom.engineering/tempus/-/tempus-0.0.46.tgz", 25 | "integrity": "sha512-s5vav3KMHYezvUCl4ee5epg0oimF6M8C9gAaKxFnFaTvX2q3ywFDryIv6XLd0mRFUt3S1uHDJqKaiEcs2ZVSvw==" 26 | }, 27 | "node_modules/@esbuild/aix-ppc64": { 28 | "version": "0.21.5", 29 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 30 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 31 | "cpu": [ 32 | "ppc64" 33 | ], 34 | "optional": true, 35 | "os": [ 36 | "aix" 37 | ], 38 | "engines": { 39 | "node": ">=12" 40 | } 41 | }, 42 | "node_modules/@esbuild/android-arm": { 43 | "version": "0.21.5", 44 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 45 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 46 | "cpu": [ 47 | "arm" 48 | ], 49 | "optional": true, 50 | "os": [ 51 | "android" 52 | ], 53 | "engines": { 54 | "node": ">=12" 55 | } 56 | }, 57 | "node_modules/@esbuild/android-arm64": { 58 | "version": "0.21.5", 59 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 60 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 61 | "cpu": [ 62 | "arm64" 63 | ], 64 | "optional": true, 65 | "os": [ 66 | "android" 67 | ], 68 | "engines": { 69 | "node": ">=12" 70 | } 71 | }, 72 | "node_modules/@esbuild/android-x64": { 73 | "version": "0.21.5", 74 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 75 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 76 | "cpu": [ 77 | "x64" 78 | ], 79 | "optional": true, 80 | "os": [ 81 | "android" 82 | ], 83 | "engines": { 84 | "node": ">=12" 85 | } 86 | }, 87 | "node_modules/@esbuild/darwin-arm64": { 88 | "version": "0.21.5", 89 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 90 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 91 | "cpu": [ 92 | "arm64" 93 | ], 94 | "optional": true, 95 | "os": [ 96 | "darwin" 97 | ], 98 | "engines": { 99 | "node": ">=12" 100 | } 101 | }, 102 | "node_modules/@esbuild/darwin-x64": { 103 | "version": "0.21.5", 104 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 105 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 106 | "cpu": [ 107 | "x64" 108 | ], 109 | "optional": true, 110 | "os": [ 111 | "darwin" 112 | ], 113 | "engines": { 114 | "node": ">=12" 115 | } 116 | }, 117 | "node_modules/@esbuild/freebsd-arm64": { 118 | "version": "0.21.5", 119 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 120 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 121 | "cpu": [ 122 | "arm64" 123 | ], 124 | "optional": true, 125 | "os": [ 126 | "freebsd" 127 | ], 128 | "engines": { 129 | "node": ">=12" 130 | } 131 | }, 132 | "node_modules/@esbuild/freebsd-x64": { 133 | "version": "0.21.5", 134 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 135 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 136 | "cpu": [ 137 | "x64" 138 | ], 139 | "optional": true, 140 | "os": [ 141 | "freebsd" 142 | ], 143 | "engines": { 144 | "node": ">=12" 145 | } 146 | }, 147 | "node_modules/@esbuild/linux-arm": { 148 | "version": "0.21.5", 149 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 150 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 151 | "cpu": [ 152 | "arm" 153 | ], 154 | "optional": true, 155 | "os": [ 156 | "linux" 157 | ], 158 | "engines": { 159 | "node": ">=12" 160 | } 161 | }, 162 | "node_modules/@esbuild/linux-arm64": { 163 | "version": "0.21.5", 164 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 165 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 166 | "cpu": [ 167 | "arm64" 168 | ], 169 | "optional": true, 170 | "os": [ 171 | "linux" 172 | ], 173 | "engines": { 174 | "node": ">=12" 175 | } 176 | }, 177 | "node_modules/@esbuild/linux-ia32": { 178 | "version": "0.21.5", 179 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 180 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 181 | "cpu": [ 182 | "ia32" 183 | ], 184 | "optional": true, 185 | "os": [ 186 | "linux" 187 | ], 188 | "engines": { 189 | "node": ">=12" 190 | } 191 | }, 192 | "node_modules/@esbuild/linux-loong64": { 193 | "version": "0.21.5", 194 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 195 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 196 | "cpu": [ 197 | "loong64" 198 | ], 199 | "optional": true, 200 | "os": [ 201 | "linux" 202 | ], 203 | "engines": { 204 | "node": ">=12" 205 | } 206 | }, 207 | "node_modules/@esbuild/linux-mips64el": { 208 | "version": "0.21.5", 209 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 210 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 211 | "cpu": [ 212 | "mips64el" 213 | ], 214 | "optional": true, 215 | "os": [ 216 | "linux" 217 | ], 218 | "engines": { 219 | "node": ">=12" 220 | } 221 | }, 222 | "node_modules/@esbuild/linux-ppc64": { 223 | "version": "0.21.5", 224 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 225 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 226 | "cpu": [ 227 | "ppc64" 228 | ], 229 | "optional": true, 230 | "os": [ 231 | "linux" 232 | ], 233 | "engines": { 234 | "node": ">=12" 235 | } 236 | }, 237 | "node_modules/@esbuild/linux-riscv64": { 238 | "version": "0.21.5", 239 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 240 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 241 | "cpu": [ 242 | "riscv64" 243 | ], 244 | "optional": true, 245 | "os": [ 246 | "linux" 247 | ], 248 | "engines": { 249 | "node": ">=12" 250 | } 251 | }, 252 | "node_modules/@esbuild/linux-s390x": { 253 | "version": "0.21.5", 254 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 255 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 256 | "cpu": [ 257 | "s390x" 258 | ], 259 | "optional": true, 260 | "os": [ 261 | "linux" 262 | ], 263 | "engines": { 264 | "node": ">=12" 265 | } 266 | }, 267 | "node_modules/@esbuild/linux-x64": { 268 | "version": "0.21.5", 269 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 270 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 271 | "cpu": [ 272 | "x64" 273 | ], 274 | "optional": true, 275 | "os": [ 276 | "linux" 277 | ], 278 | "engines": { 279 | "node": ">=12" 280 | } 281 | }, 282 | "node_modules/@esbuild/netbsd-x64": { 283 | "version": "0.21.5", 284 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 285 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 286 | "cpu": [ 287 | "x64" 288 | ], 289 | "optional": true, 290 | "os": [ 291 | "netbsd" 292 | ], 293 | "engines": { 294 | "node": ">=12" 295 | } 296 | }, 297 | "node_modules/@esbuild/openbsd-x64": { 298 | "version": "0.21.5", 299 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 300 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 301 | "cpu": [ 302 | "x64" 303 | ], 304 | "optional": true, 305 | "os": [ 306 | "openbsd" 307 | ], 308 | "engines": { 309 | "node": ">=12" 310 | } 311 | }, 312 | "node_modules/@esbuild/sunos-x64": { 313 | "version": "0.21.5", 314 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 315 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 316 | "cpu": [ 317 | "x64" 318 | ], 319 | "optional": true, 320 | "os": [ 321 | "sunos" 322 | ], 323 | "engines": { 324 | "node": ">=12" 325 | } 326 | }, 327 | "node_modules/@esbuild/win32-arm64": { 328 | "version": "0.21.5", 329 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 330 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 331 | "cpu": [ 332 | "arm64" 333 | ], 334 | "optional": true, 335 | "os": [ 336 | "win32" 337 | ], 338 | "engines": { 339 | "node": ">=12" 340 | } 341 | }, 342 | "node_modules/@esbuild/win32-ia32": { 343 | "version": "0.21.5", 344 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 345 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 346 | "cpu": [ 347 | "ia32" 348 | ], 349 | "optional": true, 350 | "os": [ 351 | "win32" 352 | ], 353 | "engines": { 354 | "node": ">=12" 355 | } 356 | }, 357 | "node_modules/@esbuild/win32-x64": { 358 | "version": "0.21.5", 359 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 360 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 361 | "cpu": [ 362 | "x64" 363 | ], 364 | "optional": true, 365 | "os": [ 366 | "win32" 367 | ], 368 | "engines": { 369 | "node": ">=12" 370 | } 371 | }, 372 | "node_modules/@rollup/pluginutils": { 373 | "version": "5.1.0", 374 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", 375 | "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", 376 | "dependencies": { 377 | "@types/estree": "^1.0.0", 378 | "estree-walker": "^2.0.2", 379 | "picomatch": "^2.3.1" 380 | }, 381 | "engines": { 382 | "node": ">=14.0.0" 383 | }, 384 | "peerDependencies": { 385 | "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 386 | }, 387 | "peerDependenciesMeta": { 388 | "rollup": { 389 | "optional": true 390 | } 391 | } 392 | }, 393 | "node_modules/@rollup/rollup-android-arm-eabi": { 394 | "version": "4.20.0", 395 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", 396 | "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", 397 | "cpu": [ 398 | "arm" 399 | ], 400 | "optional": true, 401 | "os": [ 402 | "android" 403 | ] 404 | }, 405 | "node_modules/@rollup/rollup-android-arm64": { 406 | "version": "4.20.0", 407 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", 408 | "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", 409 | "cpu": [ 410 | "arm64" 411 | ], 412 | "optional": true, 413 | "os": [ 414 | "android" 415 | ] 416 | }, 417 | "node_modules/@rollup/rollup-darwin-arm64": { 418 | "version": "4.20.0", 419 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", 420 | "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", 421 | "cpu": [ 422 | "arm64" 423 | ], 424 | "optional": true, 425 | "os": [ 426 | "darwin" 427 | ] 428 | }, 429 | "node_modules/@rollup/rollup-darwin-x64": { 430 | "version": "4.20.0", 431 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", 432 | "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", 433 | "cpu": [ 434 | "x64" 435 | ], 436 | "optional": true, 437 | "os": [ 438 | "darwin" 439 | ] 440 | }, 441 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 442 | "version": "4.20.0", 443 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", 444 | "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", 445 | "cpu": [ 446 | "arm" 447 | ], 448 | "optional": true, 449 | "os": [ 450 | "linux" 451 | ] 452 | }, 453 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 454 | "version": "4.20.0", 455 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", 456 | "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", 457 | "cpu": [ 458 | "arm" 459 | ], 460 | "optional": true, 461 | "os": [ 462 | "linux" 463 | ] 464 | }, 465 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 466 | "version": "4.20.0", 467 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", 468 | "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", 469 | "cpu": [ 470 | "arm64" 471 | ], 472 | "optional": true, 473 | "os": [ 474 | "linux" 475 | ] 476 | }, 477 | "node_modules/@rollup/rollup-linux-arm64-musl": { 478 | "version": "4.20.0", 479 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", 480 | "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", 481 | "cpu": [ 482 | "arm64" 483 | ], 484 | "optional": true, 485 | "os": [ 486 | "linux" 487 | ] 488 | }, 489 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 490 | "version": "4.20.0", 491 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", 492 | "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", 493 | "cpu": [ 494 | "ppc64" 495 | ], 496 | "optional": true, 497 | "os": [ 498 | "linux" 499 | ] 500 | }, 501 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 502 | "version": "4.20.0", 503 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", 504 | "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", 505 | "cpu": [ 506 | "riscv64" 507 | ], 508 | "optional": true, 509 | "os": [ 510 | "linux" 511 | ] 512 | }, 513 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 514 | "version": "4.20.0", 515 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", 516 | "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", 517 | "cpu": [ 518 | "s390x" 519 | ], 520 | "optional": true, 521 | "os": [ 522 | "linux" 523 | ] 524 | }, 525 | "node_modules/@rollup/rollup-linux-x64-gnu": { 526 | "version": "4.20.0", 527 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", 528 | "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", 529 | "cpu": [ 530 | "x64" 531 | ], 532 | "optional": true, 533 | "os": [ 534 | "linux" 535 | ] 536 | }, 537 | "node_modules/@rollup/rollup-linux-x64-musl": { 538 | "version": "4.20.0", 539 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", 540 | "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", 541 | "cpu": [ 542 | "x64" 543 | ], 544 | "optional": true, 545 | "os": [ 546 | "linux" 547 | ] 548 | }, 549 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 550 | "version": "4.20.0", 551 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", 552 | "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", 553 | "cpu": [ 554 | "arm64" 555 | ], 556 | "optional": true, 557 | "os": [ 558 | "win32" 559 | ] 560 | }, 561 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 562 | "version": "4.20.0", 563 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", 564 | "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", 565 | "cpu": [ 566 | "ia32" 567 | ], 568 | "optional": true, 569 | "os": [ 570 | "win32" 571 | ] 572 | }, 573 | "node_modules/@rollup/rollup-win32-x64-msvc": { 574 | "version": "4.20.0", 575 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", 576 | "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", 577 | "cpu": [ 578 | "x64" 579 | ], 580 | "optional": true, 581 | "os": [ 582 | "win32" 583 | ] 584 | }, 585 | "node_modules/@tweenjs/tween.js": { 586 | "version": "23.1.3", 587 | "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", 588 | "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", 589 | "dev": true 590 | }, 591 | "node_modules/@types/estree": { 592 | "version": "1.0.5", 593 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 594 | "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" 595 | }, 596 | "node_modules/@types/stats.js": { 597 | "version": "0.17.3", 598 | "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", 599 | "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", 600 | "dev": true 601 | }, 602 | "node_modules/@types/three": { 603 | "version": "0.167.1", 604 | "resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.1.tgz", 605 | "integrity": "sha512-OCd2Uv/8/4TbmSaIRFawrCOnDMLdpaa+QGJdhlUBmdfbHjLY8k6uFc0tde2/UvcaHQ6NtLl28onj/vJfofV+Tg==", 606 | "dev": true, 607 | "dependencies": { 608 | "@tweenjs/tween.js": "~23.1.2", 609 | "@types/stats.js": "*", 610 | "@types/webxr": "*", 611 | "fflate": "~0.8.2", 612 | "meshoptimizer": "~0.18.1" 613 | } 614 | }, 615 | "node_modules/@types/webxr": { 616 | "version": "0.5.19", 617 | "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.19.tgz", 618 | "integrity": "sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw==", 619 | "dev": true 620 | }, 621 | "node_modules/esbuild": { 622 | "version": "0.21.5", 623 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 624 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 625 | "hasInstallScript": true, 626 | "bin": { 627 | "esbuild": "bin/esbuild" 628 | }, 629 | "engines": { 630 | "node": ">=12" 631 | }, 632 | "optionalDependencies": { 633 | "@esbuild/aix-ppc64": "0.21.5", 634 | "@esbuild/android-arm": "0.21.5", 635 | "@esbuild/android-arm64": "0.21.5", 636 | "@esbuild/android-x64": "0.21.5", 637 | "@esbuild/darwin-arm64": "0.21.5", 638 | "@esbuild/darwin-x64": "0.21.5", 639 | "@esbuild/freebsd-arm64": "0.21.5", 640 | "@esbuild/freebsd-x64": "0.21.5", 641 | "@esbuild/linux-arm": "0.21.5", 642 | "@esbuild/linux-arm64": "0.21.5", 643 | "@esbuild/linux-ia32": "0.21.5", 644 | "@esbuild/linux-loong64": "0.21.5", 645 | "@esbuild/linux-mips64el": "0.21.5", 646 | "@esbuild/linux-ppc64": "0.21.5", 647 | "@esbuild/linux-riscv64": "0.21.5", 648 | "@esbuild/linux-s390x": "0.21.5", 649 | "@esbuild/linux-x64": "0.21.5", 650 | "@esbuild/netbsd-x64": "0.21.5", 651 | "@esbuild/openbsd-x64": "0.21.5", 652 | "@esbuild/sunos-x64": "0.21.5", 653 | "@esbuild/win32-arm64": "0.21.5", 654 | "@esbuild/win32-ia32": "0.21.5", 655 | "@esbuild/win32-x64": "0.21.5" 656 | } 657 | }, 658 | "node_modules/estree-walker": { 659 | "version": "2.0.2", 660 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 661 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 662 | }, 663 | "node_modules/fflate": { 664 | "version": "0.8.2", 665 | "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", 666 | "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", 667 | "dev": true 668 | }, 669 | "node_modules/fsevents": { 670 | "version": "2.3.3", 671 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 672 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 673 | "hasInstallScript": true, 674 | "optional": true, 675 | "os": [ 676 | "darwin" 677 | ], 678 | "engines": { 679 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 680 | } 681 | }, 682 | "node_modules/gsap": { 683 | "version": "3.12.5", 684 | "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz", 685 | "integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==" 686 | }, 687 | "node_modules/lenis": { 688 | "version": "1.1.9", 689 | "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.1.9.tgz", 690 | "integrity": "sha512-xU2UU1J8mJCiWBSfiMWsC/kPbQMIadSsJiEu3mVqptXGnwo/FUnfp8xCyiOJEvBwzqx4XDdB6TKYFn83y871vA==", 691 | "dependencies": { 692 | "@darkroom.engineering/tempus": "^0.0.46" 693 | }, 694 | "funding": { 695 | "type": "github", 696 | "url": "https://github.com/sponsors/darkroomengineering" 697 | }, 698 | "peerDependencies": { 699 | "react": ">=17.0.0", 700 | "react-dom": ">=17.0.0" 701 | }, 702 | "peerDependenciesMeta": { 703 | "react": { 704 | "optional": true 705 | }, 706 | "react-dom": { 707 | "optional": true 708 | } 709 | } 710 | }, 711 | "node_modules/meshoptimizer": { 712 | "version": "0.18.1", 713 | "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", 714 | "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", 715 | "dev": true 716 | }, 717 | "node_modules/nanoid": { 718 | "version": "3.3.7", 719 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 720 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 721 | "funding": [ 722 | { 723 | "type": "github", 724 | "url": "https://github.com/sponsors/ai" 725 | } 726 | ], 727 | "bin": { 728 | "nanoid": "bin/nanoid.cjs" 729 | }, 730 | "engines": { 731 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 732 | } 733 | }, 734 | "node_modules/picocolors": { 735 | "version": "1.0.1", 736 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", 737 | "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" 738 | }, 739 | "node_modules/picomatch": { 740 | "version": "2.3.1", 741 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 742 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 743 | "engines": { 744 | "node": ">=8.6" 745 | }, 746 | "funding": { 747 | "url": "https://github.com/sponsors/jonschlinkert" 748 | } 749 | }, 750 | "node_modules/postcss": { 751 | "version": "8.4.41", 752 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", 753 | "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", 754 | "funding": [ 755 | { 756 | "type": "opencollective", 757 | "url": "https://opencollective.com/postcss/" 758 | }, 759 | { 760 | "type": "tidelift", 761 | "url": "https://tidelift.com/funding/github/npm/postcss" 762 | }, 763 | { 764 | "type": "github", 765 | "url": "https://github.com/sponsors/ai" 766 | } 767 | ], 768 | "dependencies": { 769 | "nanoid": "^3.3.7", 770 | "picocolors": "^1.0.1", 771 | "source-map-js": "^1.2.0" 772 | }, 773 | "engines": { 774 | "node": "^10 || ^12 || >=14" 775 | } 776 | }, 777 | "node_modules/rollup": { 778 | "version": "4.20.0", 779 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", 780 | "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", 781 | "dependencies": { 782 | "@types/estree": "1.0.5" 783 | }, 784 | "bin": { 785 | "rollup": "dist/bin/rollup" 786 | }, 787 | "engines": { 788 | "node": ">=18.0.0", 789 | "npm": ">=8.0.0" 790 | }, 791 | "optionalDependencies": { 792 | "@rollup/rollup-android-arm-eabi": "4.20.0", 793 | "@rollup/rollup-android-arm64": "4.20.0", 794 | "@rollup/rollup-darwin-arm64": "4.20.0", 795 | "@rollup/rollup-darwin-x64": "4.20.0", 796 | "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", 797 | "@rollup/rollup-linux-arm-musleabihf": "4.20.0", 798 | "@rollup/rollup-linux-arm64-gnu": "4.20.0", 799 | "@rollup/rollup-linux-arm64-musl": "4.20.0", 800 | "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", 801 | "@rollup/rollup-linux-riscv64-gnu": "4.20.0", 802 | "@rollup/rollup-linux-s390x-gnu": "4.20.0", 803 | "@rollup/rollup-linux-x64-gnu": "4.20.0", 804 | "@rollup/rollup-linux-x64-musl": "4.20.0", 805 | "@rollup/rollup-win32-arm64-msvc": "4.20.0", 806 | "@rollup/rollup-win32-ia32-msvc": "4.20.0", 807 | "@rollup/rollup-win32-x64-msvc": "4.20.0", 808 | "fsevents": "~2.3.2" 809 | } 810 | }, 811 | "node_modules/source-map-js": { 812 | "version": "1.2.0", 813 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", 814 | "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", 815 | "engines": { 816 | "node": ">=0.10.0" 817 | } 818 | }, 819 | "node_modules/three": { 820 | "version": "0.167.1", 821 | "resolved": "https://registry.npmjs.org/three/-/three-0.167.1.tgz", 822 | "integrity": "sha512-gYTLJA/UQip6J/tJvl91YYqlZF47+D/kxiWrbTon35ZHlXEN0VOo+Qke2walF1/x92v55H6enomymg4Dak52kw==" 823 | }, 824 | "node_modules/typescript": { 825 | "version": "5.5.4", 826 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", 827 | "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", 828 | "dev": true, 829 | "bin": { 830 | "tsc": "bin/tsc", 831 | "tsserver": "bin/tsserver" 832 | }, 833 | "engines": { 834 | "node": ">=14.17" 835 | } 836 | }, 837 | "node_modules/vite": { 838 | "version": "5.3.5", 839 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", 840 | "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", 841 | "dependencies": { 842 | "esbuild": "^0.21.3", 843 | "postcss": "^8.4.39", 844 | "rollup": "^4.13.0" 845 | }, 846 | "bin": { 847 | "vite": "bin/vite.js" 848 | }, 849 | "engines": { 850 | "node": "^18.0.0 || >=20.0.0" 851 | }, 852 | "funding": { 853 | "url": "https://github.com/vitejs/vite?sponsor=1" 854 | }, 855 | "optionalDependencies": { 856 | "fsevents": "~2.3.3" 857 | }, 858 | "peerDependencies": { 859 | "@types/node": "^18.0.0 || >=20.0.0", 860 | "less": "*", 861 | "lightningcss": "^1.21.0", 862 | "sass": "*", 863 | "stylus": "*", 864 | "sugarss": "*", 865 | "terser": "^5.4.0" 866 | }, 867 | "peerDependenciesMeta": { 868 | "@types/node": { 869 | "optional": true 870 | }, 871 | "less": { 872 | "optional": true 873 | }, 874 | "lightningcss": { 875 | "optional": true 876 | }, 877 | "sass": { 878 | "optional": true 879 | }, 880 | "stylus": { 881 | "optional": true 882 | }, 883 | "sugarss": { 884 | "optional": true 885 | }, 886 | "terser": { 887 | "optional": true 888 | } 889 | } 890 | }, 891 | "node_modules/vite-plugin-glsl": { 892 | "version": "1.3.0", 893 | "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.3.0.tgz", 894 | "integrity": "sha512-SzEoLet9Bp5VSozjrhUiSc3xX1+u7rCTjXAsq4qWM3u8UjilI76A9ucX/T+CRGQCe25j50GSY+9mKSGUVPET1w==", 895 | "dependencies": { 896 | "@rollup/pluginutils": "^5.1.0" 897 | }, 898 | "engines": { 899 | "node": ">= 16.15.1", 900 | "npm": ">= 8.11.0" 901 | }, 902 | "peerDependencies": { 903 | "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" 904 | } 905 | } 906 | } 907 | } 908 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onscroll-grid-reveal", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@types/three": "^0.167.1", 13 | "typescript": "^5.2.2", 14 | "vite": "^5.3.4" 15 | }, 16 | "dependencies": { 17 | "gsap": "^3.12.5", 18 | "lenis": "^1.1.9", 19 | "three": "^0.167.1", 20 | "vite-plugin-glsl": "^1.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/images/i1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/onscroll-grid-image-reveal/3029018500d25636d8a579aed813d96f5353110f/public/images/i1.jpg -------------------------------------------------------------------------------- /public/images/i2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/onscroll-grid-image-reveal/3029018500d25636d8a579aed813d96f5353110f/public/images/i2.jpg -------------------------------------------------------------------------------- /public/images/i3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/onscroll-grid-image-reveal/3029018500d25636d8a579aed813d96f5353110f/public/images/i3.jpg -------------------------------------------------------------------------------- /public/images/i4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/onscroll-grid-image-reveal/3029018500d25636d8a579aed813d96f5353110f/public/images/i4.jpg -------------------------------------------------------------------------------- /src/components/canvas.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { Dimensions, Size } from '../types/types' 3 | 4 | import vertexShader from '../shaders/vertex.glsl' 5 | import fragmentShader from '../shaders/fragment.glsl' 6 | import Media from './media' 7 | 8 | export default class Canvas { 9 | element: HTMLCanvasElement 10 | scene: THREE.Scene 11 | camera: THREE.PerspectiveCamera 12 | renderer: THREE.WebGLRenderer 13 | sizes: Size 14 | dimensions: Dimensions 15 | time: number 16 | clock: THREE.Clock 17 | medias: Media[] 18 | 19 | constructor() { 20 | this.element = document.getElementById('webgl') as HTMLCanvasElement 21 | this.time = 0 22 | this.medias = [] 23 | this.createClock() 24 | this.createScene() 25 | this.createCamera() 26 | this.createRenderer() 27 | this.setSizes() 28 | this.addEventListeners() 29 | this.createMedias() 30 | } 31 | 32 | createScene() { 33 | this.scene = new THREE.Scene() 34 | } 35 | 36 | createCamera() { 37 | this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100) 38 | this.scene.add(this.camera) 39 | this.camera.position.z = 10 40 | } 41 | 42 | createRenderer() { 43 | this.dimensions = { 44 | width: window.innerWidth, 45 | height: window.innerHeight, 46 | pixelRatio: Math.min(2, window.devicePixelRatio), 47 | } 48 | 49 | this.renderer = new THREE.WebGLRenderer({ canvas: this.element, alpha: true }) 50 | this.renderer.setSize(this.dimensions.width, this.dimensions.height) 51 | this.renderer.render(this.scene, this.camera) 52 | 53 | this.renderer.setPixelRatio(this.dimensions.pixelRatio) 54 | } 55 | 56 | setSizes() { 57 | let fov = this.camera.fov * (Math.PI / 180) 58 | let height = this.camera.position.z * Math.tan(fov / 2) * 2 59 | let width = height * this.camera.aspect 60 | 61 | this.sizes = { 62 | width: width, 63 | height: height, 64 | } 65 | } 66 | 67 | createClock() { 68 | this.clock = new THREE.Clock() 69 | } 70 | 71 | addEventListeners() { 72 | window.addEventListener('resize', this.onResize.bind(this)) 73 | } 74 | 75 | onResize() { 76 | this.dimensions = { 77 | width: window.innerWidth, 78 | height: window.innerHeight, 79 | pixelRatio: Math.min(2, window.devicePixelRatio), 80 | } 81 | 82 | this.camera.aspect = window.innerWidth / window.innerHeight 83 | this.camera.updateProjectionMatrix() 84 | this.setSizes() 85 | 86 | this.renderer.setPixelRatio(this.dimensions.pixelRatio) 87 | this.renderer.setSize(this.dimensions.width, this.dimensions.height) 88 | 89 | this.medias.forEach((media) => { 90 | media.onResize(this.sizes) 91 | }) 92 | } 93 | 94 | createDebugMesh() { 95 | const mesh = new THREE.Mesh( 96 | new THREE.PlaneGeometry(5, 5), 97 | new THREE.ShaderMaterial({ vertexShader, fragmentShader }) 98 | ) 99 | 100 | this.scene.add(mesh) 101 | } 102 | 103 | getTime() { 104 | return this.time 105 | } 106 | 107 | createMedias() { 108 | const images = document.querySelectorAll('img') 109 | images.forEach((image) => { 110 | const media = new Media({ 111 | element: image, 112 | scene: this.scene, 113 | sizes: this.sizes, 114 | }) 115 | 116 | this.medias.push(media) 117 | }) 118 | } 119 | 120 | render(scroll: number) { 121 | this.time = this.clock.getElapsedTime() 122 | 123 | this.medias.forEach((media) => { 124 | media.updateScroll(scroll) 125 | }) 126 | 127 | this.renderer.render(this.scene, this.camera) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/components/media.ts: -------------------------------------------------------------------------------- 1 | import { Position, Size } from '../types/types' 2 | import * as THREE from 'three' 3 | import gsap from 'gsap' 4 | 5 | import vertexShader from '../shaders/vertex.glsl' 6 | import fragmentShader from '../shaders/fragment.glsl' 7 | 8 | interface Props { 9 | element: HTMLImageElement 10 | scene: THREE.Scene 11 | sizes: Size 12 | } 13 | 14 | export default class Media { 15 | element: HTMLImageElement 16 | scene: THREE.Scene 17 | sizes: Size 18 | material: THREE.ShaderMaterial 19 | geometry: THREE.PlaneGeometry 20 | mesh: THREE.Mesh 21 | nodeDimensions: Size 22 | meshDimensions: Size 23 | meshPostion: Position 24 | elementBounds: DOMRect 25 | currentScroll: number 26 | lastScroll: number 27 | scrollSpeed: number 28 | observer: IntersectionObserver 29 | 30 | constructor({ element, scene, sizes }: Props) { 31 | this.element = element 32 | this.scene = scene 33 | this.sizes = sizes 34 | 35 | this.currentScroll = 0 36 | this.lastScroll = 0 37 | this.scrollSpeed = 0 38 | 39 | this.createGeometry() 40 | this.createMaterial() 41 | this.createMesh() 42 | this.setNodeBounds() 43 | this.setMeshDimensions() 44 | this.setMeshPosition() 45 | 46 | this.setTexture() 47 | 48 | this.scene.add(this.mesh) 49 | } 50 | 51 | createGeometry() { 52 | this.geometry = new THREE.PlaneGeometry(1, 1, 1, 1) 53 | } 54 | 55 | createMaterial() { 56 | this.material = new THREE.ShaderMaterial({ 57 | vertexShader, 58 | fragmentShader, 59 | uniforms: { 60 | uTexture: new THREE.Uniform(new THREE.Vector4()), 61 | uResolution: new THREE.Uniform(new THREE.Vector2(0, 0)), 62 | uProgress: new THREE.Uniform(0), 63 | uColor: new THREE.Uniform(new THREE.Color('#242424')), 64 | }, 65 | }) 66 | } 67 | 68 | createMesh() { 69 | this.mesh = new THREE.Mesh(this.geometry, this.material) 70 | } 71 | 72 | setNodeBounds() { 73 | this.elementBounds = this.element.getBoundingClientRect() 74 | 75 | this.nodeDimensions = { 76 | width: this.elementBounds.width, 77 | height: this.elementBounds.height, 78 | } 79 | } 80 | 81 | setMeshDimensions() { 82 | this.meshDimensions = { 83 | width: (this.nodeDimensions.width * this.sizes.width) / window.innerWidth, 84 | height: (this.nodeDimensions.height * this.sizes.height) / window.innerHeight, 85 | } 86 | 87 | this.mesh.scale.x = this.meshDimensions.width 88 | this.mesh.scale.y = this.meshDimensions.height 89 | } 90 | 91 | setMeshPosition() { 92 | this.meshPostion = { 93 | x: (this.elementBounds.left * this.sizes.width) / window.innerWidth, 94 | y: (-this.elementBounds.top * this.sizes.height) / window.innerHeight, 95 | } 96 | 97 | this.meshPostion.x -= this.sizes.width / 2 98 | this.meshPostion.x += this.meshDimensions.width / 2 99 | 100 | this.meshPostion.y -= this.meshDimensions.height / 2 101 | this.meshPostion.y += this.sizes.height / 2 102 | 103 | this.mesh.position.x = this.meshPostion.x 104 | this.mesh.position.y = this.meshPostion.y 105 | } 106 | 107 | setTexture() { 108 | this.material.uniforms.uTexture.value = new THREE.TextureLoader().load(this.element.src, ({ image }) => { 109 | const { naturalWidth, naturalHeight } = image 110 | 111 | this.material.uniforms.uResolution.value = new THREE.Vector2(naturalWidth, naturalHeight) 112 | this.observe() 113 | }) 114 | } 115 | 116 | updateScroll(scrollY: number) { 117 | this.currentScroll = (-scrollY * this.sizes.height) / window.innerHeight 118 | 119 | const deltaScroll = this.currentScroll - this.lastScroll 120 | this.lastScroll = this.currentScroll 121 | 122 | this.updateY(deltaScroll) 123 | } 124 | 125 | updateY(deltaScroll: number) { 126 | this.meshPostion.y -= deltaScroll 127 | this.mesh.position.y = this.meshPostion.y 128 | } 129 | 130 | onVisible() { 131 | gsap.to(this.material.uniforms.uProgress, { 132 | value: 1, 133 | duration: 1.6, 134 | ease: 'linear', 135 | }) 136 | } 137 | 138 | onInvisible() { 139 | gsap.set(this.material.uniforms.uProgress, { 140 | value: 0, 141 | }) 142 | } 143 | 144 | observe() { 145 | this.observer = new IntersectionObserver( 146 | (entries) => { 147 | const isVisible = entries[0].isIntersecting 148 | 149 | if (isVisible) { 150 | this.onVisible() 151 | } else { 152 | this.onInvisible() 153 | } 154 | }, 155 | { 156 | threshold: 0, 157 | } 158 | ) 159 | 160 | this.observer.observe(this.element) 161 | } 162 | 163 | onResize(sizes: Size) { 164 | this.sizes = sizes 165 | 166 | this.setNodeBounds() 167 | this.setMeshDimensions() 168 | this.setMeshPosition() 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/components/scroll.ts: -------------------------------------------------------------------------------- 1 | import Lenis from 'lenis' 2 | import '../lenis.css' 3 | 4 | export default class Scroll { 5 | lenis: Lenis 6 | scroll: number 7 | 8 | constructor() { 9 | this.scroll = 0 10 | this.lenis = new Lenis() 11 | 12 | this.lenis.on('scroll', (e: Lenis) => { 13 | this.scroll = e.scroll as number 14 | }) 15 | 16 | requestAnimationFrame(this.raf.bind(this)) 17 | 18 | window.addEventListener('resize', () => { 19 | this.lenis.resize() 20 | }) 21 | } 22 | 23 | getScroll() { 24 | return this.scroll 25 | } 26 | 27 | raf(time: number) { 28 | this.lenis.raf(time) 29 | requestAnimationFrame(this.raf.bind(this)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/lenis.css: -------------------------------------------------------------------------------- 1 | html.lenis, 2 | html.lenis body { 3 | height: auto; 4 | } 5 | 6 | .lenis.lenis-smooth { 7 | scroll-behavior: auto !important; 8 | } 9 | 10 | .lenis.lenis-smooth [data-lenis-prevent] { 11 | overscroll-behavior: contain; 12 | } 13 | 14 | .lenis.lenis-stopped { 15 | overflow: hidden; 16 | } 17 | 18 | .lenis.lenis-smooth iframe { 19 | pointer-events: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import Canvas from './components/canvas' 3 | import Scroll from './components/scroll' 4 | 5 | class App { 6 | canvas: Canvas 7 | scroll: Scroll 8 | 9 | constructor() { 10 | this.scroll = new Scroll() 11 | this.canvas = new Canvas() 12 | 13 | this.render() 14 | } 15 | 16 | render() { 17 | const s = this.scroll.getScroll() 18 | 19 | this.canvas.render(s) 20 | 21 | requestAnimationFrame(this.render.bind(this)) 22 | } 23 | } 24 | 25 | export default new App() 26 | -------------------------------------------------------------------------------- /src/shaders/fragment.glsl: -------------------------------------------------------------------------------- 1 | uniform sampler2D uTexture; 2 | varying vec2 vUv; 3 | 4 | uniform vec2 uResolution; 5 | uniform float uProgress; 6 | uniform vec3 uColor; 7 | 8 | float random (vec2 st) { 9 | return fract(sin(dot(st.xy, 10 | vec2(12.9898,78.233)))* 11 | 43758.5453123); 12 | } 13 | 14 | vec2 squaresGrid(vec2 vUv) 15 | { 16 | float imageAspectX = 1.; 17 | float imageAspectY = 1.; 18 | 19 | float containerAspectX = uResolution.x/uResolution.y; 20 | float containerAspectY = uResolution.y/uResolution.x; 21 | 22 | vec2 ratio = vec2( 23 | min(containerAspectX / imageAspectX, 1.0), 24 | min(containerAspectY / imageAspectY, 1.0) 25 | ); 26 | 27 | vec2 squareUvs = vec2( 28 | vUv.x * ratio.x + (1.0 - ratio.x) * 0.5, 29 | vUv.y * ratio.y + (1.0 - ratio.y) * 0.5 30 | ); 31 | 32 | return squareUvs; 33 | } 34 | 35 | 36 | void main() 37 | { 38 | 39 | vec2 newUvs = vUv; 40 | 41 | 42 | //generate grid 43 | vec2 squareUvs = squaresGrid(vUv); 44 | float gridSize = 20.; 45 | vec2 grid = vec2(floor(squareUvs.x*gridSize)/gridSize,floor(squareUvs.y*gridSize)/gridSize); 46 | vec4 gridTexture = vec4(uColor,0.); 47 | 48 | 49 | //image texture 50 | vec4 texture = texture2D(uTexture,vUv); 51 | 52 | float height = 0.2; 53 | 54 | float progress = (1.+height)-(uProgress*(1.+height+height)); //goes from 1+height to -height 55 | 56 | 57 | float dist = 1.-distance(grid.y,progress); 58 | 59 | float clampedDist = smoothstep(height,0.,distance(grid.y,progress)); 60 | 61 | float randDist=step(1.-height*random(grid),dist); 62 | dist=step(1.-height,dist); 63 | 64 | float rand = random(grid); 65 | 66 | float alpha = dist*(clampedDist+rand-0.5*(1.-randDist)); 67 | alpha=max(0.,alpha); 68 | gridTexture.a = alpha; 69 | 70 | 71 | texture.rgba *= step(progress,grid.y); 72 | 73 | gl_FragColor = vec4(mix(texture,gridTexture,gridTexture.a)); 74 | } -------------------------------------------------------------------------------- /src/shaders/vertex.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main() 4 | { 5 | vUv=uv; 6 | 7 | gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); 8 | 9 | } -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | div, 4 | span, 5 | applet, 6 | object, 7 | iframe, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | p, 15 | blockquote, 16 | pre, 17 | a, 18 | abbr, 19 | acronym, 20 | address, 21 | big, 22 | cite, 23 | code, 24 | del, 25 | dfn, 26 | em, 27 | img, 28 | ins, 29 | kbd, 30 | q, 31 | s, 32 | samp, 33 | small, 34 | strike, 35 | strong, 36 | sub, 37 | sup, 38 | tt, 39 | var, 40 | b, 41 | u, 42 | i, 43 | center, 44 | dl, 45 | dt, 46 | dd, 47 | ol, 48 | ul, 49 | li, 50 | fieldset, 51 | form, 52 | label, 53 | legend, 54 | table, 55 | caption, 56 | tbody, 57 | tfoot, 58 | thead, 59 | tr, 60 | th, 61 | td, 62 | article, 63 | aside, 64 | canvas, 65 | details, 66 | embed, 67 | figure, 68 | figcaption, 69 | footer, 70 | header, 71 | hgroup, 72 | menu, 73 | nav, 74 | output, 75 | ruby, 76 | section, 77 | summary, 78 | time, 79 | mark, 80 | audio, 81 | video { 82 | margin: 0; 83 | padding: 0; 84 | border: 0; 85 | font-size: 100%; 86 | font: inherit; 87 | vertical-align: baseline; 88 | } 89 | /* HTML5 display-role reset for older browsers */ 90 | article, 91 | aside, 92 | details, 93 | figcaption, 94 | figure, 95 | footer, 96 | header, 97 | hgroup, 98 | menu, 99 | nav, 100 | section { 101 | display: block; 102 | } 103 | body { 104 | line-height: 1; 105 | } 106 | ol, 107 | ul { 108 | list-style: none; 109 | } 110 | blockquote, 111 | q { 112 | quotes: none; 113 | } 114 | blockquote:before, 115 | blockquote:after, 116 | q:before, 117 | q:after { 118 | content: ''; 119 | content: none; 120 | } 121 | table { 122 | border-collapse: collapse; 123 | border-spacing: 0; 124 | } 125 | 126 | #app { 127 | position: relative; 128 | z-index: 1; 129 | } 130 | 131 | #webgl { 132 | position: fixed; 133 | z-index: 0; 134 | inset: 0; 135 | top: 0; 136 | left: 0; 137 | } 138 | 139 | .container { 140 | display: flex; 141 | flex-direction: column; 142 | padding: 3vmax 0; 143 | gap: 3vmax; 144 | } 145 | 146 | .grid { 147 | display: grid; 148 | padding: 6vmax; 149 | gap: 3vmax; 150 | grid-template-columns: repeat(3, 1fr); 151 | } 152 | 153 | .container img { 154 | max-width: 100%; 155 | opacity: 0; 156 | } 157 | -------------------------------------------------------------------------------- /src/types/types.ts: -------------------------------------------------------------------------------- 1 | export interface Size { 2 | width: number 3 | height: number 4 | } 5 | 6 | export interface Dimensions { 7 | width: number 8 | height: number 9 | pixelRatio: number 10 | } 11 | 12 | export interface Position { 13 | x: number 14 | y: number 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | "types": [ 9 | "vite-plugin-glsl/ext" 10 | ], 11 | 12 | /* Bundler mode */ 13 | "moduleResolution": "bundler", 14 | "allowImportingTsExtensions": true, 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "moduleDetection": "force", 18 | "noEmit": true, 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "strictPropertyInitialization": false, 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noFallthroughCasesInSwitch": true 26 | }, 27 | "include": ["src"] 28 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import glsl from 'vite-plugin-glsl' 3 | import { defineConfig } from 'vite' 4 | 5 | export default defineConfig({ 6 | plugins: [glsl()], 7 | }) 8 | --------------------------------------------------------------------------------