├── .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 | 
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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
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 |
50 | Seoul
51 | Bangkok
52 | Taipei
53 | Tokyo
54 |
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 | };
--------------------------------------------------------------------------------