├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── css └── base.css ├── favicon.ico ├── img ├── disp-01.png ├── disp-02.png ├── img-01.jpg ├── img-02.jpg ├── img-03.jpg ├── img-04.jpg ├── img-05.jpg ├── img-06.jpg ├── img-07.jpg ├── img-08.jpg ├── img-09.jpg ├── img-10.jpg ├── img-11.jpg ├── img-12.jpg ├── slideshow-01.jpg ├── slideshow-02.jpg ├── slideshow-03.jpg ├── slideshow-04.jpg └── thumbnail.jpg ├── index.html ├── index2.html ├── js ├── app.js ├── components │ └── Smooth.js ├── events │ ├── Events.js │ ├── Raf.js │ ├── Resize.js │ ├── Scroll.js │ └── index.js ├── gl │ ├── GlObject.js │ ├── Plane.js │ ├── Slideshow.js │ ├── glsl │ │ ├── fragment-01.glsl │ │ ├── fragment-02.glsl │ │ ├── vertex-01.glsl │ │ └── vertex-02.glsl │ └── index.js ├── index.js ├── index2.js └── utils │ ├── index.js │ ├── lerp.js │ └── preload.js ├── package-lock.json ├── package.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-proposal-class-properties"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2009 - 2020 [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 | # Wave Motion Effect with Three.js 2 | 3 | *Learn how to make a wave motion effect on an image with Three.js.* 4 | 5 | ![Wave motion effect](https://tympanus.net/codrops/wp-content/uploads/2020/03/WaveMotion_featured.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=48418) 8 | 9 | [Demo](http://tympanus.net/Tutorials/WaveMotionEffect/) 10 | 11 | ## Installation 12 | Run this command in the terminal 13 | ``` 14 | npm install 15 | ``` 16 | 17 | Compile the code 18 | ``` 19 | npm run dev 20 | ``` 21 | 22 | Compile the code with a local server 23 | ``` 24 | npm run watch 25 | ``` 26 | 27 | ## Credits 28 | 29 | - [Behind the scenes of WeCargo](https://medium.com/epicagency/behind-the-scenes-of-we-cargo-3999f5f559c) 30 | - [Three.js](https://threejs.org/docs/) 31 | - [The Book of Shaders](https://www.npmjs.com/package/glsl-noise) 32 | - [GLSL Noise](https://www.npmjs.com/package/glsl-noise) 33 | 34 | 35 | ## Misc 36 | 37 | Follow *@marioecg*: [Twitter](https://twitter.com/marioecg), [Instagram](https://www.instagram.com/marioecg/) 38 | 39 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [GitHub](https://github.com/codrops), [Instagram](https://www.instagram.com/codropsss/) 40 | 41 | ## License 42 | [MIT](LICENSE) 43 | 44 | Made with :blue_heart: by [Codrops](http://www.codrops.com) 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 14px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #424242; 14 | --color-bg: #190e17; 15 | --color-link: #999; 16 | --color-link-hover: #fff; 17 | --color-primary: #88e7c4; 18 | --color-secondary: #c5517d; 19 | --color-description: #3b343a; 20 | color: var(--color-text); 21 | background-color: var(--color-bg); 22 | font-family: pragmatica-extended, sans-serif; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | /* Page Loader */ 28 | .js .loading::before, 29 | .js .loading::after { 30 | content: ''; 31 | position: fixed; 32 | z-index: 1000; 33 | } 34 | 35 | .js .loading::before { 36 | top: 0; 37 | left: 0; 38 | width: 100%; 39 | height: 100%; 40 | background: var(--color-bg); 41 | } 42 | 43 | .js .loading::after { 44 | top: 50%; 45 | left: 50%; 46 | width: 60px; 47 | height: 60px; 48 | margin: -30px 0 0 -30px; 49 | border-radius: 50%; 50 | opacity: 0.4; 51 | background: var(--color-link); 52 | animation: loaderAnim 0.7s linear infinite alternate forwards; 53 | 54 | } 55 | 56 | @keyframes loaderAnim { 57 | to { 58 | opacity: 1; 59 | transform: scale3d(0.5, 0.5, 1); 60 | } 61 | } 62 | 63 | a { 64 | text-decoration: none; 65 | color: var(--color-link); 66 | outline: none; 67 | } 68 | 69 | a:hover, 70 | a:focus { 71 | color: var(--color-link-hover); 72 | outline: none; 73 | } 74 | 75 | .message { 76 | background: var(--color-text); 77 | color: var(--color-bg); 78 | padding: 1rem; 79 | text-align: center; 80 | } 81 | 82 | .frame { 83 | padding: 2rem 1rem; 84 | text-align: center; 85 | position: relative; 86 | z-index: 1000; 87 | background: #000; 88 | } 89 | 90 | .frame__title { 91 | font-size: 1rem; 92 | margin: 0 0 1rem; 93 | font-weight: normal; 94 | } 95 | 96 | .frame__links { 97 | display: inline; 98 | text-transform: lowercase; 99 | } 100 | 101 | .frame__links a:not(:last-child), 102 | .frame__demos a:not(:last-child) { 103 | margin-right: 1rem; 104 | } 105 | 106 | .frame__demos { 107 | margin: 1rem 0 0; 108 | } 109 | 110 | .frame__demo--current, 111 | .frame__demo--current:hover { 112 | color: var(--color-text); 113 | } 114 | 115 | /* Slideshow demo 1 */ 116 | .slideshow { 117 | display: grid; 118 | width: 100%; 119 | height: 100vh; 120 | grid-template-columns: 90% 10%; 121 | grid-template-rows: auto auto; 122 | } 123 | 124 | .slideshow__intro { 125 | grid-area: 1 / 1 / 3 / 3; 126 | align-self: center; 127 | justify-self: center; 128 | position: relative; 129 | } 130 | 131 | .slideshow__intro-title { 132 | font-family: vortice-concept, sans-serif; 133 | font-weight: 400; 134 | font-size: 9vw; 135 | line-height: 0.5; 136 | margin: 5vw 0 0 0; 137 | -webkit-text-stroke: 2px var(--color-primary); 138 | text-stroke: 2px var(--color-primary); 139 | -webkit-text-fill-color: transparent; 140 | text-fill-color: transparent; 141 | color: transparent; 142 | } 143 | 144 | .slideshow__intro-subline { 145 | text-align: center; 146 | width: 100%; 147 | display: block; 148 | color: var(--color-primary); 149 | font-size: 1.25rem; 150 | } 151 | 152 | .slideshow__intro-description { 153 | margin: 2rem 0 0 1rem; 154 | max-width: 300px; 155 | } 156 | 157 | .slideshow__img-wrap { 158 | position: relative; 159 | grid-area: 1 / 1 / 3 / 2; 160 | justify-self: end; 161 | display: grid; 162 | grid-template-columns: auto; 163 | grid-template-rows: auto; 164 | overflow: hidden; 165 | width: 47%; 166 | } 167 | 168 | 169 | .slideshow__img { 170 | display: none; 171 | position: absolute; 172 | top: 0; 173 | left: 0; 174 | max-width: 100%; 175 | height: 100%; 176 | object-fit: cover; 177 | } 178 | 179 | .slideshow__img--current { 180 | opacity: 1; 181 | } 182 | 183 | .slideshow__nav { 184 | grid-area: 2 / 1 / 3 / 3; 185 | display: flex; 186 | flex-direction: column; 187 | align-self: end; 188 | align-items: end; 189 | justify-content: space-between; 190 | padding: 2rem 3rem; 191 | z-index: 100; 192 | } 193 | 194 | .slideshow__nav-item { 195 | font-family: vortice-concept, sans-serif; 196 | font-weight: 400; 197 | color: var(--color-secondary); 198 | font-size: 1rem; 199 | cursor: pointer; 200 | } 201 | 202 | .slideshow__nav-item--current { 203 | color: var(--color-link-hover); 204 | } 205 | 206 | /* Grid demo 2 */ 207 | 208 | .content { 209 | display: grid; 210 | --column-gap: 2rem; 211 | --row-gap: 10vh; 212 | --mwidth: 400px; 213 | --columns: 3; 214 | max-width: calc(var(--mwidth) * var(--columns) + (var(--columns) - 1) * var(--column-gap)); 215 | width: 100%; 216 | margin: 0 auto; 217 | grid-template-columns: repeat(auto-fill, minmax(calc(var(--mwidth)/2), var(--mwidth))); 218 | grid-column-gap: var(--column-gap); 219 | grid-row-gap: var(--row-gap); 220 | justify-content: center; 221 | padding: 4rem 0 20rem; 222 | margin-top: 13rem; 223 | } 224 | 225 | .item { 226 | margin: 0; 227 | padding: 2rem; 228 | align-self: end; 229 | display: grid; 230 | grid-template-areas: 231 | "... ... ..." 232 | "pretitle pretitle pretitle" 233 | "title title tile" 234 | "... counter counter" 235 | "... description ..."; 236 | grid-template-columns: 10% 1fr 1rem; 237 | grid-template-rows: 3rem auto 1fr 2rem auto; 238 | } 239 | 240 | .item__fig { 241 | position: relative; 242 | max-width: 100%; 243 | grid-area: 1 / 2 / 4 / 3; 244 | margin: 0; 245 | } 246 | 247 | .aspect { 248 | padding-top: var(--aspect); 249 | } 250 | 251 | .item__img { 252 | display: none; 253 | position: absolute; 254 | top: 0; 255 | left: 0; 256 | max-width: 100%; 257 | } 258 | 259 | .item__pretitle { 260 | font-family: vortice-concept, sans-serif; 261 | font-weight: 400; 262 | color: var(--color-secondary); 263 | font-size: 1rem; 264 | margin: 0 0 0.75rem; 265 | grid-area: pretitle; 266 | display: flex; 267 | pointer-events: none; 268 | } 269 | 270 | .item__title { 271 | font-family: vortice-concept, sans-serif; 272 | font-weight: 400; 273 | margin: 0; 274 | -webkit-text-stroke: 1px var(--color-primary); 275 | text-stroke: 1px var(--color-primary); 276 | -webkit-text-fill-color: transparent; 277 | text-fill-color: transparent; 278 | color: transparent; 279 | font-size: 2.5rem; 280 | grid-area: title; 281 | pointer-events: none; 282 | } 283 | 284 | .item__counter { 285 | font-family: vortice-concept, sans-serif; 286 | font-weight: 400; 287 | color: var(--color-primary); 288 | font-size: 3rem; 289 | grid-area: counter; 290 | align-self: end; 291 | justify-self: end; 292 | line-height: 1; 293 | pointer-events: none; 294 | } 295 | 296 | .item__description { 297 | max-width: 200px; 298 | grid-area: description; 299 | font-size: 0.85rem; 300 | color: var(--color-description); 301 | } 302 | 303 | @media screen and (min-width: 53em) { 304 | .frame { 305 | position: fixed; 306 | text-align: left; 307 | z-index: 100; 308 | top: 0; 309 | left: 0; 310 | display: grid; 311 | align-content: space-between; 312 | width: 100%; 313 | max-width: none; 314 | height: 100vh; 315 | padding: 3rem; 316 | background: none; 317 | pointer-events: none; 318 | grid-template-columns: 75% 25%; 319 | grid-template-rows: auto auto auto; 320 | grid-template-areas: 'title demos' 321 | '... ...' 322 | '... ...'; 323 | } 324 | 325 | .frame__title-wrap { 326 | grid-area: title; 327 | } 328 | 329 | .frame__title { 330 | margin: 0; 331 | } 332 | 333 | .frame__tagline { 334 | position: relative; 335 | margin: 0 0 0 1rem; 336 | padding: 0 0 0 1rem; 337 | opacity: 0.5; 338 | } 339 | 340 | .frame__demos { 341 | margin: 0; 342 | grid-area: demos; 343 | justify-self: end; 344 | } 345 | 346 | .frame__links { 347 | grid-area: links; 348 | padding: 0; 349 | justify-self: end; 350 | } 351 | 352 | .frame a { 353 | pointer-events: auto; 354 | } 355 | 356 | .slideshow__nav { 357 | flex-direction: row; 358 | } 359 | } 360 | 361 | /* Canvas */ 362 | .dom-gl { 363 | position: fixed; 364 | z-index: -1; 365 | top: 0; 366 | left: 0; 367 | width: 100%; 368 | height: 100%; 369 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/favicon.ico -------------------------------------------------------------------------------- /img/disp-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/disp-01.png -------------------------------------------------------------------------------- /img/disp-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/disp-02.png -------------------------------------------------------------------------------- /img/img-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-01.jpg -------------------------------------------------------------------------------- /img/img-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-02.jpg -------------------------------------------------------------------------------- /img/img-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-03.jpg -------------------------------------------------------------------------------- /img/img-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-04.jpg -------------------------------------------------------------------------------- /img/img-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-05.jpg -------------------------------------------------------------------------------- /img/img-06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-06.jpg -------------------------------------------------------------------------------- /img/img-07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-07.jpg -------------------------------------------------------------------------------- /img/img-08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-08.jpg -------------------------------------------------------------------------------- /img/img-09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-09.jpg -------------------------------------------------------------------------------- /img/img-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-10.jpg -------------------------------------------------------------------------------- /img/img-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-11.jpg -------------------------------------------------------------------------------- /img/img-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/img-12.jpg -------------------------------------------------------------------------------- /img/slideshow-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/slideshow-01.jpg -------------------------------------------------------------------------------- /img/slideshow-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/slideshow-02.jpg -------------------------------------------------------------------------------- /img/slideshow-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/slideshow-03.jpg -------------------------------------------------------------------------------- /img/slideshow-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/slideshow-04.jpg -------------------------------------------------------------------------------- /img/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioecg/codrops-wave-motion/ae54980de4e04791303ab508bd4849aec25c18b1/img/thumbnail.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Wave Motion Effect | Demo 1 | Codrops 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 |
22 |
23 |

