├── .gitignore ├── README.md ├── css └── base.css ├── img ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── dos1.jpg ├── dos2.jpg ├── intro1.jpg ├── intro2.jpg ├── last.jpg ├── tres1.jpg └── tres2.jpg ├── index.html ├── js ├── app.js ├── imagesLoaded.js ├── rolls.js └── shader │ ├── fragment.glsl │ └── vertex.glsl ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | dist/ 3 | node_modules/ 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Unroll Effects with Three.js 2 | 3 | Some unrolling image effects using WebGL. By Yuriy Artyukh 4 | 5 | ![Image Title](https://tympanus.net/codrops/wp-content/uploads/2020/01/Unrolling_featured-1.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=46712) 8 | 9 | [Demo](https://tympanus.net/Development/UnrollingImages/) 10 | 11 | ## Instructions 12 | 13 | Run `parcel index.html` if you have [Parcel](https://parceljs.org/) installed, otherwise `npx parcel index.html` 14 | 15 | ## Credits 16 | 17 | - [three.js](https://threejs.org/) 18 | - Images from [Unsplash.com](https://unsplash.com/) 19 | - [imagesLoaded](https://imagesloaded.desandro.com/) by Dave DeSandro 20 | 21 | ## License 22 | This resource can be used freely if integrated or build upon in personal or commercial projects such as websites, web apps and web templates intended for sale. It is not allowed to take the resource "as-is" and sell it, redistribute, re-publish it, or sell "pluginized" versions of it. Free plugins built using this resource should have a visible mention and link to the original work. Always consider the licenses of all included libraries, scripts and images used. 23 | 24 | ## Misc 25 | 26 | Follow Yuriy: [Twitter](https://twitter.com/akella), [Instagram](http://instagram.com/akella_), [Facebook](https://facebook.com/akella), [GitHub](https://github.com/akella) 27 | 28 | 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/) 29 | 30 | [© Codrops 2020](http://www.codrops.com) 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | :root { 10 | font-size: 18px; 11 | } 12 | 13 | body { 14 | --color-text: #000; 15 | --color-bg: #dde8eb; 16 | --color-link: #4b4b4b; 17 | --color-link-hover: #000; 18 | color: var(--color-text); 19 | background-color: var(--color-bg); 20 | font-family: moderno-fb, serif; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | /* Page Loader */ 26 | .js .loading::before, 27 | .js .loading::after { 28 | content: ''; 29 | position: fixed; 30 | z-index: 1000; 31 | } 32 | 33 | .js .loading::before { 34 | top: 0; 35 | left: 0; 36 | width: 100%; 37 | height: 100%; 38 | background: var(--color-bg); 39 | } 40 | 41 | .js .loading::after { 42 | top: 50%; 43 | left: 50%; 44 | width: 60px; 45 | height: 60px; 46 | margin: -30px 0 0 -30px; 47 | border-radius: 50%; 48 | opacity: 0.4; 49 | background: var(--color-link); 50 | animation: loaderAnim 0.7s linear infinite alternate forwards; 51 | 52 | } 53 | 54 | @keyframes loaderAnim { 55 | to { 56 | opacity: 1; 57 | transform: scale3d(0.5,0.5,1); 58 | } 59 | } 60 | 61 | a { 62 | text-decoration: none; 63 | color: var(--color-link); 64 | outline: none; 65 | } 66 | 67 | a:hover, 68 | a:focus { 69 | color: var(--color-link-hover); 70 | outline: none; 71 | text-decoration: underline; 72 | } 73 | 74 | .message { 75 | background: var(--color-text); 76 | color: var(--color-bg); 77 | padding: 1rem; 78 | text-align: center; 79 | } 80 | 81 | .frame { 82 | padding: 3rem 5vw; 83 | text-align: center; 84 | position: relative; 85 | z-index: 1000; 86 | } 87 | 88 | .frame__title { 89 | font-size: 1rem; 90 | margin: 0 0 1rem; 91 | font-weight: normal; 92 | } 93 | 94 | .frame__links { 95 | display: inline; 96 | } 97 | 98 | .frame__links a:not(:last-child) { 99 | margin-right: 1rem; 100 | } 101 | 102 | .frame__demos a:not(:last-child) { 103 | margin-right: 2rem; 104 | } 105 | 106 | .frame__demos { 107 | margin: 1rem 0; 108 | } 109 | 110 | .frame__demo { 111 | white-space: nowrap; 112 | } 113 | 114 | .frame__demo--current, 115 | .frame__demo--current:hover { 116 | color: var(--color-text); 117 | } 118 | 119 | .title { 120 | font-family: paralucent, sans-serif; 121 | font-weight: 700; 122 | } 123 | 124 | .js-image { 125 | max-width: 100%; 126 | display: block; 127 | } 128 | 129 | .loaded .js-image { 130 | opacity: 0; 131 | } 132 | 133 | canvas { 134 | position: fixed; 135 | top: 0; 136 | left: 0; 137 | z-index: 100; 138 | pointer-events: none; 139 | } 140 | 141 | .header { 142 | padding: 3rem; 143 | } 144 | 145 | .header__title { 146 | font-family: paralucent, sans-serif; 147 | font-size: 2rem; 148 | } 149 | 150 | .info { 151 | margin: 0.5rem 0; 152 | display: flex; 153 | } 154 | 155 | .info__link { 156 | margin-right: 1.25rem; 157 | } 158 | 159 | .section { 160 | padding: 20vh 0; 161 | margin: 0 100px; 162 | min-height: 100vh; 163 | } 164 | 165 | /*intro*/ 166 | .intro { 167 | text-align: right; 168 | font-size: 18px; 169 | padding-top: 100px; 170 | } 171 | 172 | .intro__title { 173 | padding-top: 1em; 174 | position: absolute; 175 | bottom: -2em; 176 | right: 0; 177 | } 178 | 179 | .intro__wrap { 180 | position: relative; 181 | } 182 | 183 | .intro__background-image { 184 | margin-right: 20%; 185 | position: relative; 186 | } 187 | 188 | .intro__background-image img { 189 | width: 100%; 190 | display: block; 191 | } 192 | 193 | .intro__foreground-image { 194 | position: absolute; 195 | right: 0; 196 | width: 60%; 197 | top: 50%; 198 | transform: translate(0, -50%); 199 | } 200 | 201 | /*dos*/ 202 | .dos { 203 | margin: 100px 50px; 204 | position: relative; 205 | } 206 | 207 | .dos img { 208 | max-width: 100%; 209 | display: block; 210 | width: 100%; 211 | } 212 | 213 | .dos__foreground { 214 | width: 40%; 215 | position: relative; 216 | padding-left: 6em; 217 | } 218 | 219 | .dos__background { 220 | text-align: right; 221 | margin-left: 30%; 222 | position: relative; 223 | margin-top: -14vh; 224 | } 225 | 226 | .dos__text { 227 | position: absolute; 228 | bottom: 0; 229 | width: 40%; 230 | right: calc(100% + 1em); 231 | } 232 | 233 | .meta { 234 | font-family: paralucent, sans-serif; 235 | font-weight: 700; 236 | } 237 | 238 | .dos .meta { 239 | position: absolute; 240 | left: 0; 241 | /*right: 0;*/ 242 | /*left: auto;*/ 243 | top: 100%; 244 | transform: rotate(-90deg); 245 | transform-origin: 0 0; 246 | 247 | } 248 | 249 | .meta__subtitle { 250 | font-size: 1.2em; 251 | white-space: nowrap; 252 | } 253 | 254 | .meta__title { 255 | font-size: 2.5em; 256 | white-space: nowrap; 257 | } 258 | 259 | /*tres*/ 260 | .tres { 261 | position: relative; 262 | } 263 | 264 | .tres__background { 265 | position: relative; 266 | width: 55%; 267 | margin-top: -40%; 268 | z-index: 1; 269 | } 270 | 271 | .tres__background img { 272 | width: 100%; 273 | display: block; 274 | } 275 | 276 | .tres__text { 277 | position: absolute; 278 | top: 100%; 279 | width: 20em; 280 | left: 0; 281 | padding-top: 1em; 282 | } 283 | 284 | .tres .meta { 285 | position: absolute; 286 | right: -1em; 287 | top: 100%; 288 | width: 8em; 289 | /*background: green;*/ 290 | transform-origin: 0 0; 291 | transform: rotate(-90deg); 292 | text-align: left; 293 | } 294 | 295 | .tres__foreground { 296 | padding-right: 8em; 297 | text-align: right; 298 | display: inline-block; 299 | width: 100%; 300 | position: relative; 301 | z-index: 2; 302 | } 303 | 304 | .tres__foreground-image { 305 | float: right; 306 | width: calc(70% - 8em); 307 | } 308 | 309 | .tres__foreground img { 310 | width: 100%; 311 | display: block; 312 | } 313 | 314 | /*cuatro*/ 315 | .cuatro ul { 316 | max-width: 800px; 317 | margin: 100px auto; 318 | list-style: none; 319 | overflow: hidden; 320 | } 321 | 322 | .cuatro li { 323 | width: 50%; 324 | text-align: center; 325 | float: left; 326 | margin-bottom: 2em; 327 | } 328 | 329 | /*last*/ 330 | .last { 331 | margin: 100px auto 0 auto; 332 | text-align: center; 333 | padding-bottom: 30vh; 334 | } 335 | 336 | .last__image { 337 | max-width: 480px; 338 | margin: 0 auto; 339 | } 340 | 341 | .last img { 342 | display: block; 343 | max-width: 100%; 344 | } 345 | 346 | .button { 347 | display: inline-block; 348 | background: none; 349 | border-radius: 0; 350 | border: 1px solid #94a6ab; 351 | cursor: pointer; 352 | padding: 0.7em 1.3em; 353 | font-size: 1em; 354 | margin: 0 0.5em; 355 | } 356 | 357 | .button:focus { 358 | outline: none; 359 | } 360 | 361 | .buttons { 362 | margin-bottom: 100px; 363 | } 364 | 365 | 366 | @media (min-width: 1480px) { 367 | .section { 368 | margin-left: auto; 369 | margin-right: auto; 370 | max-width: 1200px; 371 | } 372 | } 373 | 374 | @media screen and (min-width: 53em) { 375 | .message { 376 | display: none; 377 | } 378 | .frame { 379 | position: fixed; 380 | text-align: left; 381 | z-index: 100; 382 | top: 0; 383 | left: 0; 384 | display: grid; 385 | align-content: space-between; 386 | width: 100%; 387 | max-width: none; 388 | height: 100vh; 389 | padding: 2rem; 390 | grid-gap: 3rem; 391 | pointer-events: none; 392 | -webkit-touch-callout: none; 393 | -webkit-user-select: none; 394 | -moz-user-select: none; 395 | -ms-user-select: none; 396 | user-select: none; 397 | grid-template-columns: min-content min-content auto 300px; 398 | grid-template-rows: auto auto auto; 399 | grid-template-areas: 'title links info ...' 400 | '... ... ... ...' 401 | '... ... demos demos'; 402 | } 403 | .frame__title-wrap { 404 | grid-area: title; 405 | display: flex; 406 | } 407 | .frame__title { 408 | margin: 0; 409 | white-space: nowrap; 410 | } 411 | .frame__info { 412 | grid-area: info; 413 | } 414 | .frame__demos { 415 | margin: 0; 416 | grid-area: demos; 417 | justify-self: end; 418 | } 419 | .frame__links { 420 | grid-area: links; 421 | padding: 0; 422 | white-space: nowrap; 423 | margin: 0 1rem; 424 | } 425 | .frame a { 426 | pointer-events: auto; 427 | } 428 | } 429 | 430 | @media (max-width: 750px) { 431 | 432 | .section, 433 | .last { 434 | margin: 0 50px; 435 | } 436 | 437 | .last { 438 | margin: 0 auto; 439 | } 440 | 441 | .intro__background-image { 442 | position: static; 443 | } 444 | 445 | .intro__background-image img { 446 | display: none; 447 | } 448 | 449 | .intro__foreground-image { 450 | position: static; 451 | margin: 0; 452 | padding: 0; 453 | transform: none; 454 | width: auto; 455 | } 456 | 457 | .intro__foreground-image img { 458 | display: block; 459 | width: 100%; 460 | } 461 | 462 | .dos { 463 | position: relative; 464 | } 465 | 466 | .dos__background { 467 | position: static; 468 | width: 100%; 469 | padding: 0; 470 | margin: 0; 471 | } 472 | 473 | .dos__background-image { 474 | padding-left: 3em; 475 | min-height: 200px; 476 | } 477 | 478 | .dos__foreground { 479 | margin: 0; 480 | position: absolute; 481 | left: 0; 482 | } 483 | 484 | .dos__foreground-image { 485 | display: none; 486 | } 487 | 488 | .dos__foreground { 489 | width: 0; 490 | } 491 | 492 | .dos__text { 493 | position: static; 494 | width: auto; 495 | text-align: right; 496 | margin: 1em 0; 497 | } 498 | 499 | .dos .meta { 500 | transform: rotate(-90deg); 501 | width: 8em; 502 | left: -8em; 503 | transform-origin: 100% 0; 504 | top: 4em; 505 | font-size: 0.5rem; 506 | } 507 | 508 | .tres .meta { 509 | font-size: 0.5rem; 510 | } 511 | 512 | .tres__foreground-image { 513 | width: 100%; 514 | float: none; 515 | } 516 | 517 | .tres__background { 518 | margin: 0; 519 | width: auto; 520 | } 521 | 522 | .tres__background-image { 523 | display: none; 524 | } 525 | 526 | .tres__text { 527 | position: static; 528 | width: auto; 529 | margin: 0; 530 | } 531 | 532 | .cuatro li { 533 | float: none; 534 | width: auto; 535 | } 536 | 537 | .cuatro li img { 538 | width: 100%; 539 | } 540 | 541 | .last__image { 542 | margin: 0 20px; 543 | } 544 | 545 | .button { 546 | font-size: 0.8em; 547 | } 548 | } 549 | 550 | 551 | -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/1.jpg -------------------------------------------------------------------------------- /img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/2.jpg -------------------------------------------------------------------------------- /img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/3.jpg -------------------------------------------------------------------------------- /img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/4.jpg -------------------------------------------------------------------------------- /img/dos1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/dos1.jpg -------------------------------------------------------------------------------- /img/dos2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/dos2.jpg -------------------------------------------------------------------------------- /img/intro1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/intro1.jpg -------------------------------------------------------------------------------- /img/intro2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/intro2.jpg -------------------------------------------------------------------------------- /img/last.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/last.jpg -------------------------------------------------------------------------------- /img/tres1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/tres1.jpg -------------------------------------------------------------------------------- /img/tres2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akella/UnrollingImages/28000ef2de029c59d4fd863a02958b4a72607cd9/img/tres2.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Image Unroll Effects with Three.js | Codrops 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |

Image Unroll Effects with Three.js

26 |
27 | Article 28 | Previous demo 29 | GitHub 30 |
31 |
32 | 33 |
34 |
35 |
36 | Some image 37 |
Watson Building, Dubai, 2020
38 |
39 |
40 | Some image 41 |
42 |
43 |
44 | 45 | 46 |
47 |
48 |
49 |

– architecture

50 |

Megalith

51 |
52 |
53 | Some image 54 |
55 |
56 |
57 |
58 | Some image 59 |
60 |
61 |

Carlon Arc, Chicago, 2019

62 |
63 |
64 |
65 | 66 | 67 |
68 |
69 |
70 | Some image 71 |
72 |
73 |

– innovator

74 |

Neon Plex

75 |
76 |
77 |
78 |
79 | Some image 80 |
81 |
82 |

Plex Construct, Sydney, 2018

83 |
84 |
85 |
86 | 87 | 88 |
89 |
    90 |
  • Some image
  • 91 |
  • Some image
  • 92 |
  • Some image
  • 93 |
  • Some image
  • 94 |
95 |
96 | 97 | 98 |
99 |
100 | 101 | 102 | 103 |
104 |
105 | Some image 106 |
107 |
108 | 109 | 110 | 111 |
112 |
113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | import FontFaceObserver from "fontfaceobserver"; 2 | import imagesLoaded from "./imagesLoaded"; 3 | import Scene from "./rolls"; 4 | import gsap from "gsap"; 5 | 6 | const scene = new Scene("container"); 7 | 8 | // helper functions 9 | const MathUtils = { 10 | // map number x from range [a, b] to [c, d] 11 | map: (x, a, b, c, d) => ((x - a) * (d - c)) / (b - a) + c, 12 | // linear interpolation 13 | lerp: (a, b, n) => (1 - n) * a + n * b 14 | }; 15 | 16 | // body element 17 | const body = document.body; 18 | let IMAGES; 19 | 20 | // calculate the viewport size 21 | let winsize; 22 | const calcWinsize = () => 23 | (winsize = { width: window.innerWidth, height: window.innerHeight }); 24 | calcWinsize(); 25 | // and recalculate on resize 26 | window.addEventListener("resize", calcWinsize); 27 | 28 | window.onbeforeunload = function() { 29 | window.scrollTo(0, 0); 30 | }; 31 | 32 | // scroll position and update function 33 | let docScroll; 34 | const getPageYScroll = () => 35 | (docScroll = window.pageYOffset || document.documentElement.scrollTop); 36 | window.addEventListener("scroll", getPageYScroll); 37 | 38 | // Item 39 | class Item { 40 | constructor(el, scroll) { 41 | // the .item element 42 | this.scroll = scroll; 43 | this.DOM = { el: el.img }; 44 | this.currentScroll = docScroll; 45 | this.animated = false; 46 | this.isBeingAnimatedNow = false; 47 | this.shouldRollBack = false; 48 | this.shouldUnRoll = false; 49 | this.positions = []; 50 | 51 | // set the initial values 52 | this.getSize(); 53 | this.mesh = scene.createMesh({ 54 | width: this.width, 55 | height: this.height, 56 | src: this.src, 57 | image: this.DOM.el, 58 | iWidth: this.DOM.el.width, 59 | iHeight: this.DOM.el.height 60 | }); 61 | scene.scene.add(this.mesh); 62 | // use the IntersectionObserver API to check when the element is inside the viewport 63 | // only then the element translation will be updated 64 | this.intersectionRatio; 65 | let options = { 66 | root: null, 67 | rootMargin: "0px", 68 | threshold: [0, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] 69 | }; 70 | this.observer = new IntersectionObserver(entries => { 71 | entries.forEach(entry => { 72 | this.positions.push(entry.boundingClientRect.y); 73 | let compareArray = this.positions.slice( 74 | this.positions.length - 2, 75 | this.positions.length 76 | ); 77 | let down = compareArray[0] > compareArray[1] ? true : false; 78 | 79 | this.isVisible = entry.intersectionRatio > 0.0; 80 | 81 | this.shouldRollBack = false; 82 | this.shouldUnRoll = false; 83 | if ( 84 | entry.intersectionRatio < 0.5 && 85 | entry.boundingClientRect.y > 0 && 86 | this.isVisible && 87 | !down 88 | ) { 89 | this.shouldRollBack = true; 90 | } 91 | 92 | if ( 93 | entry.intersectionRatio > 0.5 && 94 | entry.boundingClientRect.y > 0 && 95 | this.isVisible 96 | ) { 97 | this.shouldUnRoll = true; 98 | } 99 | 100 | this.mesh.visible = this.isVisible; 101 | }); 102 | }, options); 103 | this.observer.observe(this.DOM.el); 104 | // init/bind events 105 | window.addEventListener("resize", () => this.resize()); 106 | this.render(0); 107 | } 108 | 109 | getSize() { 110 | // get all the sizes here, bounds and all 111 | const bounds = this.DOM.el.getBoundingClientRect(); 112 | const fromTop = bounds.top; 113 | const windowHeight = window.innerHeight; 114 | const withoutHeight = fromTop - windowHeight; 115 | const withHeight = fromTop + bounds.height; 116 | this.insideTop = withoutHeight - docScroll; 117 | this.insideRealTop = fromTop + docScroll; 118 | this.insideBottom = withHeight - docScroll + 50; 119 | this.width = bounds.width; 120 | this.height = bounds.height; 121 | this.left = bounds.left; 122 | } 123 | resize() { 124 | // on resize rest sizes and update the translation value 125 | this.getSize(); 126 | this.mesh.scale.set(this.width, this.height, 200); 127 | this.render(this.scroll.renderedStyles.translationY.current); 128 | this.scroll.shouldRender = true; 129 | } 130 | 131 | render(currentScroll) { 132 | this.currentScroll = currentScroll; 133 | this.mesh.position.y = 134 | currentScroll + winsize.height / 2 - this.insideRealTop - this.height / 2; 135 | this.mesh.position.x = 0 - winsize.width / 2 + this.left + this.width / 2; 136 | if (this.shouldUnRoll && !this.animated) { 137 | this.animated = true; 138 | this.isBeingAnimatedNow = true; 139 | this.shouldUnRoll = false; 140 | gsap.to(this.mesh.material.uniforms.progress, { 141 | duration: 1.7, 142 | value: 1, 143 | ease: "power2.out", 144 | onUpdate: () => { 145 | this.scroll.shouldRender = true; 146 | }, 147 | onComplete: () => { 148 | this.isBeingAnimatedNow = false; 149 | } 150 | }); 151 | } 152 | 153 | if (this.shouldRollBack && this.animated) { 154 | this.animated = false; 155 | this.isBeingAnimatedNow = true; 156 | this.shouldRollBack = false; 157 | gsap.to(this.mesh.material.uniforms.progress, { 158 | duration: 1.7, 159 | value: 0, 160 | ease: "power2.inOut", 161 | onUpdate: () => { 162 | this.scroll.shouldRender = true; 163 | }, 164 | onComplete: () => { 165 | this.isBeingAnimatedNow = false; 166 | } 167 | }); 168 | } 169 | } 170 | } 171 | 172 | // SmoothScroll 173 | class SmoothScroll { 174 | constructor() { 175 | this.shouldRender = false; 176 | // the
element 177 | this.DOM = { main: document.querySelector("main") }; 178 | // the scrollable element 179 | // we translate this element when scrolling (y-axis) 180 | this.DOM.scrollable = this.DOM.main.querySelector("div[data-scroll]"); 181 | // the items on the page 182 | this.items = []; 183 | 184 | this.createItems(); 185 | 186 | // here we define which property will change as we scroll the page 187 | // in this case we will be translating on the y-axis 188 | // we interpolate between the previous and current value to achieve the smooth scrolling effect 189 | this.renderedStyles = { 190 | translationY: { 191 | // interpolated value 192 | previous: 0, 193 | // current value 194 | current: 0, 195 | // amount to interpolate 196 | ease: 0.1, 197 | // current value setter 198 | // in this case the value of the translation will be the same like the document scroll 199 | setValue: () => docScroll 200 | } 201 | }; 202 | // set the body's height 203 | this.setSize(); 204 | // set the initial values 205 | this.update(); 206 | // the
element's style needs to be modified 207 | this.style(); 208 | // init/bind events 209 | this.initEvents(); 210 | this.buttons(); 211 | // start the render loop 212 | requestAnimationFrame(() => this.render()); 213 | } 214 | 215 | buttons() { 216 | let that = this; 217 | let buttons = [...document.querySelectorAll(".js-change")]; 218 | 219 | buttons.forEach(b => { 220 | let angle = b.getAttribute("data-angle"); 221 | let radians = (angle * Math.PI) / 180; 222 | 223 | b.addEventListener("click", () => { 224 | scene.settings.angle = radians; 225 | 226 | scene.scene.children.forEach(mesh => { 227 | gsap.fromTo( 228 | mesh.material.uniforms.progress, 229 | { 230 | value: 0 231 | }, 232 | { 233 | duration: 2, 234 | value: 1, 235 | ease: "power2.out", 236 | onUpdate: () => { 237 | this.shouldRender = true; 238 | } 239 | } 240 | ); 241 | }); 242 | }); 243 | }); 244 | } 245 | 246 | update() { 247 | // sets the initial value (no interpolation) - translate the scroll value 248 | for (const key in this.renderedStyles) { 249 | this.renderedStyles[key].current = this.renderedStyles[ 250 | key 251 | ].previous = this.renderedStyles[key].setValue(); 252 | } 253 | // translate the scrollable element 254 | this.setPosition(); 255 | this.shouldRender = true; 256 | } 257 | setPosition() { 258 | // translates the scrollable element 259 | if ( 260 | Math.round(this.renderedStyles.translationY.previous) !== 261 | Math.round(this.renderedStyles.translationY.current) || 262 | this.renderedStyles.translationY.previous < 10 263 | ) { 264 | this.shouldRender = true; 265 | this.DOM.scrollable.style.transform = `translate3d(0,${-1 * 266 | this.renderedStyles.translationY.previous}px,0)`; 267 | // console.log(this.items); 268 | for (const item of this.items) { 269 | // if the item is inside the viewport call it's render function 270 | // this will update the item's inner image translation, based on the document scroll value and the item's position on the viewport 271 | if (item.isVisible || item.isBeingAnimatedNow) { 272 | item.render(this.renderedStyles.translationY.previous); 273 | } 274 | } 275 | } 276 | 277 | if (this.shouldRender) { 278 | this.shouldRender = false; 279 | scene.render(); 280 | } 281 | } 282 | setSize() { 283 | // set the heigh of the body in order to keep the scrollbar on the page 284 | // console.log(this.DOM.scrollable.scrollHeight, 'HEIGHT'); 285 | body.style.height = `${this.DOM.scrollable.scrollHeight}px`; 286 | } 287 | 288 | createItems() { 289 | IMAGES.forEach(image => { 290 | if (image.img.classList.contains("js-image")) { 291 | this.items.push(new Item(image, this)); 292 | } 293 | }); 294 | } 295 | 296 | style() { 297 | // the
needs to "stick" to the screen and not scroll 298 | // for that we set it to position fixed and overflow hidden 299 | this.DOM.main.style.position = "fixed"; 300 | this.DOM.main.style.width = this.DOM.main.style.height = "100%"; 301 | this.DOM.main.style.top = this.DOM.main.style.left = 0; 302 | this.DOM.main.style.overflow = "hidden"; 303 | } 304 | initEvents() { 305 | // on resize reset the body's height 306 | window.addEventListener("resize", () => this.setSize()); 307 | } 308 | render() { 309 | // update the current and interpolated values 310 | for (const key in this.renderedStyles) { 311 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 312 | this.renderedStyles[key].previous = MathUtils.lerp( 313 | this.renderedStyles[key].previous, 314 | this.renderedStyles[key].current, 315 | this.renderedStyles[key].ease 316 | ); 317 | } 318 | // and translate the scrollable element 319 | this.setPosition(); 320 | 321 | // loop.. 322 | requestAnimationFrame(() => this.render()); 323 | } 324 | } 325 | 326 | /***********************************/ 327 | /********** Preload stuff **********/ 328 | 329 | const fontParalucent = new Promise(resolve => { 330 | new FontFaceObserver("paralucent").load().then(() => { 331 | resolve(); 332 | }); 333 | }); 334 | const fontStarling = new Promise(resolve => { 335 | new FontFaceObserver("starling").load().then(() => { 336 | resolve(); 337 | }); 338 | }); 339 | 340 | // Preload images 341 | const preloadImages = new Promise((resolve, reject) => { 342 | imagesLoaded(document.querySelectorAll("img"), { background: true }, resolve); 343 | }); 344 | 345 | preloadImages.then(images => { 346 | IMAGES = images.images; 347 | }); 348 | 349 | const preloadEverything = [fontStarling, fontParalucent, preloadImages]; 350 | 351 | // And then.. 352 | Promise.all(preloadEverything).then(() => { 353 | // Remove the loader 354 | document.body.classList.remove("loading"); 355 | document.body.classList.add("loaded"); 356 | // Get the scroll position 357 | getPageYScroll(); 358 | // Initialize the Smooth Scrolling 359 | new SmoothScroll(); 360 | }); 361 | -------------------------------------------------------------------------------- /js/imagesLoaded.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * imagesLoaded PACKAGED v4.1.1 3 | * JavaScript is all like "You images are done yet or what?" 4 | * MIT License 5 | */ 6 | 7 | !function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=0,o=i[n];e=e||[];for(var r=this._onceEvents&&this._onceEvents[t];o;){var s=r&&r[o];s&&(this.off(t,o),delete r[o]),o.apply(this,e),n+=s?0:1,o=i[n]}return this}},t}),function(t,e){"use strict";"function"==typeof define&&define.amd?define(["ev-emitter/ev-emitter"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter")):t.imagesLoaded=e(t,t.EvEmitter)}(window,function(t,e){function i(t,e){for(var i in e)t[i]=e[i];return t}function n(t){var e=[];if(Array.isArray(t))e=t;else if("number"==typeof t.length)for(var i=0;i imageAspect) { 112 | a1 = (o.width / o.height) * imageAspect; 113 | a2 = 1; 114 | } else { 115 | a1 = 1; 116 | a2 = o.height / o.width / imageAspect; 117 | } 118 | texture.minFilter = THREE.LinearFilter; 119 | material.uniforms.resolution.value.x = o.width; 120 | material.uniforms.resolution.value.y = o.height; 121 | material.uniforms.resolution.value.z = a1; 122 | material.uniforms.resolution.value.w = a2; 123 | material.uniforms.progress.value = 0; 124 | material.uniforms.angle.value = 0.3; 125 | 126 | material.uniforms.texture1.value = texture; 127 | material.uniforms.texture1.value.needsUpdate = true; 128 | 129 | let mesh = new THREE.Mesh(this.geometry, material); 130 | 131 | mesh.scale.set(o.width, o.height, o.width / 2); 132 | 133 | return mesh; 134 | } 135 | 136 | stop() { 137 | this.paused = true; 138 | } 139 | 140 | play() { 141 | this.paused = false; 142 | this.render(); 143 | } 144 | 145 | render() { 146 | this.scene.children.forEach(m => { 147 | if (m.material.uniforms) { 148 | m.material.uniforms.angle.value = this.settings.angle; 149 | } 150 | }); 151 | this.renderer.render(this.scene, this.camera); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /js/shader/fragment.glsl: -------------------------------------------------------------------------------- 1 | uniform float time; 2 | uniform float progress; 3 | uniform sampler2D texture1; 4 | uniform sampler2D texture2; 5 | uniform vec4 resolution; 6 | 7 | varying vec2 vUv; 8 | varying float vFrontShadow; 9 | // varying float vBackShadow; 10 | // varying float vProgress; 11 | 12 | 13 | 14 | 15 | void main() { 16 | vec2 newUV = (vUv - vec2(0.5))*resolution.zw + vec2(0.5); 17 | 18 | gl_FragColor = texture2D(texture1,newUV); 19 | gl_FragColor.rgb *=vFrontShadow; 20 | gl_FragColor.a = clamp(progress*5.,0.,1.); 21 | 22 | } -------------------------------------------------------------------------------- /js/shader/vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform float time; 2 | uniform float angle; 3 | uniform float progress; 4 | uniform vec4 resolution; 5 | varying vec2 vUv; 6 | varying float vFrontShadow; 7 | // varying float vBackShadow; 8 | // varying float vProgress; 9 | 10 | uniform sampler2D texture1; 11 | uniform sampler2D texture2; 12 | uniform vec2 pixels; 13 | 14 | const float pi = 3.1415925; 15 | 16 | mat4 rotationMatrix(vec3 axis, float angle) { 17 | axis = normalize(axis); 18 | float s = sin(angle); 19 | float c = cos(angle); 20 | float oc = 1.0 - c; 21 | 22 | return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, 23 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, 24 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 25 | 0.0, 0.0, 0.0, 1.0); 26 | } 27 | vec3 rotate(vec3 v, vec3 axis, float angle) { 28 | mat4 m = rotationMatrix(axis, angle); 29 | return (m * vec4(v, 1.0)).xyz; 30 | } 31 | 32 | 33 | 34 | 35 | void main() { 36 | vUv = uv; 37 | float pi = 3.14159265359; 38 | 39 | 40 | float finalAngle = angle - 0.*0.3*sin(progress*6.); 41 | 42 | // @todo account for aspect ratio!!! 43 | vec3 newposition = position; 44 | 45 | // float angle = pi/10.; 46 | float rad = 0.1; 47 | float rolls = 8.; 48 | // rot 49 | newposition = rotate(newposition - vec3(-.5,.5,0.), vec3(0.,0.,1.),-finalAngle) + vec3(-.5,.5,0.); 50 | 51 | float offs = (newposition.x + 0.5)/(sin(finalAngle) + cos(finalAngle)) ; // -0.5..0.5 -> 0..1 52 | float tProgress = clamp( (progress - offs*0.99)/0.01 , 0.,1.); 53 | 54 | // shadows 55 | vFrontShadow = clamp((progress - offs*0.95)/0.05,0.7,1.); 56 | // vBackShadow = 1. - clamp(abs((progress - offs*0.9)/0.1),0.,1.); 57 | // vProgress = clamp((progress - offs*0.95)/0.05,0.,1.); 58 | 59 | 60 | 61 | newposition.z = rad + rad*(1. - offs/2.)*sin(-offs*rolls*pi - 0.5*pi); 62 | newposition.x = - 0.5 + rad*(1. - offs/2.)*cos(-offs*rolls*pi + 0.5*pi); 63 | // // rot back 64 | newposition = rotate(newposition - vec3(-.5,.5,0.), vec3(0.,0.,1.),finalAngle) + vec3(-.5,.5,0.); 65 | // unroll 66 | newposition = rotate(newposition - vec3(-.5,0.5,rad), vec3(sin(finalAngle),cos(finalAngle),0.), -pi*progress*rolls); 67 | newposition += vec3( 68 | -.5 + progress*cos(finalAngle)*(sin(finalAngle) + cos(finalAngle)), 69 | 0.5 - progress*sin(finalAngle)*(sin(finalAngle) + cos(finalAngle)), 70 | rad*(1.-progress/2.) 71 | ); 72 | 73 | // animation 74 | vec3 finalposition = mix(newposition,position,tProgress); 75 | gl_Position = projectionMatrix * modelViewMatrix * vec4(finalposition, 1.0 ); 76 | } 77 | 78 | 79 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "@choojs/findup": { 6 | "version": "0.2.1", 7 | "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", 8 | "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", 9 | "dev": true, 10 | "requires": { 11 | "commander": "^2.15.1" 12 | } 13 | }, 14 | "commander": { 15 | "version": "2.20.3", 16 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 17 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 18 | "dev": true 19 | }, 20 | "core-util-is": { 21 | "version": "1.0.2", 22 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 23 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 24 | "dev": true 25 | }, 26 | "dat.gui": { 27 | "version": "0.7.6", 28 | "resolved": "https://registry.npmjs.org/dat.gui/-/dat.gui-0.7.6.tgz", 29 | "integrity": "sha512-9Uqr4aQUvp9q5P2b4y6gK604HXafubOq578OmOS8mjrIkYrBP4EbQ9gz9YRXgyPh7aQi+b9H/jAG7EucmhYpSA==" 30 | }, 31 | "ev-emitter": { 32 | "version": "1.1.1", 33 | "resolved": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz", 34 | "integrity": "sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==" 35 | }, 36 | "events": { 37 | "version": "1.1.1", 38 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 39 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 40 | "dev": true 41 | }, 42 | "fontfaceobserver": { 43 | "version": "2.1.0", 44 | "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.1.0.tgz", 45 | "integrity": "sha512-ReOsO2F66jUa0jmv2nlM/s1MiutJx/srhAe2+TE8dJCMi02ZZOcCTxTCQFr3Yet+uODUtnr4Mewg+tNQ+4V1Ng==" 46 | }, 47 | "glsl-inject-defines": { 48 | "version": "1.0.3", 49 | "resolved": "https://registry.npmjs.org/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz", 50 | "integrity": "sha1-3RqswsF/yyvT/DJBHGYz0Ne2D9Q=", 51 | "dev": true, 52 | "requires": { 53 | "glsl-token-inject-block": "^1.0.0", 54 | "glsl-token-string": "^1.0.1", 55 | "glsl-tokenizer": "^2.0.2" 56 | } 57 | }, 58 | "glsl-resolve": { 59 | "version": "0.0.1", 60 | "resolved": "https://registry.npmjs.org/glsl-resolve/-/glsl-resolve-0.0.1.tgz", 61 | "integrity": "sha1-iUvvc5ENeSyBtRQxgANdCnivdtM=", 62 | "dev": true, 63 | "requires": { 64 | "resolve": "^0.6.1", 65 | "xtend": "^2.1.2" 66 | }, 67 | "dependencies": { 68 | "resolve": { 69 | "version": "0.6.3", 70 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", 71 | "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=", 72 | "dev": true 73 | } 74 | } 75 | }, 76 | "glsl-token-assignments": { 77 | "version": "2.0.2", 78 | "resolved": "https://registry.npmjs.org/glsl-token-assignments/-/glsl-token-assignments-2.0.2.tgz", 79 | "integrity": "sha1-pdgqt4SZwuimuDy2lJXm5mXOAZ8=", 80 | "dev": true 81 | }, 82 | "glsl-token-defines": { 83 | "version": "1.0.0", 84 | "resolved": "https://registry.npmjs.org/glsl-token-defines/-/glsl-token-defines-1.0.0.tgz", 85 | "integrity": "sha1-y4kqqVmTYjFyhHDU90AySJaX+p0=", 86 | "dev": true, 87 | "requires": { 88 | "glsl-tokenizer": "^2.0.0" 89 | } 90 | }, 91 | "glsl-token-depth": { 92 | "version": "1.1.2", 93 | "resolved": "https://registry.npmjs.org/glsl-token-depth/-/glsl-token-depth-1.1.2.tgz", 94 | "integrity": "sha1-I8XjDuK9JViEtKKLyFC495HpXYQ=", 95 | "dev": true 96 | }, 97 | "glsl-token-descope": { 98 | "version": "1.0.2", 99 | "resolved": "https://registry.npmjs.org/glsl-token-descope/-/glsl-token-descope-1.0.2.tgz", 100 | "integrity": "sha1-D8kKsyYYa4L1l7LnfcniHvzTIHY=", 101 | "dev": true, 102 | "requires": { 103 | "glsl-token-assignments": "^2.0.0", 104 | "glsl-token-depth": "^1.1.0", 105 | "glsl-token-properties": "^1.0.0", 106 | "glsl-token-scope": "^1.1.0" 107 | } 108 | }, 109 | "glsl-token-inject-block": { 110 | "version": "1.1.0", 111 | "resolved": "https://registry.npmjs.org/glsl-token-inject-block/-/glsl-token-inject-block-1.1.0.tgz", 112 | "integrity": "sha1-4QFfWYDBCRgkraomJfHf3ovQADQ=", 113 | "dev": true 114 | }, 115 | "glsl-token-properties": { 116 | "version": "1.0.1", 117 | "resolved": "https://registry.npmjs.org/glsl-token-properties/-/glsl-token-properties-1.0.1.tgz", 118 | "integrity": "sha1-SD3D2Dnw1LXGFx0VkfJJvlPCip4=", 119 | "dev": true 120 | }, 121 | "glsl-token-scope": { 122 | "version": "1.1.2", 123 | "resolved": "https://registry.npmjs.org/glsl-token-scope/-/glsl-token-scope-1.1.2.tgz", 124 | "integrity": "sha1-oXKOeN8kRE+cuT/RjvD3VQOmQ7E=", 125 | "dev": true 126 | }, 127 | "glsl-token-string": { 128 | "version": "1.0.1", 129 | "resolved": "https://registry.npmjs.org/glsl-token-string/-/glsl-token-string-1.0.1.tgz", 130 | "integrity": "sha1-WUQdL4V958NEnJRWZgIezjWOSOw=", 131 | "dev": true 132 | }, 133 | "glsl-token-whitespace-trim": { 134 | "version": "1.0.0", 135 | "resolved": "https://registry.npmjs.org/glsl-token-whitespace-trim/-/glsl-token-whitespace-trim-1.0.0.tgz", 136 | "integrity": "sha1-RtHf6Yx1vX1QTAXX0RsbPpzJOxA=", 137 | "dev": true 138 | }, 139 | "glsl-tokenizer": { 140 | "version": "2.1.5", 141 | "resolved": "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz", 142 | "integrity": "sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==", 143 | "dev": true, 144 | "requires": { 145 | "through2": "^0.6.3" 146 | } 147 | }, 148 | "glslify-bundle": { 149 | "version": "5.1.1", 150 | "resolved": "https://registry.npmjs.org/glslify-bundle/-/glslify-bundle-5.1.1.tgz", 151 | "integrity": "sha512-plaAOQPv62M1r3OsWf2UbjN0hUYAB7Aph5bfH58VxJZJhloRNbxOL9tl/7H71K7OLJoSJ2ZqWOKk3ttQ6wy24A==", 152 | "dev": true, 153 | "requires": { 154 | "glsl-inject-defines": "^1.0.1", 155 | "glsl-token-defines": "^1.0.0", 156 | "glsl-token-depth": "^1.1.1", 157 | "glsl-token-descope": "^1.0.2", 158 | "glsl-token-scope": "^1.1.1", 159 | "glsl-token-string": "^1.0.1", 160 | "glsl-token-whitespace-trim": "^1.0.0", 161 | "glsl-tokenizer": "^2.0.2", 162 | "murmurhash-js": "^1.0.0", 163 | "shallow-copy": "0.0.1" 164 | } 165 | }, 166 | "glslify-deps": { 167 | "version": "1.3.1", 168 | "resolved": "https://registry.npmjs.org/glslify-deps/-/glslify-deps-1.3.1.tgz", 169 | "integrity": "sha512-Ogm179MCazwIRyEqs3g3EOY4Y3XIAa0yl8J5RE9rJC6QH1w8weVOp2RZu0mvnYy/2xIas1w166YR2eZdDkWQxg==", 170 | "dev": true, 171 | "requires": { 172 | "@choojs/findup": "^0.2.0", 173 | "events": "^1.0.2", 174 | "glsl-resolve": "0.0.1", 175 | "glsl-tokenizer": "^2.0.0", 176 | "graceful-fs": "^4.1.2", 177 | "inherits": "^2.0.1", 178 | "map-limit": "0.0.1", 179 | "resolve": "^1.0.0" 180 | } 181 | }, 182 | "graceful-fs": { 183 | "version": "4.2.3", 184 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", 185 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", 186 | "dev": true 187 | }, 188 | "gsap": { 189 | "version": "3.0.5", 190 | "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.0.5.tgz", 191 | "integrity": "sha512-TpIeFoIU8x+46/RGTD/qY+jUAnxcPgqxwOZtJ0YZSaLoL5JR87tyH8p3X02ZnYrMpE0Tt9cOA3ueiWhCVuRv1A==" 192 | }, 193 | "inherits": { 194 | "version": "2.0.4", 195 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 196 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 197 | "dev": true 198 | }, 199 | "isarray": { 200 | "version": "0.0.1", 201 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 202 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 203 | "dev": true 204 | }, 205 | "map-limit": { 206 | "version": "0.0.1", 207 | "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", 208 | "integrity": "sha1-63lhAxwPDo0AG/LVb6toXViCLzg=", 209 | "dev": true, 210 | "requires": { 211 | "once": "~1.3.0" 212 | } 213 | }, 214 | "murmurhash-js": { 215 | "version": "1.0.0", 216 | "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", 217 | "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=", 218 | "dev": true 219 | }, 220 | "once": { 221 | "version": "1.3.3", 222 | "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", 223 | "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", 224 | "dev": true, 225 | "requires": { 226 | "wrappy": "1" 227 | } 228 | }, 229 | "path-parse": { 230 | "version": "1.0.6", 231 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 232 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 233 | "dev": true 234 | }, 235 | "readable-stream": { 236 | "version": "1.0.34", 237 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", 238 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", 239 | "dev": true, 240 | "requires": { 241 | "core-util-is": "~1.0.0", 242 | "inherits": "~2.0.1", 243 | "isarray": "0.0.1", 244 | "string_decoder": "~0.10.x" 245 | } 246 | }, 247 | "resolve": { 248 | "version": "1.14.2", 249 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", 250 | "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", 251 | "dev": true, 252 | "requires": { 253 | "path-parse": "^1.0.6" 254 | } 255 | }, 256 | "shallow-copy": { 257 | "version": "0.0.1", 258 | "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", 259 | "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", 260 | "dev": true 261 | }, 262 | "string_decoder": { 263 | "version": "0.10.31", 264 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 265 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 266 | "dev": true 267 | }, 268 | "three": { 269 | "version": "0.112.1", 270 | "resolved": "https://registry.npmjs.org/three/-/three-0.112.1.tgz", 271 | "integrity": "sha512-8I0O74hiYtKl3LgDNcPJbBGOlpekbcJ6fJnImmW3mFdeUFJ2H9Y3/UuUSW2sBdjrIlCM0gvOkaTEFlofO900TQ==" 272 | }, 273 | "through2": { 274 | "version": "0.6.5", 275 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", 276 | "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", 277 | "dev": true, 278 | "requires": { 279 | "readable-stream": ">=1.0.33-1 <1.1.0-0", 280 | "xtend": ">=4.0.0 <4.1.0-0" 281 | }, 282 | "dependencies": { 283 | "xtend": { 284 | "version": "4.0.2", 285 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 286 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 287 | "dev": true 288 | } 289 | } 290 | }, 291 | "wrappy": { 292 | "version": "1.0.2", 293 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 294 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 295 | "dev": true 296 | }, 297 | "xtend": { 298 | "version": "2.2.0", 299 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", 300 | "integrity": "sha1-7vax8ZjByN6vrYsXZaBNrUoBxak=", 301 | "dev": true 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "dat.gui": "^0.7.6", 4 | "ev-emitter": "^1.1.1", 5 | "fontfaceobserver": "^2.1.0", 6 | "gsap": "^3.0.5", 7 | "three": "^0.112.1", 8 | "three-orbit-controls": "^82.1.0" 9 | }, 10 | "devDependencies": { 11 | "cssnano": "^4.1.10", 12 | "glslify-bundle": "^5.1.1", 13 | "glslify-deps": "^1.3.1" 14 | } 15 | } 16 | --------------------------------------------------------------------------------