├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── .DS_Store ├── 1.webp ├── 2.webp ├── 3.webp ├── 4.webp └── 5.webp ├── css ├── base.css └── demo.css ├── favicon.ico ├── index.html ├── js ├── script.js └── utils.js ├── package.json ├── shader ├── baseFragment.glsl ├── baseVertex.glsl ├── effectFragment.glsl ├── effectVertex.glsl └── resources │ ├── noise.glsl │ └── utils.glsl ├── vite.config.js └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/codrops-shader-on-scroll/8120ad43def74a1243761a3f5f064cc1262afc2a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | .parcel-cache 4 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2009 - 2024 [Codrops](https://tympanus.net/codrops) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # On-Scroll Image Distortion and Grain Effect 2 | 3 | Demo for the tutorial on how to create on-scroll distortion and grain effect with shaders in Three.js. 4 | 5 | ![Image](https://tympanus.net/codrops/wp-content/uploads/2024/07/grainscroll.png) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=78948) 8 | 9 | [Demo](http://tympanus.net/Tutorials/ShaderOnScroll/) 10 | 11 | ## Installation 12 | 13 | Install dependencies: 14 | ``` 15 | yarn install 16 | ``` 17 | 18 | Run local environment: 19 | ``` 20 | yarn dev 21 | ``` 22 | 23 | Create build: 24 | ``` 25 | yarn build 26 | ``` 27 | 28 | ## Credits 29 | 30 | - Images generated with [Midjourney](https://midjourney.com) 31 | 32 | ## Misc 33 | 34 | Follow Jan Kohlbach: [Twitter](https://x.com/jankohlbach), [Instagram](https://instagram.com/jankohlbach.work), [GitHub](https://github.com/jankohlbach) 35 | 36 | Follow Codrops: [X](http://www.X.com/codrops), [Facebook](http://www.facebook.com/codrops), [GitHub](https://github.com/codrops), [Instagram](https://www.instagram.com/codropsss/) 37 | 38 | ## License 39 | [MIT](LICENSE) 40 | 41 | Made with 💙 by [Codrops](http://www.codrops.com) and [Jan Kohlbach](https://x.com/jankohlbach) 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/codrops-shader-on-scroll/8120ad43def74a1243761a3f5f064cc1262afc2a/assets/.DS_Store -------------------------------------------------------------------------------- /assets/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/codrops-shader-on-scroll/8120ad43def74a1243761a3f5f064cc1262afc2a/assets/1.webp -------------------------------------------------------------------------------- /assets/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/codrops-shader-on-scroll/8120ad43def74a1243761a3f5f064cc1262afc2a/assets/2.webp -------------------------------------------------------------------------------- /assets/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/codrops-shader-on-scroll/8120ad43def74a1243761a3f5f064cc1262afc2a/assets/3.webp -------------------------------------------------------------------------------- /assets/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/codrops-shader-on-scroll/8120ad43def74a1243761a3f5f064cc1262afc2a/assets/4.webp -------------------------------------------------------------------------------- /assets/5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/codrops-shader-on-scroll/8120ad43def74a1243761a3f5f064cc1262afc2a/assets/5.webp -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 13px; 9 | --color-text: #232222; 10 | --color-bg: #fff; 11 | --color-link: #232222; 12 | --color-link-hover: #000; 13 | --page-padding: 1.5rem; 14 | } 15 | 16 | body { 17 | margin: 0; 18 | color: var(--color-text); 19 | background-color: var(--color-bg); 20 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | /* Page Loader */ 26 | .js .loading::before, 27 | .js .loading::after { 28 | content: ''; 29 | position: fixed; 30 | z-index: 10000; 31 | } 32 | 33 | .js .loading::before { 34 | top: 0; 35 | left: 0; 36 | width: 100%; 37 | height: 100%; 38 | background: var(--color-bg); 39 | } 40 | 41 | .js .loading::after { 42 | top: 50%; 43 | left: 50%; 44 | width: 100px; 45 | height: 1px; 46 | margin: 0 0 0 -50px; 47 | background: var(--color-link); 48 | animation: loaderAnim 1.5s ease-in-out infinite alternate forwards; 49 | 50 | } 51 | 52 | @keyframes loaderAnim { 53 | 0% { 54 | transform: scaleX(0); 55 | transform-origin: 0% 50%; 56 | } 57 | 50% { 58 | transform: scaleX(1); 59 | transform-origin: 0% 50%; 60 | } 61 | 50.1% { 62 | transform: scaleX(1); 63 | transform-origin: 100% 50%; 64 | } 65 | 100% { 66 | transform: scaleX(0); 67 | transform-origin: 100% 50%; 68 | } 69 | } 70 | 71 | a { 72 | text-decoration: none; 73 | color: var(--color-link); 74 | outline: none; 75 | cursor: pointer; 76 | } 77 | 78 | a:hover { 79 | text-decoration: underline; 80 | color: var(--color-link-hover); 81 | outline: none; 82 | } 83 | 84 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */ 85 | a:focus { 86 | /* Provide a fallback style for browsers 87 | that don't support :focus-visible */ 88 | outline: none; 89 | background: lightgrey; 90 | } 91 | 92 | a:focus:not(:focus-visible) { 93 | /* Remove the focus indicator on mouse-focus for browsers 94 | that do support :focus-visible */ 95 | background: transparent; 96 | } 97 | 98 | a:focus-visible { 99 | /* Draw a very noticeable focus style for 100 | keyboard-focus on browsers that do support 101 | :focus-visible */ 102 | outline: 2px solid red; 103 | background: transparent; 104 | } 105 | 106 | .unbutton { 107 | background: none; 108 | border: 0; 109 | padding: 0; 110 | margin: 0; 111 | font: inherit; 112 | cursor: pointer; 113 | } 114 | 115 | .unbutton:focus { 116 | outline: none; 117 | } 118 | 119 | .frame { 120 | padding: var(--page-padding); 121 | position: relative; 122 | display: grid; 123 | z-index: 1000; 124 | width: 100%; 125 | height: 100%; 126 | grid-row-gap: 1rem; 127 | grid-column-gap: 2rem; 128 | pointer-events: none; 129 | justify-items: start; 130 | grid-template-columns: auto auto; 131 | grid-template-areas: 'title' 'archive' 'back' 'github' 'sponsor' 'demos' 'tags'; 132 | } 133 | 134 | .frame #cdawrap { 135 | justify-self: start; 136 | } 137 | 138 | .frame a { 139 | pointer-events: auto; 140 | } 141 | 142 | .frame__title { 143 | grid-area: title; 144 | font-size: inherit; 145 | font-weight: 700; 146 | margin: 0; 147 | } 148 | 149 | .frame__back { 150 | grid-area: back; 151 | justify-self: start; 152 | } 153 | 154 | .frame__archive { 155 | grid-area: archive; 156 | justify-self: start; 157 | } 158 | 159 | .frame__sub { 160 | grid-area: sub; 161 | } 162 | 163 | .frame__github { 164 | grid-area: github; 165 | } 166 | 167 | .frame__tags { 168 | grid-area: tags; 169 | display: flex; 170 | gap: 1rem; 171 | } 172 | 173 | .frame__hire { 174 | grid-area: hire; 175 | } 176 | 177 | .frame__demos { 178 | grid-area: demos; 179 | display: flex; 180 | gap: 1rem; 181 | } 182 | 183 | .content { 184 | padding: var(--page-padding); 185 | display: flex; 186 | flex-direction: column; 187 | position: relative; 188 | } 189 | 190 | @media screen and (min-width: 53em) { 191 | body { 192 | --page-padding: 1rem; 193 | } 194 | .frame { 195 | position: fixed; 196 | top: 0; 197 | left: 0; 198 | width: 100%; 199 | height: 100%; 200 | grid-template-columns: auto auto 1fr auto 1fr; 201 | grid-template-rows: auto auto; 202 | align-content: space-between; 203 | grid-template-areas: 'title title ... back sponsor' 'tags tags ... github archive'; 204 | } 205 | .frame #cdawrap { 206 | justify-self: end; 207 | max-width: 250px; 208 | text-align: right; 209 | } 210 | .content { 211 | min-height: 100vh; 212 | justify-content: center; 213 | align-items: center; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /css/demo.css: -------------------------------------------------------------------------------- 1 | /* vars */ 2 | :root { 3 | --fluid-min: 375; 4 | --fluid-max: 1720; 5 | --container-max-width: 120rem; 6 | --grid-gutter: clamp(0.625rem, -0.2463rem + 3.7175vw, 3.75rem); 7 | --grid-columns: 6; 8 | } 9 | 10 | /* scroll */ 11 | .lenis.lenis-smooth { 12 | scroll-behavior: auto; 13 | } 14 | 15 | .lenis.lenis-smooth [data-lenis-prevent] { 16 | overscroll-behavior: contain; 17 | } 18 | 19 | .lenis.lenis-stopped { 20 | overflow: hidden; 21 | } 22 | 23 | /* grid */ 24 | .container { 25 | max-width: var(--container-max-width); 26 | margin-inline: auto; 27 | } 28 | 29 | .grid { 30 | display: grid; 31 | gap: calc(var(--grid-gutter) * 5) var(--grid-gutter); 32 | grid-template-columns: repeat(var(--grid-columns), 1fr); 33 | padding-block: 6rem; 34 | } 35 | 36 | /* webgl */ 37 | [data-webgl-media] { 38 | opacity: 0; 39 | } 40 | 41 | /* demo */ 42 | canvas { 43 | position: fixed; 44 | inset: 0; 45 | z-index: 100; 46 | width: 100%; 47 | height: 100%; 48 | opacity: 1; 49 | pointer-events: none; 50 | } 51 | 52 | .img-wrap { 53 | display: grid; 54 | gap: 1rem; 55 | grid-template-columns: repeat(3, 1fr); 56 | max-width: 100%; 57 | margin: 0; 58 | } 59 | 60 | .img { 61 | display: block; 62 | grid-column: 1 / span 2; 63 | max-width: 100%; 64 | } 65 | 66 | .img-wrap figcaption { 67 | max-width: 200px; 68 | align-self: end; 69 | } 70 | 71 | .img-wrap figcaption span { 72 | display: block; 73 | } 74 | 75 | .img-wrap-1 { 76 | grid-column: 2 / span 3; 77 | grid-row: 1; 78 | } 79 | 80 | .img-wrap-2 { 81 | grid-column: 4 / span 3; 82 | grid-row: 2; 83 | } 84 | 85 | .img-wrap-3 { 86 | grid-column: 3 / span 3; 87 | grid-row: 3; 88 | } 89 | 90 | .img-wrap-4 { 91 | grid-column: 1 / span 3; 92 | grid-row: 4; 93 | } 94 | 95 | .img-wrap-5 { 96 | grid-column: 4 / span 3; 97 | grid-row: 5; 98 | } 99 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/codrops-shader-on-scroll/8120ad43def74a1243761a3f5f064cc1262afc2a/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | On-Scroll Image Distortion and Grain Effect | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

On-Scroll Image Distortion and Grain Effect

19 | Read the tutorial 20 | All demos 21 | GitHub 22 | 27 |
28 |
29 | 30 |
31 |
32 |
33 | image of ceramic piece 34 |
X05Kenji Sato
35 |
36 |
37 | image of ceramic piece 38 |
M33Yusuke Tanaka
39 |
40 |
41 | image of ceramic piece 42 |
Y78Mei Yamamoto
43 |
44 |
45 | image of ceramic piece 46 |
K08Natsumi Ito
47 |
48 |
49 | image of ceramic piece 50 |
F03Miku Inoue
51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /js/script.js: -------------------------------------------------------------------------------- 1 | import Lenis from 'lenis' 2 | import gsap from 'gsap' 3 | import { CustomEase } from 'gsap/all' 4 | import * as THREE from 'three' 5 | 6 | import { resizeThreeCanvas, calcFov, debounce, lerp } from './utils' 7 | 8 | import baseVertex from '../shader/baseVertex.glsl' 9 | import baseFragment from '../shader/baseFragment.glsl' 10 | import effectVertex from '../shader/effectVertex.glsl' 11 | import effectFragment from '../shader/effectFragment.glsl' 12 | 13 | gsap.registerPlugin(CustomEase) 14 | 15 | // smooth scroll (lenis) 16 | let scroll = { 17 | scrollY: window.scrollY, 18 | scrollVelocity: 0 19 | } 20 | 21 | const lenis = new Lenis() 22 | 23 | lenis.on('scroll', (e) => { 24 | scroll.scrollY = window.scrollY 25 | scroll.scrollVelocity = e.velocity 26 | }) 27 | 28 | function scrollRaf(time) { 29 | lenis.raf(time) 30 | requestAnimationFrame(scrollRaf) 31 | } 32 | 33 | requestAnimationFrame(scrollRaf) 34 | 35 | // cursor position 36 | let cursorPos = { 37 | current: { x: 0.5, y: 0.5 }, 38 | target: { x: 0.5, y: 0.5 } 39 | } 40 | 41 | let cursorRaf 42 | 43 | const lerpCursorPos = () => { 44 | const x = lerp(cursorPos.current.x, cursorPos.target.x, 0.05) 45 | const y = lerp(cursorPos.current.y, cursorPos.target.y, 0.05) 46 | 47 | cursorPos.current.x = x 48 | cursorPos.current.y = y 49 | 50 | const delta = Math.sqrt( 51 | ((cursorPos.target.x - cursorPos.current.x) ** 2) + 52 | ((cursorPos.target.y - cursorPos.current.y) ** 2) 53 | ) 54 | 55 | if (delta < 0.001 && cursorRaf) { 56 | cancelAnimationFrame(cursorRaf) 57 | cursorRaf = null 58 | return 59 | } 60 | 61 | cursorRaf = requestAnimationFrame(lerpCursorPos) 62 | } 63 | 64 | window.addEventListener('mousemove', (event) => { 65 | cursorPos.target.x = (event.clientX / window.innerWidth) 66 | cursorPos.target.y = (event.clientY / window.innerHeight) 67 | 68 | if (!cursorRaf) { 69 | cursorRaf = requestAnimationFrame(lerpCursorPos) 70 | } 71 | }) 72 | 73 | // helper for image-to-webgl and uniform updates 74 | // this lerps when entering the texture with cursor 75 | const handleMouseEnter = (index) => { 76 | gsap.to( 77 | mediaStore[index], 78 | { mouseEnter: 1, duration: 0.6, ease: CustomEase.create('custom', '0.4, 0, 0.2, 1') } 79 | ) 80 | } 81 | 82 | // this updates the cursor position uniform on the texture 83 | const handleMousePos = (e, index) => { 84 | const bounds = mediaStore[index].media.getBoundingClientRect() 85 | const x = e.offsetX / bounds.width 86 | const y = e.offsetY / bounds.height 87 | 88 | mediaStore[index].mouseOverPos.target.x = x 89 | mediaStore[index].mouseOverPos.target.y = y 90 | } 91 | 92 | // this lerps when leaving the texture with cursor 93 | const handleMouseLeave = (index) => { 94 | gsap.to( 95 | mediaStore[index], 96 | { mouseEnter: 0, duration: 0.6, ease: CustomEase.create('custom', '0.4, 0, 0.2, 1') } 97 | ) 98 | gsap.to( 99 | mediaStore[index].mouseOverPos.target, 100 | { x: 0.5, y: 0.5, duration: 0.6, ease: CustomEase.create('custom', '0.4, 0, 0.2, 1') } 101 | ) 102 | } 103 | 104 | // this gets all image html tags and creates a mesh for each 105 | const setMediaStore = (scrollY) => { 106 | const media = [...document.querySelectorAll('[data-webgl-media]')] 107 | 108 | mediaStore = media.map((media, i) => { 109 | observer.observe(media) 110 | 111 | media.dataset.index = String(i) 112 | media.addEventListener('mouseenter', () => handleMouseEnter(i)) 113 | media.addEventListener('mousemove', e => handleMousePos(e, i)) 114 | media.addEventListener('mouseleave', () => handleMouseLeave(i)) 115 | 116 | const bounds = media.getBoundingClientRect() 117 | const imageMaterial = material.clone() 118 | 119 | const imageMesh = new THREE.Mesh(geometry, imageMaterial) 120 | 121 | let texture = null 122 | 123 | texture = new THREE.Texture(media) 124 | texture.needsUpdate = true 125 | 126 | imageMaterial.uniforms.uTexture.value = texture 127 | imageMaterial.uniforms.uTextureSize.value.x = media.naturalWidth 128 | imageMaterial.uniforms.uTextureSize.value.y = media.naturalHeight 129 | imageMaterial.uniforms.uQuadSize.value.x = bounds.width 130 | imageMaterial.uniforms.uQuadSize.value.y = bounds.height 131 | imageMaterial.uniforms.uBorderRadius.value = getComputedStyle(media).borderRadius.replace('px', '') 132 | 133 | imageMesh.scale.set(bounds.width, bounds.height, 1) 134 | 135 | if (!(bounds.top >= 0 && bounds.top <= window.innerHeight)) { 136 | imageMesh.position.y = 2 * window.innerHeight 137 | } 138 | 139 | scene.add(imageMesh) 140 | 141 | return { 142 | media, 143 | material: imageMaterial, 144 | mesh: imageMesh, 145 | width: bounds.width, 146 | height: bounds.height, 147 | top: bounds.top + scrollY, 148 | left: bounds.left, 149 | isInView: bounds.top >= -500 && bounds.top <= window.innerHeight + 500, 150 | mouseEnter: 0, 151 | mouseOverPos: { 152 | current: { 153 | x: 0.5, 154 | y: 0.5 155 | }, 156 | target: { 157 | x: 0.5, 158 | y: 0.5 159 | } 160 | } 161 | } 162 | }) 163 | } 164 | 165 | // this sets the position of the mesh based on the scroll position 166 | const setPositions = () => { 167 | mediaStore.forEach((object) => { 168 | if (object.isInView) { 169 | object.mesh.position.x = object.left - window.innerWidth / 2 + object.width / 2 170 | object.mesh.position.y = -object.top + window.innerHeight / 2 - object.height / 2 + scroll.scrollY 171 | } 172 | }) 173 | } 174 | 175 | // shader 176 | const CAMERA_POS = 500 177 | 178 | const canvas = document.querySelector('canvas') 179 | 180 | let observer 181 | let mediaStore 182 | let scene 183 | let geometry 184 | let material 185 | 186 | // create intersection observer to only render in view elements 187 | observer = new IntersectionObserver( 188 | (entries) => { 189 | entries.forEach((entry) => { 190 | const index = entry.target.dataset.index 191 | 192 | if (index) { 193 | mediaStore[parseInt(index)].isInView = entry.isIntersecting 194 | } 195 | }) 196 | }, 197 | { rootMargin: '500px 0px 500px 0px' } 198 | ) 199 | 200 | // scene 201 | scene = new THREE.Scene() 202 | 203 | // camera 204 | const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 10, 1000) 205 | camera.position.z = CAMERA_POS 206 | camera.fov = calcFov(CAMERA_POS) 207 | camera.updateProjectionMatrix() 208 | 209 | // geometry and material 210 | geometry = new THREE.PlaneGeometry(1, 1, 100, 100) 211 | material = new THREE.ShaderMaterial({ 212 | uniforms: { 213 | uResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }, 214 | uTime: { value: 0 }, 215 | uCursor: { value: new THREE.Vector2(0.5, 0.5) }, 216 | uScrollVelocity: { value: 0 }, 217 | uTexture: { value: null }, 218 | uTextureSize: { value: new THREE.Vector2(100, 100) }, 219 | uQuadSize: { value: new THREE.Vector2(100, 100) }, 220 | uBorderRadius: { value: 0 }, 221 | uMouseEnter: { value: 0 }, 222 | uMouseOverPos: { value: new THREE.Vector2(0.5, 0.5) } 223 | }, 224 | vertexShader: effectVertex, 225 | fragmentShader: effectFragment, 226 | glslVersion: THREE.GLSL3 227 | }) 228 | 229 | // renderer 230 | const renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true }) 231 | renderer.setSize(window.innerWidth, window.innerHeight) 232 | renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) 233 | 234 | // render loop 235 | const render = (time = 0) => { 236 | time /= 1000 237 | 238 | mediaStore.forEach((object) => { 239 | if (object.isInView) { 240 | object.mouseOverPos.current.x = lerp(object.mouseOverPos.current.x, object.mouseOverPos.target.x, 0.05) 241 | object.mouseOverPos.current.y = lerp(object.mouseOverPos.current.y, object.mouseOverPos.target.y, 0.05) 242 | 243 | object.material.uniforms.uResolution.value.x = window.innerWidth 244 | object.material.uniforms.uResolution.value.y = window.innerHeight 245 | object.material.uniforms.uTime.value = time 246 | object.material.uniforms.uCursor.value.x = cursorPos.current.x 247 | object.material.uniforms.uCursor.value.y = cursorPos.current.y 248 | object.material.uniforms.uScrollVelocity.value = scroll.scrollVelocity 249 | object.material.uniforms.uMouseOverPos.value.x = object.mouseOverPos.current.x 250 | object.material.uniforms.uMouseOverPos.value.y = object.mouseOverPos.current.y 251 | object.material.uniforms.uMouseEnter.value = object.mouseEnter 252 | } else { 253 | object.mesh.position.y = 2 * window.innerHeight 254 | } 255 | }) 256 | 257 | setPositions() 258 | 259 | renderer.render(scene, camera) 260 | 261 | requestAnimationFrame(render) 262 | } 263 | 264 | window.addEventListener('resize', debounce(() => { 265 | const fov = calcFov(CAMERA_POS) 266 | 267 | resizeThreeCanvas({ camera, fov, renderer }) 268 | 269 | mediaStore.forEach((object) => { 270 | const bounds = object.media.getBoundingClientRect() 271 | object.mesh.scale.set(bounds.width, bounds.height, 1) 272 | object.width = bounds.width 273 | object.height = bounds.height 274 | object.top = bounds.top + scroll.scrollY 275 | object.left = bounds.left 276 | object.isInView = bounds.top >= 0 && bounds.top <= window.innerHeight, 277 | object.material.uniforms.uTextureSize.value.x = object.media.naturalWidth 278 | object.material.uniforms.uTextureSize.value.y = object.media.naturalHeight 279 | object.material.uniforms.uQuadSize.value.x = bounds.width 280 | object.material.uniforms.uQuadSize.value.y = bounds.height 281 | object.material.uniforms.uBorderRadius.value = getComputedStyle(object.media).borderRadius.replace('px', '') 282 | }) 283 | })) 284 | 285 | // Add the preloader logic 286 | window.addEventListener('load', () => { 287 | // media details 288 | setMediaStore(scroll.scrollY) 289 | 290 | requestAnimationFrame(render) 291 | 292 | document.body.classList.remove('loading') 293 | }) 294 | -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | import { PerspectiveCamera } from 'three' 2 | 3 | export const resizeThreeCanvas = ({ 4 | camera, 5 | fov = null, 6 | renderer, 7 | effectComposer = null 8 | }) => { 9 | if (camera instanceof PerspectiveCamera) { 10 | camera.aspect = window.innerWidth / window.innerHeight 11 | 12 | if (fov) { 13 | camera.fov = fov 14 | } 15 | } 16 | 17 | camera.updateProjectionMatrix() 18 | 19 | renderer.setSize(window.innerWidth, window.innerHeight) 20 | renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) 21 | 22 | if (effectComposer) { 23 | effectComposer.setSize(window.innerWidth, window.innerHeight) 24 | } 25 | } 26 | 27 | export const calcFov = (CAMERA_POS) => 2 * Math.atan((window.innerHeight / 2) / CAMERA_POS) * 180 / Math.PI 28 | 29 | export const debounce = (func, timeout = 300) => { 30 | let timer 31 | 32 | return (...args) => { 33 | clearTimeout(timer) 34 | timer = setTimeout(() => { func.apply(this, args) }, timeout) 35 | } 36 | } 37 | 38 | export const lerp = (start, end, damping) => start * (1 - damping) + end * damping 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codrops-shader-on-scroll", 3 | "version": "1.0.0", 4 | "author": "Jan Kohlbach", 5 | "license": "MIT", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vite build" 10 | }, 11 | "dependencies": { 12 | "gsap": "^3.12.5", 13 | "lenis": "^1.1.5", 14 | "three": "^0.166.0", 15 | "vite": "^5.3.2", 16 | "vite-plugin-glsl": "^1.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /shader/baseFragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | 20 | void main() { 21 | // texture 22 | vec3 texture = vec3(texture(uTexture, vUvCover)); 23 | 24 | // output 25 | outColor = vec4(texture, 1.0); 26 | } 27 | -------------------------------------------------------------------------------- /shader/baseVertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include './resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /shader/effectFragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | #include './resources/noise.glsl'; 18 | 19 | out vec4 outColor; 20 | 21 | 22 | void main() { 23 | vec2 texCoords = vUvCover; 24 | 25 | // aspect ratio needed to create a real circle when quadSize is not 1:1 ratio 26 | float aspectRatio = uQuadSize.y / uQuadSize.x; 27 | 28 | // create a circle following the mouse with size 15 29 | float circle = 1.0 - distance( 30 | vec2(uMouseOverPos.x, (1.0 - uMouseOverPos.y) * aspectRatio), 31 | vec2(vUv.x, vUv.y * aspectRatio) 32 | ) * 15.0; 33 | 34 | // create noise 35 | float noise = snoise(gl_FragCoord.xy); 36 | 37 | // modify texture coordinates 38 | texCoords.x += mix(0.0, circle * noise * 0.01, uMouseEnter + uScrollVelocity * 0.1); 39 | texCoords.y += mix(0.0, circle * noise * 0.01, uMouseEnter + uScrollVelocity * 0.1); 40 | 41 | // texture 42 | vec3 texture = vec3(texture(uTexture, texCoords)); 43 | 44 | // output 45 | outColor = vec4(texture, 1.0); 46 | } 47 | -------------------------------------------------------------------------------- /shader/effectVertex.glsl: -------------------------------------------------------------------------------- 1 | float PI = 3.141592653589793; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | #include './resources/utils.glsl'; 15 | 16 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 17 | out vec2 vUvCover; 18 | 19 | 20 | vec3 deformationCurve(vec3 position, vec2 uv) { 21 | position.y = position.y - (sin(uv.x * PI) * min(abs(uScrollVelocity), 5.0) * sign(uScrollVelocity) * -0.01); 22 | 23 | return position; 24 | } 25 | 26 | void main() { 27 | vUv = uv; 28 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 29 | 30 | vec3 deformedPosition = deformationCurve(position, vUvCover); 31 | 32 | gl_Position = projectionMatrix * modelViewMatrix * vec4(deformedPosition, 1.0); 33 | } 34 | -------------------------------------------------------------------------------- /shader/resources/noise.glsl: -------------------------------------------------------------------------------- 1 | // 2 | // Description : Array and textureless GLSL 2D simplex noise function. 3 | // Author : Ian McEwan, Ashima Arts. 4 | // Maintainer : stegu 5 | // Lastmod : 20110822 (ijm) 6 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 7 | // Distributed under the MIT License. See LICENSE file. 8 | // https://github.com/ashima/webgl-noise 9 | // https://github.com/stegu/webgl-noise 10 | // 11 | 12 | vec3 mod289(vec3 x) { 13 | return x - floor(x * (1.0 / 289.0)) * 289.0; 14 | } 15 | 16 | vec2 mod289(vec2 x) { 17 | return x - floor(x * (1.0 / 289.0)) * 289.0; 18 | } 19 | 20 | vec3 permute(vec3 x) { 21 | return mod289(((x*34.0)+10.0)*x); 22 | } 23 | 24 | float snoise(vec2 v) 25 | { 26 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 27 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 28 | -0.577350269189626, // -1.0 + 2.0 * C.x 29 | 0.024390243902439); // 1.0 / 41.0 30 | // First corner 31 | vec2 i = floor(v + dot(v, C.yy) ); 32 | vec2 x0 = v - i + dot(i, C.xx); 33 | 34 | // Other corners 35 | vec2 i1; 36 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 37 | //i1.y = 1.0 - i1.x; 38 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 39 | // x0 = x0 - 0.0 + 0.0 * C.xx ; 40 | // x1 = x0 - i1 + 1.0 * C.xx ; 41 | // x2 = x0 - 1.0 + 2.0 * C.xx ; 42 | vec4 x12 = x0.xyxy + C.xxzz; 43 | x12.xy -= i1; 44 | 45 | // Permutations 46 | i = mod289(i); // Avoid truncation effects in permutation 47 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) 48 | + i.x + vec3(0.0, i1.x, 1.0 )); 49 | 50 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); 51 | m = m*m ; 52 | m = m*m ; 53 | 54 | // Gradients: 41 points uniformly over a line, mapped onto a diamond. 55 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 56 | 57 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 58 | vec3 h = abs(x) - 0.5; 59 | vec3 ox = floor(x + 0.5); 60 | vec3 a0 = x - ox; 61 | 62 | // Normalise gradients implicitly by scaling m 63 | // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 64 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); 65 | 66 | // Compute final noise value at P 67 | vec3 g; 68 | g.x = a0.x * x0.x + h.x * x0.y; 69 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; 70 | return 130.0 * dot(m, g); 71 | } 72 | -------------------------------------------------------------------------------- /shader/resources/utils.glsl: -------------------------------------------------------------------------------- 1 | // random 2 | float random(vec2 st) { 3 | return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); 4 | } 5 | 6 | // contain 7 | vec2 getContainUvFrag(vec2 uv, vec2 textureSize, vec2 quadSize) { 8 | vec2 tempUv = uv - vec2(0.5); 9 | 10 | float quadAspect = quadSize.x / quadSize.y; 11 | float textureAspect = textureSize.x / textureSize.y; 12 | 13 | if (quadAspect > textureAspect) { 14 | tempUv *= vec2(quadAspect / textureAspect, 1.0); 15 | } else { 16 | tempUv *= vec2(1.0, textureAspect / quadAspect); 17 | } 18 | 19 | tempUv += vec2(0.5); 20 | 21 | return tempUv; 22 | } 23 | 24 | // cover 25 | vec2 getCoverUvVert(vec2 uv, vec2 textureSize, vec2 quadSize) { 26 | vec2 ratio = vec2( 27 | min((quadSize.x / quadSize.y) / (textureSize.x / textureSize.y), 1.0), 28 | min((quadSize.y / quadSize.x) / (textureSize.y / textureSize.x), 1.0) 29 | ); 30 | 31 | return vec2( 32 | uv.x * ratio.x + (1.0 - ratio.x) * 0.5, 33 | uv.y * ratio.y + (1.0 - ratio.y) * 0.5 34 | ); 35 | } 36 | 37 | vec2 getCoverUvFrag(vec2 uv, vec2 textureSize, vec2 quadSize) { 38 | vec2 tempUv = uv - vec2(0.5); 39 | 40 | float quadAspect = quadSize.x / quadSize.y; 41 | float textureAspect = textureSize.x / textureSize.y; 42 | 43 | if (quadAspect < textureAspect) { 44 | tempUv *= vec2(quadAspect / textureAspect, 1.0); 45 | } else { 46 | tempUv *= vec2(1.0, textureAspect / quadAspect); 47 | } 48 | 49 | tempUv += vec2(0.5); 50 | 51 | return tempUv; 52 | } 53 | 54 | // uv, rotation (in radians), mid (point to rotate around) 55 | vec2 rotate(vec2 uv, float rotation, vec2 mid) { 56 | return vec2( 57 | cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x, 58 | cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import glsl from 'vite-plugin-glsl' 3 | 4 | export default defineConfig({ 5 | base: './', // This ensures all assets are referenced relatively 6 | plugins: [ 7 | glsl(), 8 | ] 9 | }) 10 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@darkroom.engineering/tempus@^0.0.46": 6 | version "0.0.46" 7 | resolved "https://registry.npmjs.org/@darkroom.engineering/tempus/-/tempus-0.0.46.tgz" 8 | integrity sha512-s5vav3KMHYezvUCl4ee5epg0oimF6M8C9gAaKxFnFaTvX2q3ywFDryIv6XLd0mRFUt3S1uHDJqKaiEcs2ZVSvw== 9 | 10 | "@esbuild/aix-ppc64@0.21.5": 11 | version "0.21.5" 12 | resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" 13 | integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== 14 | 15 | "@esbuild/android-arm64@0.21.5": 16 | version "0.21.5" 17 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" 18 | integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== 19 | 20 | "@esbuild/android-arm@0.21.5": 21 | version "0.21.5" 22 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" 23 | integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== 24 | 25 | "@esbuild/android-x64@0.21.5": 26 | version "0.21.5" 27 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" 28 | integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== 29 | 30 | "@esbuild/darwin-arm64@0.21.5": 31 | version "0.21.5" 32 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" 33 | integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== 34 | 35 | "@esbuild/darwin-x64@0.21.5": 36 | version "0.21.5" 37 | resolved "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz" 38 | integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== 39 | 40 | "@esbuild/freebsd-arm64@0.21.5": 41 | version "0.21.5" 42 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" 43 | integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== 44 | 45 | "@esbuild/freebsd-x64@0.21.5": 46 | version "0.21.5" 47 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" 48 | integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== 49 | 50 | "@esbuild/linux-arm64@0.21.5": 51 | version "0.21.5" 52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" 53 | integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== 54 | 55 | "@esbuild/linux-arm@0.21.5": 56 | version "0.21.5" 57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" 58 | integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== 59 | 60 | "@esbuild/linux-ia32@0.21.5": 61 | version "0.21.5" 62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" 63 | integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== 64 | 65 | "@esbuild/linux-loong64@0.21.5": 66 | version "0.21.5" 67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" 68 | integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== 69 | 70 | "@esbuild/linux-mips64el@0.21.5": 71 | version "0.21.5" 72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" 73 | integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== 74 | 75 | "@esbuild/linux-ppc64@0.21.5": 76 | version "0.21.5" 77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" 78 | integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== 79 | 80 | "@esbuild/linux-riscv64@0.21.5": 81 | version "0.21.5" 82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" 83 | integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== 84 | 85 | "@esbuild/linux-s390x@0.21.5": 86 | version "0.21.5" 87 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" 88 | integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== 89 | 90 | "@esbuild/linux-x64@0.21.5": 91 | version "0.21.5" 92 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" 93 | integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== 94 | 95 | "@esbuild/netbsd-x64@0.21.5": 96 | version "0.21.5" 97 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" 98 | integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== 99 | 100 | "@esbuild/openbsd-x64@0.21.5": 101 | version "0.21.5" 102 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" 103 | integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== 104 | 105 | "@esbuild/sunos-x64@0.21.5": 106 | version "0.21.5" 107 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" 108 | integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== 109 | 110 | "@esbuild/win32-arm64@0.21.5": 111 | version "0.21.5" 112 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" 113 | integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== 114 | 115 | "@esbuild/win32-ia32@0.21.5": 116 | version "0.21.5" 117 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" 118 | integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== 119 | 120 | "@esbuild/win32-x64@0.21.5": 121 | version "0.21.5" 122 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" 123 | integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== 124 | 125 | "@rollup/pluginutils@^5.1.0": 126 | version "5.1.0" 127 | resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz" 128 | integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== 129 | dependencies: 130 | "@types/estree" "^1.0.0" 131 | estree-walker "^2.0.2" 132 | picomatch "^2.3.1" 133 | 134 | "@rollup/rollup-android-arm-eabi@4.18.0": 135 | version "4.18.0" 136 | resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" 137 | integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== 138 | 139 | "@rollup/rollup-android-arm64@4.18.0": 140 | version "4.18.0" 141 | resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" 142 | integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== 143 | 144 | "@rollup/rollup-darwin-arm64@4.18.0": 145 | version "4.18.0" 146 | resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" 147 | integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== 148 | 149 | "@rollup/rollup-darwin-x64@4.18.0": 150 | version "4.18.0" 151 | resolved "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz" 152 | integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== 153 | 154 | "@rollup/rollup-linux-arm-gnueabihf@4.18.0": 155 | version "4.18.0" 156 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" 157 | integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== 158 | 159 | "@rollup/rollup-linux-arm-musleabihf@4.18.0": 160 | version "4.18.0" 161 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" 162 | integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== 163 | 164 | "@rollup/rollup-linux-arm64-gnu@4.18.0": 165 | version "4.18.0" 166 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" 167 | integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== 168 | 169 | "@rollup/rollup-linux-arm64-musl@4.18.0": 170 | version "4.18.0" 171 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" 172 | integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== 173 | 174 | "@rollup/rollup-linux-powerpc64le-gnu@4.18.0": 175 | version "4.18.0" 176 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" 177 | integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== 178 | 179 | "@rollup/rollup-linux-riscv64-gnu@4.18.0": 180 | version "4.18.0" 181 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" 182 | integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== 183 | 184 | "@rollup/rollup-linux-s390x-gnu@4.18.0": 185 | version "4.18.0" 186 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" 187 | integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== 188 | 189 | "@rollup/rollup-linux-x64-gnu@4.18.0": 190 | version "4.18.0" 191 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" 192 | integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== 193 | 194 | "@rollup/rollup-linux-x64-musl@4.18.0": 195 | version "4.18.0" 196 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" 197 | integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== 198 | 199 | "@rollup/rollup-win32-arm64-msvc@4.18.0": 200 | version "4.18.0" 201 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" 202 | integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== 203 | 204 | "@rollup/rollup-win32-ia32-msvc@4.18.0": 205 | version "4.18.0" 206 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" 207 | integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== 208 | 209 | "@rollup/rollup-win32-x64-msvc@4.18.0": 210 | version "4.18.0" 211 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" 212 | integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== 213 | 214 | "@types/estree@1.0.5", "@types/estree@^1.0.0": 215 | version "1.0.5" 216 | resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" 217 | integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== 218 | 219 | esbuild@^0.21.3: 220 | version "0.21.5" 221 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz" 222 | integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== 223 | optionalDependencies: 224 | "@esbuild/aix-ppc64" "0.21.5" 225 | "@esbuild/android-arm" "0.21.5" 226 | "@esbuild/android-arm64" "0.21.5" 227 | "@esbuild/android-x64" "0.21.5" 228 | "@esbuild/darwin-arm64" "0.21.5" 229 | "@esbuild/darwin-x64" "0.21.5" 230 | "@esbuild/freebsd-arm64" "0.21.5" 231 | "@esbuild/freebsd-x64" "0.21.5" 232 | "@esbuild/linux-arm" "0.21.5" 233 | "@esbuild/linux-arm64" "0.21.5" 234 | "@esbuild/linux-ia32" "0.21.5" 235 | "@esbuild/linux-loong64" "0.21.5" 236 | "@esbuild/linux-mips64el" "0.21.5" 237 | "@esbuild/linux-ppc64" "0.21.5" 238 | "@esbuild/linux-riscv64" "0.21.5" 239 | "@esbuild/linux-s390x" "0.21.5" 240 | "@esbuild/linux-x64" "0.21.5" 241 | "@esbuild/netbsd-x64" "0.21.5" 242 | "@esbuild/openbsd-x64" "0.21.5" 243 | "@esbuild/sunos-x64" "0.21.5" 244 | "@esbuild/win32-arm64" "0.21.5" 245 | "@esbuild/win32-ia32" "0.21.5" 246 | "@esbuild/win32-x64" "0.21.5" 247 | 248 | estree-walker@^2.0.2: 249 | version "2.0.2" 250 | resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" 251 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 252 | 253 | fsevents@~2.3.2, fsevents@~2.3.3: 254 | version "2.3.3" 255 | resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" 256 | integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== 257 | 258 | gsap@^3.12.5: 259 | version "3.12.5" 260 | resolved "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz" 261 | integrity sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ== 262 | 263 | lenis@^1.1.5: 264 | version "1.1.5" 265 | resolved "https://registry.npmjs.org/lenis/-/lenis-1.1.5.tgz" 266 | integrity sha512-RNpGFoOVFGboCdJ7EUcMTUNZC7d/AJV1ad8eBTYBdL2TuwdWJ11Mb5jx4dLreHadH8Mg2o+3uVHQHTd4txasaw== 267 | dependencies: 268 | "@darkroom.engineering/tempus" "^0.0.46" 269 | 270 | nanoid@^3.3.7: 271 | version "3.3.7" 272 | resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" 273 | integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== 274 | 275 | picocolors@^1.0.0: 276 | version "1.0.1" 277 | resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" 278 | integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== 279 | 280 | picomatch@^2.3.1: 281 | version "2.3.1" 282 | resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" 283 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 284 | 285 | postcss@^8.4.38: 286 | version "8.4.38" 287 | resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz" 288 | integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== 289 | dependencies: 290 | nanoid "^3.3.7" 291 | picocolors "^1.0.0" 292 | source-map-js "^1.2.0" 293 | 294 | rollup@^4.13.0: 295 | version "4.18.0" 296 | resolved "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz" 297 | integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg== 298 | dependencies: 299 | "@types/estree" "1.0.5" 300 | optionalDependencies: 301 | "@rollup/rollup-android-arm-eabi" "4.18.0" 302 | "@rollup/rollup-android-arm64" "4.18.0" 303 | "@rollup/rollup-darwin-arm64" "4.18.0" 304 | "@rollup/rollup-darwin-x64" "4.18.0" 305 | "@rollup/rollup-linux-arm-gnueabihf" "4.18.0" 306 | "@rollup/rollup-linux-arm-musleabihf" "4.18.0" 307 | "@rollup/rollup-linux-arm64-gnu" "4.18.0" 308 | "@rollup/rollup-linux-arm64-musl" "4.18.0" 309 | "@rollup/rollup-linux-powerpc64le-gnu" "4.18.0" 310 | "@rollup/rollup-linux-riscv64-gnu" "4.18.0" 311 | "@rollup/rollup-linux-s390x-gnu" "4.18.0" 312 | "@rollup/rollup-linux-x64-gnu" "4.18.0" 313 | "@rollup/rollup-linux-x64-musl" "4.18.0" 314 | "@rollup/rollup-win32-arm64-msvc" "4.18.0" 315 | "@rollup/rollup-win32-ia32-msvc" "4.18.0" 316 | "@rollup/rollup-win32-x64-msvc" "4.18.0" 317 | fsevents "~2.3.2" 318 | 319 | source-map-js@^1.2.0: 320 | version "1.2.0" 321 | resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" 322 | integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== 323 | 324 | three@^0.166.0: 325 | version "0.166.0" 326 | resolved "https://registry.npmjs.org/three/-/three-0.166.0.tgz" 327 | integrity sha512-3Gw7oyZ/vCmz3RmNx1xuyNu7Ou/igDtoh953QsJh/QkAoi6B7jpkKwk05N8Y7/9bZeIE44zdC+i2KZNF+KWQ8A== 328 | 329 | vite-plugin-glsl@^1.3.0: 330 | version "1.3.0" 331 | resolved "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.3.0.tgz" 332 | integrity sha512-SzEoLet9Bp5VSozjrhUiSc3xX1+u7rCTjXAsq4qWM3u8UjilI76A9ucX/T+CRGQCe25j50GSY+9mKSGUVPET1w== 333 | dependencies: 334 | "@rollup/pluginutils" "^5.1.0" 335 | 336 | vite@^5.3.2: 337 | version "5.3.2" 338 | resolved "https://registry.npmjs.org/vite/-/vite-5.3.2.tgz" 339 | integrity sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA== 340 | dependencies: 341 | esbuild "^0.21.3" 342 | postcss "^8.4.38" 343 | rollup "^4.13.0" 344 | optionalDependencies: 345 | fsevents "~2.3.3" 346 | --------------------------------------------------------------------------------