Wave Motion Effect

24 | 29 |
30 |
31 | demo 1 32 | demo 2 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Some image 42 |
43 | Cartesian man 44 |

Actuator

45 | 01 46 |

Life lived in the absence of the psychedelic experience is life trivialized, life denied, life enslaved to the ego.

47 |
48 |
49 |
50 |
51 | Some image 52 |
53 | Assembly fix 54 |

Random

55 | 02 56 |

You are a divine being. You matter, you count. You come from realms of unimaginable power and light, and you will return to those realms.

57 |
58 |
59 |
60 |
61 | Some image 62 |
63 | Dead head 64 |

Uplinking

65 | 03 66 |

You simply have to turn your back on a culture that has gone sterile and dead and get with the program of a living world and the imagination.

67 |
68 |
69 |
70 |
71 | Some image 72 |
73 | Absence 74 |

Half time

75 | 04 76 |

The syntactical nature of reality, the real secret of magic, is that the world is made of words. And if you know the words that the world is made of, you can make of it whatever you wish.

77 |
78 |
79 |
80 |
81 | Some image 82 |
83 | Material Handling 84 |

Operator

85 | 05 86 |

We tend to disempower ourselves. We tend to believe that we don’t matter. And in the act of taking that idea to ourselves we give everything away to somebody else, to something else.

