├── .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 |
--------------------------------------------------------------------------------