87 |
88 |
89 |
90 |
91 | Some image 92 |
93 | Quality Assurance 94 |

Remote

95 | 06 96 |

We are so much the victims of abstraction that with the Earth in flames we can barely rouse ourselves to wander across the room and look at the thermostat.

97 |
98 |
99 |
100 |
101 | Some image 102 |
103 | Teach Mode 104 |

SLURBT

105 | 07 106 |

The cost of sanity, in this society, is a certain level of alienation.

107 |
108 |
109 |
110 |
111 | Some image 112 |
113 | World Model 114 |

Cybark

115 | 08 116 |

Our need to feel part of the world seems to demand that we express ourselves through creative activity.

117 |
118 |
119 |
120 |
121 | Some image 122 |
123 | Sensory guide 124 |

Alt Touch

125 | 09 126 |

We are caged by our cultural programming. Culture is a mass hallucination, and when you step outside the mass hallucination you see it for what it’s worth.

127 |
128 |
129 |
130 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Wave Motion Effect | Demo 2 | Codrops 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |

Wave Motion Effect

25 | 30 |
31 |
32 | demo 1 33 | demo 2 34 |
35 |
36 |
37 |
38 |
39 | Some image 40 | Some image 41 | Some image 42 | Some image 43 |
44 |
45 | A new cyber movement 46 |

Mechanism

47 |

“Your assumptions are your windows on the world. Scrub them off every once in a while, or the light won't come in.”

48 |
49 | 55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | import '../css/base.css'; 2 | import Smooth from './components/Smooth'; 3 | import Gl from './gl'; 4 | import Plane from './gl/Plane'; 5 | import Slideshow from './gl/Slideshow'; 6 | import { preloadImages } from './utils'; 7 | 8 | preloadImages().then(() => { 9 | document.body.classList.remove('loading'); 10 | 11 | if(window.location.pathname.includes('index2')) { 12 | const element = document.querySelector('.js-slideshow'); 13 | const slideshow = new Slideshow().init(element); 14 | } else { 15 | const elements = document.querySelectorAll('.js-plane'); 16 | elements.forEach((el, index) => new Plane().init(el, index)); 17 | const smooth = new Smooth(); 18 | } 19 | }); -------------------------------------------------------------------------------- /js/components/Smooth.js: -------------------------------------------------------------------------------- 1 | import imagesLoaded from 'imagesloaded'; 2 | import { Events } from '../events'; 3 | 4 | export default class Smooth { 5 | constructor() { 6 | this.bindMethods(); 7 | 8 | this.dom = { 9 | el: document.querySelector('[data-scroll]'), 10 | content: document.querySelector('[data-scroll-content]') 11 | }; 12 | 13 | this.init(); 14 | } 15 | 16 | bindMethods() { 17 | ['scroll', 'run', 'resize'] 18 | .forEach((fn) => this[fn] = this[fn].bind(this)); 19 | } 20 | 21 | setStyles() { 22 | Object.assign(this.dom.el.style, { 23 | position: 'fixed', 24 | top: 0, 25 | left: 0, 26 | height: '100%', 27 | width: '100%', 28 | overflow: 'hidden' 29 | }); 30 | } 31 | 32 | setHeight() { 33 | document.body.style.height = `${this.dom.content.offsetHeight}px`; 34 | } 35 | 36 | resize() { 37 | this.setHeight(); 38 | this.scroll(); 39 | } 40 | 41 | preload() { 42 | imagesLoaded(this.dom.content, (instance) => this.setHeight()); 43 | } 44 | 45 | scroll() { 46 | this.data.current = window.scrollY; 47 | } 48 | 49 | run({ current, target }) { 50 | const diff = target - current; 51 | const acc = diff / window.innerWidth; 52 | const velo =+ acc; 53 | 54 | this.dom.content.style.transform = `translate3d(0, -${current}px, 0)`; 55 | } 56 | 57 | on() { 58 | this.setStyles(); 59 | this.setHeight(); 60 | Events.on('tick', this.run); 61 | Events.on('resize', this.resize); 62 | } 63 | 64 | off() { 65 | Events.off('tick', this.run); 66 | Events.off('resize', this.resize); 67 | } 68 | 69 | destroy() { 70 | document.body.style.height = ''; 71 | 72 | this.data = null; 73 | 74 | this.removeEvents(); 75 | this.cancelAnimationFrame(); 76 | } 77 | 78 | resize() { 79 | this.setHeight(); 80 | } 81 | 82 | init() { 83 | this.preload(); 84 | this.on(); 85 | } 86 | } -------------------------------------------------------------------------------- /js/events/Events.js: -------------------------------------------------------------------------------- 1 | import Emitter from 'tiny-emitter'; 2 | 3 | export default new Emitter(); 4 | -------------------------------------------------------------------------------- /js/events/Raf.js: -------------------------------------------------------------------------------- 1 | import gsap from 'gsap'; 2 | import Events from './Events'; 3 | import { lerp } from '../utils' 4 | 5 | class Raf { 6 | constructor() { 7 | this.target = 0; 8 | this.current = 0; 9 | this.currentRounded = 0; 10 | this.ease = 0.115; 11 | 12 | this.init(); 13 | } 14 | 15 | tick() { 16 | this.current = lerp(this.current, this.target, this.ease); 17 | this.currentRounded = Math.round(this.current * 100) / 100; 18 | 19 | Events.emit('tick', { 20 | target: this.target, 21 | current: this.currentRounded, 22 | }); 23 | } 24 | 25 | onScroll({ y }) { 26 | this.target = y; 27 | } 28 | 29 | on() { 30 | gsap.ticker.add(this.tick.bind(this)); 31 | Events.on('scroll', this.onScroll.bind(this)); 32 | } 33 | 34 | init() { 35 | this.on(); 36 | } 37 | } 38 | 39 | export default new Raf(); 40 | -------------------------------------------------------------------------------- /js/events/Resize.js: -------------------------------------------------------------------------------- 1 | import Events from './Events'; 2 | 3 | class Resize { 4 | constructor() { 5 | this.init(); 6 | } 7 | 8 | onResize() { 9 | Events.emit('resize'); 10 | } 11 | 12 | on() { 13 | window.addEventListener('resize', this.onResize); 14 | } 15 | 16 | init() { 17 | this.on(); 18 | } 19 | } 20 | 21 | export default new Resize(); 22 | -------------------------------------------------------------------------------- /js/events/Scroll.js: -------------------------------------------------------------------------------- 1 | import Events from './Events'; 2 | 3 | class Scroll { 4 | constructor() { 5 | this.init(); 6 | } 7 | 8 | onScroll() { 9 | Events.emit('scroll', { y: window.scrollY }); 10 | } 11 | 12 | on() { 13 | window.addEventListener('scroll', this.onScroll); 14 | } 15 | 16 | init() { 17 | this.on(); 18 | } 19 | } 20 | 21 | export default new Scroll(); -------------------------------------------------------------------------------- /js/events/index.js: -------------------------------------------------------------------------------- 1 | import Events from './Events'; 2 | import GlobalRaf from './Raf'; 3 | import GlobalScroll from './Scroll'; 4 | import GlobalResize from './Resize'; 5 | 6 | export { 7 | Events, 8 | GlobalRaf, 9 | GlobalScroll, 10 | GlobalResize, 11 | }; 12 | -------------------------------------------------------------------------------- /js/gl/GlObject.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import gl from './index' 3 | import gsap from 'gsap'; 4 | import { GlobalRaf } from '../events'; 5 | 6 | export default class extends THREE.Object3D { 7 | init(el) { 8 | this.el = el; 9 | 10 | this.resize(); 11 | } 12 | 13 | setBounds() { 14 | this.rect = this.el.getBoundingClientRect(); 15 | 16 | this.bounds = { 17 | left: this.rect.left, 18 | top: this.rect.top + window.scrollY, 19 | width: this.rect.width, 20 | height: this.rect.height 21 | }; 22 | 23 | this.updateSize(); 24 | this.updatePosition(); 25 | } 26 | 27 | resize() { 28 | if (!this.visible) return; 29 | this.setBounds(); 30 | } 31 | 32 | calculateUnitSize(distance = this.position.z) { 33 | const vFov = gl.camera.fov * Math.PI / 180; 34 | const height = 2 * Math.tan(vFov / 2) * distance; 35 | const width = height * gl.camera.aspect; 36 | 37 | return { width, height }; 38 | } 39 | 40 | updateSize() { 41 | this.camUnit = this.calculateUnitSize(gl.camera.position.z - this.position.z); 42 | 43 | const x = this.bounds.width / window.innerWidth; 44 | const y = this.bounds.height / window.innerHeight; 45 | 46 | if (!x || !y) return; 47 | 48 | this.scale.x = this.camUnit.width * x; 49 | this.scale.y = this.camUnit.height * y; 50 | } 51 | 52 | updateY(y = 0) { 53 | const { top, height } = this.bounds; 54 | 55 | this.position.y = (this.camUnit.height / 2) - (this.scale.y / 2); 56 | this.position.y -= ((top - y) / window.innerHeight) * this.camUnit.height; 57 | 58 | this.progress = gsap.utils.clamp(0, 1, 1 - (-y + top + height) / (window.innerHeight + height)); 59 | } 60 | 61 | updateX(x = 0) { 62 | const { left } = this.bounds; 63 | 64 | this.position.x = -(this.camUnit.width / 2) + (this.scale.x / 2); 65 | this.position.x += ((left + x) / window.innerWidth) * this.camUnit.width; 66 | } 67 | 68 | updatePosition(y) { 69 | this.updateY(y); 70 | this.updateX(0); 71 | } 72 | } -------------------------------------------------------------------------------- /js/gl/Plane.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import Gl from './index' 3 | import GlObject from './GlObject' 4 | import vertex from './glsl/vertex-01.glsl' 5 | import fragment from './glsl/fragment-01.glsl' 6 | import gsap from 'gsap'; 7 | import glsl from 'glslify'; 8 | 9 | const planeGeometry = new THREE.PlaneBufferGeometry(1, 1, 32, 32); 10 | const planeMaterial = new THREE.ShaderMaterial({ 11 | vertexShader: glsl(vertex), 12 | fragmentShader: glsl(fragment), 13 | }); 14 | 15 | const loader = new THREE.TextureLoader(); 16 | 17 | export default class extends GlObject { 18 | init(el, index) { 19 | super.init(el); 20 | 21 | this.geometry = planeGeometry; 22 | this.material = planeMaterial.clone(); 23 | 24 | this.material.uniforms = { 25 | uTexture: { value: 0 }, 26 | uTime: { value: 0 }, 27 | uProg: { value: 0 }, 28 | uIndex: { value: index }, 29 | } 30 | 31 | this.img = this.el.querySelector('img'); 32 | this.texture = loader.load(this.img.src, (texture) => { 33 | texture.minFilter = THREE.LinearFilter; 34 | texture.generateMipmaps = false; 35 | 36 | this.material.uniforms.uTexture.value = texture; 37 | }) 38 | 39 | this.mesh = new THREE.Mesh(this.geometry, this.material); 40 | this.add(this.mesh); 41 | 42 | Gl.scene.add(this); 43 | this.addEvents(); 44 | } 45 | 46 | updateTime(time) { 47 | this.material.uniforms.uTime.value = time; 48 | } 49 | 50 | addEvents() { 51 | this.mouseEnter(); 52 | this.mouseLeave(); 53 | } 54 | 55 | mouseEnter() { 56 | this.el.addEventListener('mouseenter', () => { 57 | gsap.to(this.material.uniforms.uProg, { 58 | // duration: 1, 59 | value: 1, 60 | ease: 'power.inOut', 61 | }); 62 | }); 63 | } 64 | 65 | mouseLeave() { 66 | this.el.addEventListener('mouseleave', () => { 67 | gsap.to(this.material.uniforms.uProg, { 68 | // duration: 1, 69 | value: 0, 70 | ease: 'power.inOut', 71 | }); 72 | }); 73 | } 74 | } -------------------------------------------------------------------------------- /js/gl/Slideshow.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import Gl from './index' 3 | import GlObject from './GlObject' 4 | import vertexShader from './glsl/vertex-02.glsl' 5 | import fragmentShader from './glsl/fragment-02.glsl' 6 | import gsap from 'gsap'; 7 | import disp from '../../img/disp-02.png'; 8 | 9 | const planeGeometry = new THREE.PlaneBufferGeometry(1, 1, 32, 32); 10 | const planeMaterial = new THREE.ShaderMaterial({ 11 | vertexShader, 12 | fragmentShader, 13 | }); 14 | 15 | export default class extends GlObject { 16 | init(el) { 17 | super.init(el); 18 | 19 | this.geometry = planeGeometry; 20 | this.material = planeMaterial.clone(); 21 | 22 | this.material.uniforms = { 23 | uCurrTex: { value: 0 }, 24 | uNextTex: { value: 0 }, 25 | uDisp: { value: new THREE.TextureLoader().load(disp) }, 26 | uMeshSize: { value: [this.rect.width, this.rect.height] }, 27 | uImageSize: { value: [0, 0] }, 28 | uTime: { value: 0 }, 29 | uProg: { value: 0 }, 30 | }; 31 | 32 | this.textures = []; 33 | 34 | this.state = { 35 | animating: false, 36 | current: 0 37 | }; 38 | 39 | this.navItems = document.querySelectorAll('.slideshow__nav-item'); 40 | 41 | this.mesh = new THREE.Mesh(this.geometry, this.material); 42 | this.add(this.mesh); 43 | 44 | Gl.scene.add(this); 45 | 46 | this.loadTextures(); 47 | this.addEvents(); 48 | } 49 | 50 | loadTextures() { 51 | const manager = new THREE.LoadingManager(() => { 52 | // Set first texture as default 53 | this.material.uniforms.uCurrTex.value = this.textures[0]; 54 | }); 55 | const loader = new THREE.TextureLoader(manager); 56 | const imgs = [...this.el.querySelectorAll('img')]; 57 | 58 | imgs.forEach(img => { 59 | loader.load(img.src, texture => { 60 | texture.minFilter = THREE.LinearFilter; 61 | texture.generateMipmaps = false; 62 | 63 | this.material.uniforms.uImageSize.value = [img.naturalWidth, img.naturalHeight]; 64 | this.textures.push(texture); 65 | }) 66 | }); 67 | } 68 | 69 | switchTextures(index) { 70 | if(this.state.animating) return; 71 | 72 | this.state.animating = true; 73 | 74 | this.navItems[this.state.current].classList.remove('slideshow__nav-item--current'); 75 | this.navItems[index].classList.add('slideshow__nav-item--current'); 76 | this.state.current = index; 77 | 78 | this.material.uniforms.uNextTex.value = this.textures[index]; 79 | 80 | const tl = gsap.timeline({ 81 | onComplete: () => { 82 | this.state.animating = false; 83 | this.material.uniforms.uCurrTex.value = this.textures[index]; 84 | } 85 | }); 86 | 87 | tl 88 | .fromTo(this.material.uniforms.uProg, { 89 | value: 0 90 | }, { 91 | value: 1, 92 | duration: 2, 93 | ease: 'expo.inOut', 94 | }, 0); 95 | } 96 | 97 | addEvents() { 98 | this.navItems.forEach((el, i) => { 99 | el.addEventListener('click', () => { 100 | this.switchTextures(i); 101 | }); 102 | }); 103 | } 104 | 105 | updateTime(time) { 106 | this.material.uniforms.uTime.value = time; 107 | } 108 | 109 | resize() { 110 | super.resize(); 111 | if (!this.material) return; 112 | this.material.uniforms.uMeshSize.value.x = this.rect.width; 113 | this.material.uniforms.uMeshSize.value.y = this.rect.height; 114 | } 115 | } -------------------------------------------------------------------------------- /js/gl/glsl/fragment-01.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec2 vUv; 4 | varying float wave; 5 | 6 | uniform sampler2D uTexture; 7 | uniform float uTime; 8 | uniform float uProg; 9 | uniform float uIndex; 10 | 11 | void main() { 12 | vec2 uv = vUv; 13 | vec2 dUv = vec2(uv.x, uv.y); 14 | vec3 texture; 15 | 16 | if (uIndex < 3.) { 17 | float w = wave; 18 | float r = texture2D(uTexture, dUv + vec2(0., 0.) + uProg * w * 0.05).r; 19 | float g = texture2D(uTexture, dUv + vec2(0., 0.) + uProg * w * 0.0).g; 20 | float b = texture2D(uTexture, dUv + vec2(0., 0.) + uProg * w * -0.02).b; 21 | texture = vec3(r, g, b); 22 | } else if (uIndex < 6.) { 23 | float count = 10.; 24 | float smoothness = 0.5; 25 | float pr = smoothstep(-smoothness, 0., dUv.y - (1. - uProg) * (1. + smoothness)); 26 | float s = 1. - step(pr, fract(count * dUv.y)); 27 | texture = texture2D(uTexture, dUv * s).rgb; 28 | } else { 29 | dUv.y += wave * 0.05; 30 | float r = texture2D(uTexture, dUv + vec2(0., 0.)).r; 31 | float g = texture2D(uTexture, dUv + vec2(0., 0.)).g; 32 | float b = texture2D(uTexture, dUv + vec2(0., -0.02) * uProg).b; 33 | texture = vec3(r, g, b); 34 | } 35 | 36 | gl_FragColor = vec4(texture, 1.); 37 | } -------------------------------------------------------------------------------- /js/gl/glsl/fragment-02.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec2 vUv; 4 | 5 | uniform sampler2D uCurrTex; 6 | uniform sampler2D uNextTex; 7 | uniform sampler2D uDisp; 8 | uniform vec2 uMeshSize; 9 | uniform vec2 uImageSize; 10 | uniform float uTime; 11 | uniform float uProg; 12 | 13 | vec2 backgroundCoverUv(vec2 uv, vec2 canvasSize, vec2 textureSize){ 14 | vec2 ratio = vec2( 15 | min((canvasSize.x / canvasSize.y) / (textureSize.x / textureSize.y), 1.0), 16 | min((canvasSize.y / canvasSize.x) / (textureSize.y / textureSize.x), 1.0) 17 | ); 18 | 19 | vec2 uvWithRatio = uv * ratio; 20 | 21 | return vec2( 22 | uvWithRatio.x + (1.0 - ratio.x) * 0.5, 23 | uvWithRatio.y + (1.0 - ratio.y) * 0.5 24 | ); 25 | } 26 | 27 | void main() { 28 | vec2 uv = vUv; 29 | vec2 texUv = backgroundCoverUv(uv, uMeshSize, uImageSize); 30 | 31 | vec4 disp = texture2D(uDisp, uv); 32 | 33 | float wipe = step(1.0 - uv.x, uProg); 34 | float scale = 0.7 + 0.3 * uProg; 35 | 36 | vec4 currTex = texture2D(uCurrTex, texUv + vec2(disp.r * uProg, 0.)); 37 | vec4 nextTex = texture2D(uNextTex, texUv * scale + vec2(0.15) * (1. - uProg)); 38 | 39 | vec4 finalTex = mix(currTex, nextTex, wipe); 40 | 41 | gl_FragColor = finalTex; 42 | } -------------------------------------------------------------------------------- /js/gl/glsl/vertex-01.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | varying vec2 vUv; 3 | varying float wave; 4 | uniform float uTime; 5 | uniform float uProg; 6 | uniform float uIndex; 7 | 8 | #pragma glslify: noise = require(glsl-noise/simplex/3d) 9 | 10 | void main() { 11 | vec3 pos = position; 12 | 13 | if (uIndex < 3.) { 14 | pos.z += noise(vec3(pos.x * 4. + uTime, pos.y, 0.)) * uProg; 15 | wave = pos.z; 16 | pos.z *= 3.; 17 | } else if (uIndex < 6.) { 18 | float pr = smoothstep(0., 0.5 - sin(pos.y), uProg) * 5.; 19 | pos.z += pr; 20 | } else { 21 | pos.z += sin(pos.y * 5. + uTime) * 2. * uProg; 22 | wave = pos.z; 23 | } 24 | 25 | vUv = uv; 26 | 27 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.); 28 | } -------------------------------------------------------------------------------- /js/gl/glsl/vertex-02.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec2 vUv; 4 | 5 | uniform float uTime; 6 | 7 | void main() { 8 | vec3 pos = position; 9 | 10 | vUv = uv; 11 | 12 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.); 13 | } -------------------------------------------------------------------------------- /js/gl/index.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import gsap from 'gsap' 3 | import { Events } from '../events' 4 | 5 | export default new class { 6 | constructor() { 7 | this.scene = new THREE.Scene(); 8 | 9 | this.camera = new THREE.PerspectiveCamera( 10 | 45, 11 | window.innerWidth / window.innerHeight, 12 | 0.1, 13 | 100 14 | ); 15 | this.camera.position.z = 50; 16 | 17 | this.renderer = new THREE.WebGLRenderer({ 18 | alpha: true, 19 | }); 20 | this.renderer.setPixelRatio(gsap.utils.clamp(1.5, 1, window.devicePixelRatio)); 21 | this.renderer.setSize(window.innerWidth, window.innerHeight); 22 | this.renderer.setClearColor(0xF2F2F2, 0); 23 | 24 | this.clock = new THREE.Clock(); 25 | 26 | this.init(); 27 | } 28 | 29 | render() { 30 | this.renderer.render(this.scene, this.camera); 31 | } 32 | 33 | run = ({ current }) => { 34 | let elapsed = this.clock.getElapsedTime(); 35 | 36 | for (let i = 0; i < this.scene.children.length; i++) { 37 | const plane = this.scene.children[i]; 38 | plane.updatePosition(current); 39 | plane.updateTime(elapsed); 40 | } 41 | 42 | this.render(); 43 | } 44 | 45 | addEvents() { 46 | Events.on('tick', this.run); 47 | Events.on('resize', this.resize); 48 | } 49 | 50 | init() { 51 | this.addToDom(); 52 | this.addEvents(); 53 | } 54 | 55 | addToDom() { 56 | const canvas = this.renderer.domElement; 57 | canvas.classList.add('dom-gl'); 58 | document.body.appendChild(canvas); 59 | } 60 | 61 | resize = () => { 62 | this.renderer.setSize(window.innerWidth, window.innerHeight); 63 | this.camera.updateProjectionMatrix(); 64 | 65 | for (let i = 0; i < this.scene.children.length; i++) { 66 | const plane = this.scene.children[i]; 67 | plane.resize(); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | import '../css/base.css'; 2 | import Smooth from './components/Smooth'; 3 | import Gl from './gl'; 4 | import Plane from './gl/Plane'; 5 | import Slideshow from './gl/Slideshow'; 6 | import { preloadImages } from './utils'; 7 | 8 | preloadImages().then(() => { 9 | document.body.classList.remove('loading'); 10 | 11 | const elements = document.querySelectorAll('.js-plane'); 12 | elements.forEach((el, index) => new Plane().init(el, index)); 13 | const smooth = new Smooth(); 14 | }); -------------------------------------------------------------------------------- /js/index2.js: -------------------------------------------------------------------------------- 1 | import '../css/base.css'; 2 | import Smooth from './components/Smooth'; 3 | import Gl from './gl'; 4 | import Plane from './gl/Plane'; 5 | import Slideshow from './gl/Slideshow'; 6 | import { preloadImages } from './utils'; 7 | 8 | preloadImages().then(() => { 9 | document.body.classList.remove('loading'); 10 | 11 | const element = document.querySelector('.js-slideshow'); 12 | const slideshow = new Slideshow().init(element); 13 | const smooth = new Smooth(); 14 | }); -------------------------------------------------------------------------------- /js/utils/index.js: -------------------------------------------------------------------------------- 1 | export { preloadImages } from './preload'; 2 | export { lerp } from './lerp'; -------------------------------------------------------------------------------- /js/utils/lerp.js: -------------------------------------------------------------------------------- 1 | export function lerp(a, b, n) { 2 | return a * (1 - n) + b * n; 3 | } -------------------------------------------------------------------------------- /js/utils/preload.js: -------------------------------------------------------------------------------- 1 | const imagesLoaded = require('imagesloaded'); 2 | 3 | // Preload images 4 | export function preloadImages() { 5 | return new Promise((resolve, reject) => { 6 | imagesLoaded(document.querySelectorAll('.item__img, .slideshow__img'), {background: true}, resolve); 7 | }); 8 | }; 9 | 10 | /* 11 | export function preload() { 12 | const paths = [...document.querySelectorAll('.item__img')].map(image => image.src); 13 | return Promise.all(paths.map(checkImage)); 14 | } 15 | 16 | export const checkImage = path => new Promise(resolve => { 17 | const img = new Image(); 18 | img.onload = () => resolve({ path, status: 'ok' }); 19 | img.onerror = () => resolve({ path, status: 'error' }); 20 | img.src = path; 21 | }); 22 | */ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codrops-shader-hover", 3 | "version": "1.0.0", 4 | "description": "*How to use this template:*", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rm -rf dist", 8 | "dev": "webpack --mode=development", 9 | "prod": "webpack --mode=production", 10 | "watch": "webpack-dev-server" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+ssh://git@bitbucket.org/marioecg/codrops-shader-hover.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "homepage": "https://bitbucket.org/marioecg/codrops-shader-hover#readme", 20 | "dependencies": { 21 | "file-loader": "^5.0.2", 22 | "glsl-noise": "0.0.0", 23 | "glslify": "^7.0.0", 24 | "glslify-loader": "^2.0.0", 25 | "gsap": "^3.0.5", 26 | "imagesloaded": "^4.1.4", 27 | "raw-loader": "^4.0.0", 28 | "three": "^0.112.1", 29 | "tiny-emitter": "^2.1.0" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.8.3", 33 | "@babel/plugin-proposal-class-properties": "^7.8.3", 34 | "@babel/preset-env": "^7.8.3", 35 | "babel-loader": "^8.0.6", 36 | "css-loader": "^3.4.2", 37 | "html-loader": "^0.5.5", 38 | "html-webpack-plugin": "^3.2.0", 39 | "mini-css-extract-plugin": "^0.9.0", 40 | "node-sass": "^4.13.1", 41 | "sass-loader": "^8.0.2", 42 | "webpack": "^4.41.5", 43 | "webpack-cli": "^3.3.10", 44 | "webpack-dev-server": "^3.10.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | 5 | module.exports = { 6 | entry: { 7 | 'index': './js/index.js', 8 | 'index2': './js/index2.js' 9 | }, 10 | 11 | output: { 12 | path: path.resolve(__dirname, 'dist'), 13 | filename: 'scripts/[name].js' 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | use: 'babel-loader', 21 | exclude: /node_modules/ 22 | }, 23 | 24 | { 25 | test: /\.css$/, 26 | use: [MiniCssExtractPlugin.loader, "css-loader"] 27 | }, 28 | 29 | { 30 | test: /\.html$/, 31 | loader: 'html-loader' 32 | }, 33 | 34 | { 35 | test: /\.(png|jpe?g|gif|svg)$/i, 36 | loader: 'file-loader', 37 | options: { 38 | name: '[name].[ext]', 39 | outputPath: 'images/', 40 | esModule: false 41 | } 42 | }, 43 | 44 | { 45 | test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/, 46 | loader: 'file-loader', 47 | options: { 48 | name: '[name].[ext]', 49 | outputPath: 'fonts/' 50 | } 51 | }, 52 | 53 | { 54 | test: /\.glsl$/, 55 | use: [ 56 | 'raw-loader', 57 | 'glslify-loader' 58 | ] 59 | } 60 | ], 61 | }, 62 | 63 | devServer: { 64 | contentBase: path.join(__dirname, "dist"), 65 | compress: true, 66 | port: 8000, 67 | stats: 'errors-only' 68 | }, 69 | 70 | plugins: [ 71 | new HtmlWebpackPlugin({ 72 | filename: 'index.html', 73 | template: 'index.html', 74 | inject: true, 75 | chunks: ['index'], 76 | hash: true, 77 | }), 78 | 79 | new HtmlWebpackPlugin({ 80 | filename: 'index2.html', 81 | template: 'index2.html', 82 | inject: true, 83 | chunks: ['index2'], 84 | hash: true, 85 | }), 86 | 87 | new MiniCssExtractPlugin({ 88 | filename: "styles/[name].css", 89 | chunkFilename: "[id].css" 90 | }), 91 | ] 92 | }; --------------------------------------------------------------------------------