├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── 1.dc197a9a.jpg ├── 10.5e22fdc3.jpg ├── 11.a56b8aeb.jpg ├── 12.b5a57fd7.jpg ├── 13.3858c623.jpg ├── 14.c732d2b3.jpg ├── 2.3ca6bb44.jpg ├── 3.a433b89d.jpg ├── 4.c6d96be5.jpg ├── 5.689b68fd.jpg ├── 6.e96dcfff.jpg ├── 7.2d66e3ed.jpg ├── 8.f4323fe0.jpg ├── 9.c9233dac.jpg ├── base.98fd6c19.css ├── demo1.151408fb.js ├── demo1.ea7e4db3.css ├── demo2.06b37b5f.css ├── demo2.44794d1a.js ├── favicon.26242483.ico ├── index.html └── index2.html ├── package.json └── src ├── css ├── base.css ├── demo1.css └── demo2.css ├── favicon.ico ├── img ├── 1.jpg ├── 10.jpg ├── 11.jpg ├── 12.jpg ├── 13.jpg ├── 14.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg └── 9.jpg ├── index.html ├── index2.html └── js ├── cursor.js ├── demo1 ├── galleryController.js ├── galleryItem.js └── index.js ├── demo2 ├── galleryController.js ├── galleryItem.js └── index.js └── utils.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.cache 3 | package-lock.json -------------------------------------------------------------------------------- /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 | # Image Stack Intro Animation 2 | 3 | A simple intro animation where an image stack moves to become a grid. 4 | 5 | ![Image Stack](https://tympanus.net/codrops/wp-content/uploads/2020/11/IntroImageStackAnimation_featured.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=52018) 8 | 9 | [Demo](https://tympanus.net/Development/ImageStackGrid/) 10 | 11 | 12 | ## Installation 13 | 14 | Install dependencies: 15 | 16 | ``` 17 | npm install 18 | ``` 19 | 20 | Compile the code for development and start a local server: 21 | 22 | ``` 23 | npm start 24 | ``` 25 | 26 | Create the build: 27 | 28 | ``` 29 | npm run build 30 | ``` 31 | 32 | ## Credits 33 | 34 | - Images by [Unsplash](http://unsplash.com) 35 | 36 | 37 | ## Misc 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 | -------------------------------------------------------------------------------- /dist/1.dc197a9a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/1.dc197a9a.jpg -------------------------------------------------------------------------------- /dist/10.5e22fdc3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/10.5e22fdc3.jpg -------------------------------------------------------------------------------- /dist/11.a56b8aeb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/11.a56b8aeb.jpg -------------------------------------------------------------------------------- /dist/12.b5a57fd7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/12.b5a57fd7.jpg -------------------------------------------------------------------------------- /dist/13.3858c623.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/13.3858c623.jpg -------------------------------------------------------------------------------- /dist/14.c732d2b3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/14.c732d2b3.jpg -------------------------------------------------------------------------------- /dist/2.3ca6bb44.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/2.3ca6bb44.jpg -------------------------------------------------------------------------------- /dist/3.a433b89d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/3.a433b89d.jpg -------------------------------------------------------------------------------- /dist/4.c6d96be5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/4.c6d96be5.jpg -------------------------------------------------------------------------------- /dist/5.689b68fd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/5.689b68fd.jpg -------------------------------------------------------------------------------- /dist/6.e96dcfff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/6.e96dcfff.jpg -------------------------------------------------------------------------------- /dist/7.2d66e3ed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/7.2d66e3ed.jpg -------------------------------------------------------------------------------- /dist/8.f4323fe0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/8.f4323fe0.jpg -------------------------------------------------------------------------------- /dist/9.c9233dac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/9.c9233dac.jpg -------------------------------------------------------------------------------- /dist/base.98fd6c19.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #f8f6f3; 14 | --color-bg: #0c0c0c; 15 | --color-link: #7d6350; 16 | --color-link-hover: #f8f6f3; 17 | color: var(--color-text); 18 | background-color: var(--color-bg); 19 | --cursor-stroke: none; 20 | --cursor-fill: #c5681c; 21 | --cursor-stroke-width: 1px; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | font-family: halyard-display, sans-serif; 25 | } 26 | 27 | .noscroll main { 28 | overflow: hidden; 29 | height: 100vh; 30 | } 31 | 32 | /* Page Loader */ 33 | .js .loading::before, 34 | .js .loading::after { 35 | content: ''; 36 | position: fixed; 37 | z-index: 1000; 38 | } 39 | 40 | .js .loading::before { 41 | top: 0; 42 | left: 0; 43 | width: 100%; 44 | height: 100%; 45 | background: var(--color-bg); 46 | } 47 | 48 | .js .loading::after { 49 | top: 50%; 50 | left: 50%; 51 | width: 60px; 52 | height: 60px; 53 | margin: -30px 0 0 -30px; 54 | border-radius: 50%; 55 | opacity: 0.4; 56 | background: var(--color-link); 57 | animation: loaderAnim 0.7s linear infinite alternate forwards; 58 | } 59 | 60 | @keyframes loaderAnim { 61 | to { 62 | opacity: 1; 63 | transform: scale3d(0.5, 0.5, 1); 64 | } 65 | 66 | } 67 | 68 | a { 69 | text-decoration: underline; 70 | color: var(--color-link); 71 | outline: none; 72 | } 73 | 74 | a:hover, 75 | a:focus { 76 | text-decoration: none; 77 | color: var(--color-link-hover); 78 | outline: none; 79 | } 80 | 81 | main { 82 | display: grid; 83 | grid-template-areas: 'frame' 'content'; 84 | } 85 | 86 | .frame { 87 | padding: 1rem; 88 | text-align: center; 89 | position: relative; 90 | z-index: 1000; 91 | grid-area: frame; 92 | font-weight: 300; 93 | } 94 | 95 | .frame__title { 96 | font-size: 1rem; 97 | margin: 0 0 1rem; 98 | font-weight: 500; 99 | position: relative; 100 | padding: 0 3.5rem; 101 | } 102 | 103 | .frame__title::before, 104 | .frame__title::after { 105 | content: ''; 106 | position: absolute; 107 | width: 1rem; 108 | height: 1.5rem; 109 | border: 2px solid #c5681c; 110 | left: 0; 111 | top: calc(50% - 0.75rem); 112 | } 113 | 114 | .frame__title::before { 115 | transform: rotate(-18deg); 116 | transform-origin: top right; 117 | } 118 | 119 | .frame__links { 120 | display: inline; 121 | } 122 | 123 | .frame__links a:not(:last-child), 124 | .frame__demos a:not(:last-child) { 125 | margin-right: 1rem; 126 | } 127 | 128 | .frame__demos { 129 | margin: 1rem 0; 130 | } 131 | 132 | .frame__demo--current, 133 | .frame__demo--current:hover { 134 | color: var(--color-text); 135 | text-decoration: none; 136 | } 137 | 138 | .cursor { 139 | display: none; 140 | } 141 | 142 | /* Recommended styles for Splitting */ 143 | .splitting .word, 144 | .splitting .char { 145 | display: inline-block; 146 | } 147 | 148 | /* Psuedo-element chars */ 149 | .splitting .char { 150 | white-space: nowrap; 151 | position: relative; 152 | } 153 | 154 | @media screen and (min-width:53em) { 155 | :root { 156 | font-size: 18px; 157 | } 158 | main { 159 | grid-template-areas: 'content'; 160 | grid-template-rows: 100%; 161 | grid-template-columns: 100%; 162 | width: 100%; 163 | } 164 | 165 | .frame { 166 | padding: 1.5rem 2rem; 167 | top: 0; 168 | left: 0; 169 | width: 100%; 170 | position: absolute; 171 | display: flex; 172 | justify-content: space-between; 173 | align-items: flex-start; 174 | } 175 | 176 | .frame__title, 177 | .frame__demos { 178 | margin: 0; 179 | } 180 | 181 | .frame__demos { 182 | margin: 0 auto 0 4rem; 183 | } 184 | 185 | .frame__links { 186 | padding: 0; 187 | } 188 | 189 | .frame a { 190 | pointer-events: auto; 191 | } 192 | 193 | } 194 | 195 | @media (any-pointer:fine) { 196 | .cursor { 197 | position: fixed; 198 | top: 0; 199 | left: 0; 200 | display: block; 201 | pointer-events: none; 202 | } 203 | 204 | .cursor__inner { 205 | fill: var(--cursor-fill); 206 | stroke: var(--cursor-stroke); 207 | stroke-width: var(--cursor-stroke-width); 208 | opacity: 0.7; 209 | } 210 | 211 | .no-js .cursor { 212 | display: none; 213 | } 214 | 215 | } 216 | 217 | /*! locomotive-scroll v4.0.4 | MIT License | https://github.com/locomotivemtl/locomotive-scroll */ 218 | html.has-scroll-smooth { 219 | overflow: hidden; 220 | } 221 | 222 | html.has-scroll-dragging { 223 | -webkit-user-select: none; 224 | -moz-user-select: none; 225 | -ms-user-select: none; 226 | user-select: none; 227 | } 228 | 229 | .has-scroll-smooth body { 230 | overflow: hidden; 231 | } 232 | 233 | .has-scroll-smooth [data-scroll-container] { 234 | min-height: 100vh; 235 | } 236 | 237 | [data-scroll-direction="horizontal"] [data-scroll-container] { 238 | white-space: nowrap; 239 | height: 100vh; 240 | display: inline-block; 241 | white-space: nowrap; 242 | } 243 | 244 | [data-scroll-direction="horizontal"] [data-scroll-section] { 245 | display: inline-block; 246 | vertical-align: top; 247 | white-space: nowrap; 248 | height: 100%; 249 | } 250 | 251 | .c-scrollbar { 252 | position: absolute; 253 | right: 0; 254 | top: 0; 255 | width: 11px; 256 | height: 100%; 257 | transform-origin: center right; 258 | transition: transform 0.3s, opacity 0.3s; 259 | opacity: 0; 260 | } 261 | 262 | .c-scrollbar:hover { 263 | transform: scaleX(1.45); 264 | } 265 | 266 | .c-scrollbar:hover, 267 | .has-scroll-scrolling .c-scrollbar, 268 | .has-scroll-dragging .c-scrollbar { 269 | opacity: 1; 270 | } 271 | 272 | [data-scroll-direction="horizontal"] .c-scrollbar { 273 | width: 100%; 274 | height: 10px; 275 | top: auto; 276 | bottom: 0; 277 | transform: scaleY(1); 278 | } 279 | 280 | [data-scroll-direction="horizontal"] .c-scrollbar:hover { 281 | transform: scaleY(1.3); 282 | } 283 | 284 | .c-scrollbar_thumb { 285 | position: absolute; 286 | top: 0; 287 | right: 0; 288 | background-color: black; 289 | opacity: 0.5; 290 | width: 7px; 291 | border-radius: 10px; 292 | margin: 2px; 293 | cursor: -webkit-grab; 294 | cursor: grab; 295 | } 296 | 297 | .has-scroll-dragging .c-scrollbar_thumb { 298 | cursor: -webkit-grabbing; 299 | cursor: grabbing; 300 | } 301 | 302 | [data-scroll-direction="horizontal"] .c-scrollbar_thumb { 303 | right: auto; 304 | bottom: 0; 305 | } -------------------------------------------------------------------------------- /dist/demo1.ea7e4db3.css: -------------------------------------------------------------------------------- 1 | .title { 2 | grid-area: content; 3 | font-size: 15vw; 4 | margin: 0; 5 | font-family: ivypresto-headline, serif; 6 | font-weight: 100; 7 | justify-self: center; 8 | align-self: center; 9 | position: relative; 10 | z-index: 1500; 11 | color: #c5681c; 12 | pointer-events: none; 13 | width: 100%; 14 | height: 100vh; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | align-self: start; 19 | } 20 | 21 | .js .title { 22 | opacity: 0; 23 | } 24 | 25 | .gallery { 26 | grid-area: content; 27 | justify-self: center; 28 | } 29 | 30 | .gallery__item { 31 | position: relative; 32 | margin: 25vh 0; 33 | display: grid; 34 | grid-template-areas: 'gallery-caption' 'gallery-image'; 35 | grid-template-columns: 100%; 36 | } 37 | 38 | .gallery__item-img { 39 | grid-area: gallery-image; 40 | width: calc(100vw - 30vw); 41 | max-width: 375px; 42 | overflow: hidden; 43 | position: relative; 44 | will-change: transform, opacity; 45 | cursor: pointer; 46 | } 47 | 48 | .noscroll .gallery__item-img { 49 | cursor: default; 50 | } 51 | 52 | .js .gallery__item-img { 53 | opacity: 0; 54 | } 55 | 56 | .gallery__item-imginner { 57 | background-size: cover; 58 | background-position: 50% 0; 59 | width: 100%; 60 | padding-bottom: 140%; 61 | } 62 | 63 | .gallery__item-caption { 64 | grid-area: gallery-caption; 65 | padding: 0 0 1rem; 66 | } 67 | 68 | .gallery__item-title { 69 | position: absolute; 70 | left: 0; 71 | width: 100%; 72 | text-align: center; 73 | font-size: 10vw; 74 | font-family: ivypresto-headline, serif; 75 | font-weight: 100; 76 | display: flex; 77 | justify-content: center; 78 | margin: 0; 79 | bottom: 15%; 80 | pointer-events: none; 81 | } 82 | 83 | .gallery__item-title span.char { 84 | opacity: 0; 85 | will-change: transform; 86 | } 87 | 88 | .gallery__item-number { 89 | font-family: ivypresto-headline, serif; 90 | font-weight: 100; 91 | font-size: 2.5rem; 92 | border-bottom: 1px solid #494444; 93 | width: 100%; 94 | display: block; 95 | margin-bottom: 1rem; 96 | } 97 | 98 | .gallery__item-text { 99 | margin: 0; 100 | } 101 | 102 | .gallery__item-number, 103 | .gallery__item-text { 104 | opacity: 0; 105 | will-change: transform, opacity; 106 | } 107 | 108 | @media screen and (min-width: 53em) { 109 | .gallery__item { 110 | grid-template-areas: 'gallery-filler gallery-image gallery-caption'; 111 | grid-template-columns: 15vw auto 15vw; 112 | } 113 | .gallery__item-caption { 114 | padding: 0 0 0 1rem; 115 | } 116 | } -------------------------------------------------------------------------------- /dist/demo2.06b37b5f.css: -------------------------------------------------------------------------------- 1 | body { 2 | --color-text: #131212; 3 | --color-bg: #eee7e0; 4 | --color-link: #52433e; 5 | --color-link-hover: #131212; 6 | } 7 | 8 | .content { 9 | padding: 20vh 1rem 4rem; 10 | } 11 | 12 | .content__title { 13 | font-family: ivypresto-headline, serif; 14 | font-weight: 700; 15 | font-size: 3rem; 16 | margin: 0; 17 | } 18 | 19 | .content__text { 20 | font-weight: 300; 21 | max-width: 500px; 22 | } 23 | 24 | .js .content__title, 25 | .js .content__text { 26 | opacity: 0; 27 | } 28 | 29 | .gallery { 30 | display: grid; 31 | grid-template-columns: repeat(3,minmax(min-content,320px)); 32 | grid-column-gap: 2rem; 33 | grid-row-gap: 5vh; 34 | padding: 4rem 0 20rem; 35 | } 36 | 37 | .gallery__item { 38 | margin: 0; 39 | display: grid; 40 | grid-template-areas: 'gallery-caption' 'gallery-image'; 41 | grid-template-columns: 100%; 42 | } 43 | 44 | .gallery__item-img { 45 | grid-area: gallery-image; 46 | width: 100%; 47 | overflow: hidden; 48 | position: relative; 49 | will-change: transform, opacity; 50 | transform: translateZ(0); 51 | } 52 | 53 | .js .gallery__item-img { 54 | opacity: 0; 55 | } 56 | 57 | .gallery__item-imginner { 58 | background-size: cover; 59 | background-position: 50% 0; 60 | width: 100%; 61 | padding-bottom: 140%; 62 | will-change: transform; 63 | } 64 | 65 | .gallery__item-caption { 66 | grid-area: gallery-caption; 67 | } 68 | 69 | .gallery__item-title { 70 | font-size: 0.9rem; 71 | margin: 1rem 0 0.5rem; 72 | font-weight: 500; 73 | display: inline-block; 74 | } 75 | 76 | .gallery__item-title span.char { 77 | opacity: 0; 78 | will-change: transform; 79 | } 80 | 81 | @media screen and (min-width: 53em) { 82 | .content { 83 | padding: 20vh 5rem 4rem; 84 | } 85 | .gallery__item-title { 86 | font-size: 1.5rem; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /dist/favicon.26242483.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/dist/favicon.26242483.ico -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Image Stack Intro Animation | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |

Image Stack Intro Animation

30 |
31 | Demo 1 32 | Demo 2 33 |
34 | 39 |
40 |

Nouveau

41 | 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /dist/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Image Stack Intro Animation | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |

Image Stack Intro Animation

30 |
31 | Demo 1 32 | Demo 2 33 |
34 | 39 |
40 |
41 |

Pleasure City

42 |

It is difficult not to feel that the unconscious aim in the most typical modern pleasure resorts is a return to the womb. For there, too, one was never alone, one never saw daylight, the temperature was always regulated, one did not have to worry about work or food, and one's thoughts, if any, were drowned by a continuous rhythmic throbbing.

43 | 99 |
100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imagestackgrid", 3 | "version": "1.0.0", 4 | "description": "*How to use this template:*", 5 | "main": "src/js/index.js", 6 | "scripts": { 7 | "start": "parcel src/index.html --open", 8 | "clean": "rm -rf dist/*", 9 | "build:parcel": "parcel build src/index.html --no-content-hash --no-minify --no-source-maps --public-url ./", 10 | "build": "npm run clean && npm run build:parcel" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/codrops/%5BNAME%5D.git" 15 | }, 16 | "keywords": [], 17 | "author": "Codrops", 18 | "license": "MIT", 19 | "homepage": "http://[HOMEPAGE]", 20 | "bugs": { 21 | "url": "https://github.com/codrops/[NAME]/issues" 22 | }, 23 | "dependencies": { 24 | "gsap": "^3.5.1", 25 | "imagesloaded": "^4.1.4", 26 | "locomotive-scroll": "^4.0.4", 27 | "parcel-bundler": "^1.12.4", 28 | "splitting": "^1.0.6" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #f8f6f3; 14 | --color-bg: #0c0c0c; 15 | --color-link: #7d6350; 16 | --color-link-hover: #f8f6f3; 17 | color: var(--color-text); 18 | background-color: var(--color-bg); 19 | --cursor-stroke: none; 20 | --cursor-fill: #c5681c; 21 | --cursor-stroke-width: 1px; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | font-family: halyard-display, sans-serif; 25 | } 26 | 27 | .noscroll main { 28 | overflow: hidden; 29 | height: 100vh; 30 | } 31 | 32 | /* Page Loader */ 33 | .js .loading::before, 34 | .js .loading::after { 35 | content: ''; 36 | position: fixed; 37 | z-index: 1000; 38 | } 39 | 40 | .js .loading::before { 41 | top: 0; 42 | left: 0; 43 | width: 100%; 44 | height: 100%; 45 | background: var(--color-bg); 46 | } 47 | 48 | .js .loading::after { 49 | top: 50%; 50 | left: 50%; 51 | width: 60px; 52 | height: 60px; 53 | margin: -30px 0 0 -30px; 54 | border-radius: 50%; 55 | opacity: 0.4; 56 | background: var(--color-link); 57 | animation: loaderAnim 0.7s linear infinite alternate forwards; 58 | } 59 | 60 | @keyframes loaderAnim { 61 | to { 62 | opacity: 1; 63 | transform: scale3d(0.5, 0.5, 1); 64 | } 65 | 66 | } 67 | 68 | a { 69 | text-decoration: underline; 70 | color: var(--color-link); 71 | outline: none; 72 | } 73 | 74 | a:hover, 75 | a:focus { 76 | text-decoration: none; 77 | color: var(--color-link-hover); 78 | outline: none; 79 | } 80 | 81 | main { 82 | display: grid; 83 | grid-template-areas: 'frame' 'content'; 84 | } 85 | 86 | .frame { 87 | padding: 1rem; 88 | text-align: center; 89 | position: relative; 90 | z-index: 1000; 91 | grid-area: frame; 92 | font-weight: 300; 93 | } 94 | 95 | .frame__title { 96 | font-size: 1rem; 97 | margin: 0 0 1rem; 98 | font-weight: 500; 99 | position: relative; 100 | padding: 0 3.5rem; 101 | } 102 | 103 | .frame__title::before, 104 | .frame__title::after { 105 | content: ''; 106 | position: absolute; 107 | width: 1rem; 108 | height: 1.5rem; 109 | border: 2px solid #c5681c; 110 | left: 0; 111 | top: calc(50% - 0.75rem); 112 | } 113 | 114 | .frame__title::before { 115 | transform: rotate(-18deg); 116 | transform-origin: top right; 117 | } 118 | 119 | .frame__links { 120 | display: inline; 121 | } 122 | 123 | .frame__links a:not(:last-child), 124 | .frame__demos a:not(:last-child) { 125 | margin-right: 1rem; 126 | } 127 | 128 | .frame__demos { 129 | margin: 1rem 0; 130 | } 131 | 132 | .frame__demo--current, 133 | .frame__demo--current:hover { 134 | color: var(--color-text); 135 | text-decoration: none; 136 | } 137 | 138 | .cursor { 139 | display: none; 140 | } 141 | 142 | /* Recommended styles for Splitting */ 143 | .splitting .word, 144 | .splitting .char { 145 | display: inline-block; 146 | } 147 | 148 | /* Psuedo-element chars */ 149 | .splitting .char { 150 | white-space: nowrap; 151 | position: relative; 152 | } 153 | 154 | @media screen and (min-width:53em) { 155 | :root { 156 | font-size: 18px; 157 | } 158 | main { 159 | grid-template-areas: 'content'; 160 | grid-template-rows: 100%; 161 | grid-template-columns: 100%; 162 | width: 100%; 163 | } 164 | 165 | .frame { 166 | padding: 1.5rem 2rem; 167 | top: 0; 168 | left: 0; 169 | width: 100%; 170 | position: absolute; 171 | display: flex; 172 | justify-content: space-between; 173 | align-items: flex-start; 174 | } 175 | 176 | .frame__title, 177 | .frame__demos { 178 | margin: 0; 179 | } 180 | 181 | .frame__demos { 182 | margin: 0 auto 0 4rem; 183 | } 184 | 185 | .frame__links { 186 | padding: 0; 187 | } 188 | 189 | .frame a { 190 | pointer-events: auto; 191 | } 192 | 193 | } 194 | 195 | @media (any-pointer:fine) { 196 | .cursor { 197 | position: fixed; 198 | top: 0; 199 | left: 0; 200 | display: block; 201 | pointer-events: none; 202 | } 203 | 204 | .cursor__inner { 205 | fill: var(--cursor-fill); 206 | stroke: var(--cursor-stroke); 207 | stroke-width: var(--cursor-stroke-width); 208 | opacity: 0.7; 209 | } 210 | 211 | .no-js .cursor { 212 | display: none; 213 | } 214 | 215 | } 216 | 217 | /*! locomotive-scroll v4.0.4 | MIT License | https://github.com/locomotivemtl/locomotive-scroll */ 218 | html.has-scroll-smooth { 219 | overflow: hidden; 220 | } 221 | 222 | html.has-scroll-dragging { 223 | -webkit-user-select: none; 224 | -moz-user-select: none; 225 | -ms-user-select: none; 226 | user-select: none; 227 | } 228 | 229 | .has-scroll-smooth body { 230 | overflow: hidden; 231 | } 232 | 233 | .has-scroll-smooth [data-scroll-container] { 234 | min-height: 100vh; 235 | } 236 | 237 | [data-scroll-direction="horizontal"] [data-scroll-container] { 238 | white-space: nowrap; 239 | height: 100vh; 240 | display: inline-block; 241 | white-space: nowrap; 242 | } 243 | 244 | [data-scroll-direction="horizontal"] [data-scroll-section] { 245 | display: inline-block; 246 | vertical-align: top; 247 | white-space: nowrap; 248 | height: 100%; 249 | } 250 | 251 | .c-scrollbar { 252 | position: absolute; 253 | right: 0; 254 | top: 0; 255 | width: 11px; 256 | height: 100%; 257 | transform-origin: center right; 258 | transition: transform 0.3s, opacity 0.3s; 259 | opacity: 0; 260 | } 261 | 262 | .c-scrollbar:hover { 263 | transform: scaleX(1.45); 264 | } 265 | 266 | .c-scrollbar:hover, 267 | .has-scroll-scrolling .c-scrollbar, 268 | .has-scroll-dragging .c-scrollbar { 269 | opacity: 1; 270 | } 271 | 272 | [data-scroll-direction="horizontal"] .c-scrollbar { 273 | width: 100%; 274 | height: 10px; 275 | top: auto; 276 | bottom: 0; 277 | transform: scaleY(1); 278 | } 279 | 280 | [data-scroll-direction="horizontal"] .c-scrollbar:hover { 281 | transform: scaleY(1.3); 282 | } 283 | 284 | .c-scrollbar_thumb { 285 | position: absolute; 286 | top: 0; 287 | right: 0; 288 | background-color: black; 289 | opacity: 0.5; 290 | width: 7px; 291 | border-radius: 10px; 292 | margin: 2px; 293 | cursor: -webkit-grab; 294 | cursor: grab; 295 | } 296 | 297 | .has-scroll-dragging .c-scrollbar_thumb { 298 | cursor: -webkit-grabbing; 299 | cursor: grabbing; 300 | } 301 | 302 | [data-scroll-direction="horizontal"] .c-scrollbar_thumb { 303 | right: auto; 304 | bottom: 0; 305 | } -------------------------------------------------------------------------------- /src/css/demo1.css: -------------------------------------------------------------------------------- 1 | .title { 2 | grid-area: content; 3 | font-size: 15vw; 4 | margin: 0; 5 | font-family: ivypresto-headline, serif; 6 | font-weight: 100; 7 | justify-self: center; 8 | align-self: center; 9 | position: relative; 10 | z-index: 1500; 11 | color: #c5681c; 12 | pointer-events: none; 13 | width: 100%; 14 | height: 100vh; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | align-self: start; 19 | } 20 | 21 | .js .title { 22 | opacity: 0; 23 | } 24 | 25 | .gallery { 26 | grid-area: content; 27 | justify-self: center; 28 | } 29 | 30 | .gallery__item { 31 | position: relative; 32 | margin: 25vh 0; 33 | display: grid; 34 | grid-template-areas: 'gallery-caption' 'gallery-image'; 35 | grid-template-columns: 100%; 36 | } 37 | 38 | .gallery__item-img { 39 | grid-area: gallery-image; 40 | width: calc(100vw - 30vw); 41 | max-width: 375px; 42 | overflow: hidden; 43 | position: relative; 44 | will-change: transform, opacity; 45 | cursor: pointer; 46 | } 47 | 48 | .noscroll .gallery__item-img { 49 | cursor: default; 50 | } 51 | 52 | .js .gallery__item-img { 53 | opacity: 0; 54 | } 55 | 56 | .gallery__item-imginner { 57 | background-size: cover; 58 | background-position: 50% 0; 59 | width: 100%; 60 | padding-bottom: 140%; 61 | } 62 | 63 | .gallery__item-caption { 64 | grid-area: gallery-caption; 65 | padding: 0 0 1rem; 66 | } 67 | 68 | .gallery__item-title { 69 | position: absolute; 70 | left: 0; 71 | width: 100%; 72 | text-align: center; 73 | font-size: 10vw; 74 | font-family: ivypresto-headline, serif; 75 | font-weight: 100; 76 | display: flex; 77 | justify-content: center; 78 | margin: 0; 79 | bottom: 15%; 80 | pointer-events: none; 81 | } 82 | 83 | .gallery__item-title span.char { 84 | opacity: 0; 85 | will-change: transform; 86 | } 87 | 88 | .gallery__item-number { 89 | font-family: ivypresto-headline, serif; 90 | font-weight: 100; 91 | font-size: 2.5rem; 92 | border-bottom: 1px solid #494444; 93 | width: 100%; 94 | display: block; 95 | margin-bottom: 1rem; 96 | } 97 | 98 | .gallery__item-text { 99 | margin: 0; 100 | } 101 | 102 | .gallery__item-number, 103 | .gallery__item-text { 104 | opacity: 0; 105 | will-change: transform, opacity; 106 | } 107 | 108 | @media screen and (min-width: 53em) { 109 | .gallery__item { 110 | grid-template-areas: 'gallery-filler gallery-image gallery-caption'; 111 | grid-template-columns: 15vw auto 15vw; 112 | } 113 | .gallery__item-caption { 114 | padding: 0 0 0 1rem; 115 | } 116 | } -------------------------------------------------------------------------------- /src/css/demo2.css: -------------------------------------------------------------------------------- 1 | body { 2 | --color-text: #131212; 3 | --color-bg: #eee7e0; 4 | --color-link: #52433e; 5 | --color-link-hover: #131212; 6 | } 7 | 8 | .content { 9 | padding: 20vh 1rem 4rem; 10 | } 11 | 12 | .content__title { 13 | font-family: ivypresto-headline, serif; 14 | font-weight: 700; 15 | font-size: 3rem; 16 | margin: 0; 17 | } 18 | 19 | .content__text { 20 | font-weight: 300; 21 | max-width: 500px; 22 | } 23 | 24 | .js .content__title, 25 | .js .content__text { 26 | opacity: 0; 27 | } 28 | 29 | .gallery { 30 | display: grid; 31 | grid-template-columns: repeat(3,minmax(min-content,320px)); 32 | grid-column-gap: 2rem; 33 | grid-row-gap: 5vh; 34 | padding: 4rem 0 20rem; 35 | } 36 | 37 | .gallery__item { 38 | margin: 0; 39 | display: grid; 40 | grid-template-areas: 'gallery-caption' 'gallery-image'; 41 | grid-template-columns: 100%; 42 | } 43 | 44 | .gallery__item-img { 45 | grid-area: gallery-image; 46 | width: 100%; 47 | overflow: hidden; 48 | position: relative; 49 | will-change: transform, opacity; 50 | transform: translateZ(0); 51 | } 52 | 53 | .js .gallery__item-img { 54 | opacity: 0; 55 | } 56 | 57 | .gallery__item-imginner { 58 | background-size: cover; 59 | background-position: 50% 0; 60 | width: 100%; 61 | padding-bottom: 140%; 62 | will-change: transform; 63 | } 64 | 65 | .gallery__item-caption { 66 | grid-area: gallery-caption; 67 | } 68 | 69 | .gallery__item-title { 70 | font-size: 0.9rem; 71 | margin: 1rem 0 0.5rem; 72 | font-weight: 500; 73 | display: inline-block; 74 | } 75 | 76 | .gallery__item-title span.char { 77 | opacity: 0; 78 | will-change: transform; 79 | } 80 | 81 | @media screen and (min-width: 53em) { 82 | .content { 83 | padding: 20vh 5rem 4rem; 84 | } 85 | .gallery__item-title { 86 | font-size: 1.5rem; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/favicon.ico -------------------------------------------------------------------------------- /src/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/1.jpg -------------------------------------------------------------------------------- /src/img/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/10.jpg -------------------------------------------------------------------------------- /src/img/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/11.jpg -------------------------------------------------------------------------------- /src/img/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/12.jpg -------------------------------------------------------------------------------- /src/img/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/13.jpg -------------------------------------------------------------------------------- /src/img/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/14.jpg -------------------------------------------------------------------------------- /src/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/2.jpg -------------------------------------------------------------------------------- /src/img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/3.jpg -------------------------------------------------------------------------------- /src/img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/4.jpg -------------------------------------------------------------------------------- /src/img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/5.jpg -------------------------------------------------------------------------------- /src/img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/6.jpg -------------------------------------------------------------------------------- /src/img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/7.jpg -------------------------------------------------------------------------------- /src/img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/8.jpg -------------------------------------------------------------------------------- /src/img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ImageStackGrid/0113fac4877a3a6b2db33a7f118faef2df979078/src/img/9.jpg -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Image Stack Intro Animation | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |

Image Stack Intro Animation

22 |
23 | Demo 1 24 | Demo 2 25 |
26 | 31 |
32 |

Nouveau

33 | 80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Image Stack Intro Animation | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |

Image Stack Intro Animation

22 |
23 | Demo 1 24 | Demo 2 25 |
26 | 31 |
32 |
33 |

Pleasure City

34 |

It is difficult not to feel that the unconscious aim in the most typical modern pleasure resorts is a return to the womb. For there, too, one was never alone, one never saw daylight, the temperature was always regulated, one did not have to worry about work or food, and one's thoughts, if any, were drowned by a continuous rhythmic throbbing.

35 | 91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/js/cursor.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { lerp, getMousePos } from './utils'; 3 | 4 | // Track the mouse position 5 | let mouse = {x: 0, y: 0}; 6 | window.addEventListener('mousemove', ev => mouse = getMousePos(ev)); 7 | 8 | export default class Cursor { 9 | constructor(el) { 10 | this.DOM = {el: el}; 11 | this.DOM.el.style.opacity = 0; 12 | 13 | this.bounds = this.DOM.el.getBoundingClientRect(); 14 | 15 | this.renderedStyles = { 16 | tx: {previous: 0, current: 0, amt: 0.2}, 17 | ty: {previous: 0, current: 0, amt: 0.2}, 18 | scale: {previous: 1, current: 1, amt: 0.15}, 19 | //opacity: {previous: 1, current: 1, amt: 0.1} 20 | }; 21 | 22 | this.onMouseMoveEv = () => { 23 | this.renderedStyles.tx.previous = this.renderedStyles.tx.current = mouse.x - this.bounds.width/2; 24 | this.renderedStyles.ty.previous = this.renderedStyles.ty.previous = mouse.y - this.bounds.height/2; 25 | gsap.to(this.DOM.el, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1}); 26 | requestAnimationFrame(() => this.render()); 27 | window.removeEventListener('mousemove', this.onMouseMoveEv); 28 | }; 29 | window.addEventListener('mousemove', this.onMouseMoveEv); 30 | } 31 | enter() { 32 | this.renderedStyles['scale'].current = 2.5; 33 | //this.renderedStyles['opacity'].current = 0.5; 34 | } 35 | leave() { 36 | this.renderedStyles['scale'].current = 1; 37 | //this.renderedStyles['opacity'].current = 1; 38 | } 39 | render() { 40 | this.renderedStyles['tx'].current = mouse.x - this.bounds.width/2; 41 | this.renderedStyles['ty'].current = mouse.y - this.bounds.height/2; 42 | 43 | for (const key in this.renderedStyles ) { 44 | this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt); 45 | } 46 | 47 | this.DOM.el.style.transform = `translateX(${(this.renderedStyles['tx'].previous)}px) translateY(${this.renderedStyles['ty'].previous}px) scale(${this.renderedStyles['scale'].previous})`; 48 | //this.DOM.el.style.opacity = this.renderedStyles['opacity'].previous; 49 | 50 | requestAnimationFrame(() => this.render()); 51 | } 52 | } -------------------------------------------------------------------------------- /src/js/demo1/galleryController.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { calcWinsize, getRandomInteger } from '../utils'; 3 | import GalleryItem from './galleryItem'; 4 | import LocomotiveScroll from 'locomotive-scroll'; 5 | import Splitting from "splitting"; 6 | 7 | // Call the splittingjs to transform the data-splitting texts to spans of chars 8 | Splitting(); 9 | 10 | // Initialize the Locomotive scroll 11 | const scroll = new LocomotiveScroll({ 12 | el: document.querySelector('[data-scroll-container]'), 13 | smooth: true 14 | }); 15 | 16 | // Calculate the viewport size 17 | let winsize = calcWinsize(); 18 | window.addEventListener('resize', () => winsize = calcWinsize()); 19 | 20 | export default class GalleryController { 21 | constructor(galleryEl) { 22 | this.DOM = { 23 | galleryEl: galleryEl, 24 | title: document.querySelector('.title') 25 | }; 26 | this.DOM.titleChars = this.DOM.title.querySelectorAll('.char'); 27 | this.titleCharsTotal = this.DOM.titleChars.length; 28 | this.DOM.galleryItemElems = [...this.DOM.galleryEl.querySelectorAll('.gallery__item')]; 29 | this.galleryItems = []; 30 | this.DOM.galleryItemElems.forEach(el => this.galleryItems.push(new GalleryItem(el))); 31 | this.itemsTotal = this.galleryItems.length; 32 | 33 | this.intro(); 34 | } 35 | intro() { 36 | // create the timeline 37 | // let's start by animating the main intro text 38 | const timeline = gsap.timeline().to(this.DOM.title, { 39 | duration: 1, 40 | ease: 'expo', 41 | startAt: {y: '10%'}, 42 | y: '0%', 43 | opacity: 1 44 | }, 0); 45 | 46 | // now let's center the images (stack) 47 | for (const [pos, item] of this.galleryItems.entries()) { 48 | timeline.set(item.DOM.img, { 49 | x: winsize.width/2 - item.imgRect.left - item.imgRect.width/2, 50 | y: winsize.height/2 - item.imgRect.top - item.imgRect.height/2, 51 | scale: 0.6, 52 | rotation: getRandomInteger(-10,10), 53 | opacity: 1, 54 | delay: 0.2*pos 55 | }, 0); 56 | 57 | // for the first image, we set a high scale for the inner image element 58 | // later we will animate this scale value together with the scale value of the outer image 59 | if ( pos === 0 ) { 60 | timeline.set(item.DOM.imgInner, { 61 | scale: 1.8 62 | }, 0); 63 | } 64 | } 65 | 66 | // access the first and other images in the stack 67 | const [firstImage, ...otherImages] = this.galleryItems.map(el => el.DOM.img); 68 | 69 | timeline 70 | .addLabel('startAnimation', '+=0') 71 | // allow scroll and update the locomotive scroll 72 | .add(() => { 73 | document.body.classList.remove('noscroll'); 74 | scroll.update(); 75 | }, 'startAnimation') 76 | 77 | // animate the main title characters out and fade them out too 78 | .to(this.DOM.titleChars, { 79 | duration: 1, 80 | ease: 'expo', 81 | x: (pos, target) => { 82 | return -40*(Math.floor(this.titleCharsTotal/2)-pos); 83 | }, 84 | opacity: 0, 85 | stagger: {from: 'center'} 86 | }, 'startAnimation') 87 | 88 | // the other images in the stack will animate its translation values randomly 89 | .to(otherImages, { 90 | duration: 1, 91 | ease: 'power3', 92 | x: () => '+='+getRandomInteger(-200,200)+'%', 93 | y: () => '+='+getRandomInteger(-200,200)+'%', 94 | opacity: 0, 95 | rotation: () => getRandomInteger(-20,20) 96 | }, 'startAnimation') 97 | // and then we make them appear in their final position in the grid 98 | .to(otherImages, { 99 | duration: 0.5, 100 | ease: 'expo', 101 | startAt: { 102 | x: 0, 103 | y: 0, 104 | rotation: 0, 105 | scale: 0.8 106 | }, 107 | scale: 1, 108 | opacity: 1 109 | }) 110 | 111 | // the first image will now animate to it's final position 112 | .to(firstImage, { 113 | duration: 1.2, 114 | ease: 'expo', 115 | x: 0, 116 | y: 0, 117 | scale: 1, 118 | rotation: 0, 119 | opacity: 1 120 | }, 'startAnimation') 121 | // both the image and inner image animate the scale value to achieve the "reveal effect" 122 | .to(this.galleryItems[0].DOM.imgInner, { 123 | duration: 1.2, 124 | ease: 'expo', 125 | scale: 1 126 | }, 'startAnimation'); 127 | 128 | // finally, animate the gallery item's content elements (title, number and texts) 129 | for (const [pos, item] of this.galleryItems.entries()) { 130 | timeline 131 | .add( () => item.inStack = false, 'startAnimation+=1' ) 132 | .to(item.DOM.captionChars, { 133 | duration: 2, 134 | ease: 'expo', 135 | startAt: { 136 | opacity: 0, 137 | x: pos => -40*(Math.floor(item.captionCharsTotal/2)-pos) 138 | }, 139 | x: 0, 140 | opacity: 1, 141 | stagger: {from: 'center'} 142 | }, 'startAnimation') 143 | .to([item.DOM.caption.number,item.DOM.caption.texts], { 144 | duration: 1, 145 | ease: 'power1', 146 | startAt: {opacity: 0}, 147 | opacity: 1, 148 | stagger: 0.04 149 | }, 'startAnimation+=0.3') 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /src/js/demo1/galleryItem.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | 3 | export default class GalleryItem { 4 | constructor(el) { 5 | this.DOM = {el: el}; 6 | this.DOM.img = this.DOM.el.querySelector('.gallery__item-img'); 7 | this.DOM.imgInner = this.DOM.img.querySelector('.gallery__item-imginner'); 8 | this.DOM.caption = { 9 | title: this.DOM.el.querySelector('.gallery__item-title'), 10 | number: this.DOM.el.querySelector('.gallery__item-number'), 11 | texts: this.DOM.el.querySelectorAll('.gallery__item-text') 12 | }; 13 | this.DOM.captionChars = this.DOM.caption.title.querySelectorAll('.char'); 14 | this.captionCharsTotal = this.DOM.captionChars.length; 15 | this.imgRect = this.DOM.img.getBoundingClientRect(); 16 | // part of the stack 17 | this.inStack = true; 18 | this.initEvents(); 19 | } 20 | initEvents() { 21 | // on hover, scale in/out the image and inner image elements and also the caption titles 22 | this.onMouseEnterFn = () => { 23 | if ( this.inStack ) return false; 24 | gsap 25 | .timeline({defaults: {duration: 1, ease: 'expo'}}) 26 | .to(this.DOM.img, {scale: 0.95}) 27 | .to(this.DOM.imgInner, {scale: 1.2}, 0) 28 | .to(this.DOM.captionChars, { 29 | x: pos => -10*(Math.floor(this.captionCharsTotal/2)-pos), 30 | stagger: {from: 'center'} 31 | }, 0); 32 | }; 33 | this.onMouseLeaveFn = () => { 34 | if ( this.inStack ) return false; 35 | gsap 36 | .timeline({defaults: {duration: 1, ease: 'expo'}}) 37 | .to([this.DOM.img, this.DOM.imgInner], {scale: 1}) 38 | .to(this.DOM.captionChars, {x: 0}, 0); 39 | }; 40 | this.DOM.img.addEventListener('mouseenter', this.onMouseEnterFn); 41 | this.DOM.img.addEventListener('mouseleave', this.onMouseLeaveFn); 42 | } 43 | } -------------------------------------------------------------------------------- /src/js/demo1/index.js: -------------------------------------------------------------------------------- 1 | import { preloadImages, preloadFonts } from '../utils'; 2 | import Cursor from '../cursor'; 3 | import GalleryController from './galleryController'; 4 | 5 | // Preload images and fonts 6 | Promise.all([preloadImages('.gallery__item-imginner'), preloadFonts('lty4rfv')]).then(() => { 7 | // Remove loader (loading class) 8 | document.body.classList.remove('loading'); 9 | 10 | // Initialize custom cursor 11 | const cursor = new Cursor(document.querySelector('.cursor')); 12 | 13 | // Initialize the GalleryController 14 | new GalleryController(document.querySelector('.gallery')); 15 | 16 | // Mouse effects on all links and others 17 | [...document.querySelectorAll('a, .gallery__item-img')].forEach(link => { 18 | link.addEventListener('mouseenter', () => cursor.enter()); 19 | link.addEventListener('mouseleave', () => cursor.leave()); 20 | }); 21 | }); -------------------------------------------------------------------------------- /src/js/demo2/galleryController.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { calcWinsize, getRandomInteger } from '../utils'; 3 | import GalleryItem from './galleryItem'; 4 | import LocomotiveScroll from 'locomotive-scroll'; 5 | import Splitting from "splitting"; 6 | 7 | Splitting(); 8 | 9 | // Initialize the Locomotive scroll 10 | const scroll = new LocomotiveScroll({ 11 | el: document.querySelector('[data-scroll-container]'), 12 | smooth: true 13 | }); 14 | 15 | // Calculate the viewport size 16 | let winsize = calcWinsize(); 17 | window.addEventListener('resize', () => winsize = calcWinsize()); 18 | 19 | export default class GalleryController { 20 | constructor(galleryEl) { 21 | this.DOM = { 22 | galleryEl: galleryEl, 23 | title: document.querySelector('.content__title'), 24 | text: document.querySelector('.content__text'), 25 | }; 26 | this.DOM.galleryItemElems = [...this.DOM.galleryEl.querySelectorAll('.gallery__item')]; 27 | this.galleryItems = []; 28 | this.DOM.galleryItemElems.forEach(el => this.galleryItems.push(new GalleryItem(el))); 29 | this.itemsTotal = this.galleryItems.length; 30 | 31 | this.intro(); 32 | } 33 | intro() { 34 | for (const [pos, item] of this.galleryItems.entries()) { 35 | gsap.set(item.DOM.el, {zIndex: this.itemsTotal-1-pos}); 36 | } 37 | 38 | gsap.to(this.DOM.galleryEl, { 39 | duration: 1.2, 40 | ease: 'expo', 41 | startAt: {y: '4%'}, 42 | y: '0%' 43 | }); 44 | 45 | // access the first and other images in the stack 46 | const [firstImage,secondImage,thirdImage, ...otherImages] = this.galleryItems.map(el => el.DOM.img); 47 | this.galleryItems.reverse(); 48 | 49 | const timeline = gsap.timeline(); 50 | 51 | // first let's center the images 52 | for (const [pos, item] of this.galleryItems.entries()) { 53 | timeline.set(item.DOM.img, { 54 | x: winsize.width/2 - item.imgRect.left - item.imgRect.width/2, 55 | y: winsize.height/2 - item.imgRect.top - item.imgRect.height/2, 56 | scale: 0.6, 57 | rotation: getRandomInteger(-10,10), 58 | opacity: 1, 59 | delay: 0.1*pos 60 | }, 0); 61 | 62 | if ( pos >= this.itemsTotal-3 ) { 63 | timeline.set(item.DOM.imgInner, { 64 | scale: 1.8 65 | }, 0); 66 | } 67 | else { 68 | timeline.set(item.DOM.img, { 69 | opacity: 0, 70 | delay: 0.1*pos 71 | }, 0.3); 72 | } 73 | } 74 | 75 | timeline 76 | .addLabel('startAnimation', '+=0.1') 77 | .add(() => { 78 | document.body.classList.remove('noscroll'); 79 | scroll.update(); 80 | }, 'startAnimation') 81 | 82 | .to([firstImage,secondImage,thirdImage], { 83 | duration: 1.2, 84 | ease: 'expo', 85 | x: 0, 86 | y: 0, 87 | scale: 1, 88 | rotation: 0, 89 | opacity: 1 90 | }, 'startAnimation') 91 | .to(this.galleryItems.filter((_,pos) => pos >= this.itemsTotal-3).map(item => item.DOM.imgInner), { 92 | duration: 1.2, 93 | ease: 'expo', 94 | scale: 1 95 | }, 'startAnimation') 96 | 97 | .to(otherImages, { 98 | duration: 1.2, 99 | ease: 'expo', 100 | startAt: { 101 | x: 0, 102 | y: 0, 103 | rotation: 0, 104 | scale: 0.8 105 | }, 106 | scale: 1, 107 | opacity: 1 108 | }, 'startAnimation'); 109 | 110 | for (const item of this.galleryItems) { 111 | timeline 112 | .add( () => item.inStack = false, 'startAnimation+=1.2' ) 113 | .to(item.DOM.captionChars, { 114 | duration: 1, 115 | ease: 'expo', 116 | startAt: { 117 | opacity: 0, 118 | y: '40%' 119 | }, 120 | y: '0%', 121 | opacity: 1, 122 | stagger: 0.03 123 | }, 'startAnimation+=0.5') 124 | } 125 | 126 | timeline.to([this.DOM.title, this.DOM.text], { 127 | duration: 1.2, 128 | ease: 'expo', 129 | startAt: { 130 | y: '50%' 131 | }, 132 | y: '0%', 133 | opacity: 1, 134 | stagger: 0.04 135 | }, 'startAnimation+=0.2'); 136 | } 137 | } -------------------------------------------------------------------------------- /src/js/demo2/galleryItem.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | 3 | export default class GalleryItem { 4 | constructor(el) { 5 | this.DOM = {el: el}; 6 | this.DOM.img = this.DOM.el.querySelector('.gallery__item-img'); 7 | this.DOM.imgInner = this.DOM.img.querySelector('.gallery__item-imginner'); 8 | this.DOM.caption = { 9 | title: this.DOM.el.querySelector('.gallery__item-caption > .gallery__item-title'), 10 | }; 11 | this.DOM.captionChars = this.DOM.caption.title.querySelectorAll('.char'); 12 | this.captionCharsTotal = this.DOM.captionChars.length; 13 | this.imgRect = this.DOM.img.getBoundingClientRect(); 14 | this.captionRect = this.DOM.caption.title.getBoundingClientRect(); 15 | // part of the stack 16 | this.inStack = true; 17 | this.initEvents(); 18 | } 19 | initEvents() { 20 | this.onMouseEnterFn = () => { 21 | gsap.killTweensOf(this.DOM.captionChars); 22 | if ( this.inStack ) return false; 23 | gsap 24 | .timeline({defaults: {duration: 1, ease: 'expo'}}) 25 | .to(this.DOM.img, {scale: 0.95}) 26 | .to(this.DOM.imgInner, {scale: 1.2}, 0) 27 | .to(this.DOM.captionChars, { 28 | x: pos => this.imgRect.width-this.captionRect.width*1.1, 29 | stagger: -0.02 30 | }, 0); 31 | }; 32 | this.onMouseLeaveFn = () => { 33 | gsap.killTweensOf(this.DOM.captionChars); 34 | if ( this.inStack ) return false; 35 | gsap 36 | .timeline({defaults: {duration: 1, ease: 'expo'}}) 37 | .to([this.DOM.img, this.DOM.imgInner], {scale: 1}) 38 | .to(this.DOM.captionChars, {x: 0}, 0); 39 | }; 40 | this.DOM.img.addEventListener('mouseenter', this.onMouseEnterFn); 41 | this.DOM.img.addEventListener('mouseleave', this.onMouseLeaveFn); 42 | } 43 | } -------------------------------------------------------------------------------- /src/js/demo2/index.js: -------------------------------------------------------------------------------- 1 | import { preloadImages, preloadFonts } from '../utils'; 2 | import Cursor from '../cursor'; 3 | import GalleryController from './galleryController'; 4 | 5 | // Preload images and fonts 6 | Promise.all([preloadImages('.gallery__item-imginner'), preloadFonts('lty4rfv')]).then(() => { 7 | // Remove loader (loading class) 8 | document.body.classList.remove('loading'); 9 | 10 | // Initialize custom cursor 11 | const cursor = new Cursor(document.querySelector('.cursor')); 12 | 13 | // Initialize the GalleryController 14 | new GalleryController(document.querySelector('.gallery')); 15 | 16 | // Mouse effects on all links and others 17 | [...document.querySelectorAll('a, .gallery__item-img')].forEach(link => { 18 | link.addEventListener('mouseenter', () => cursor.enter()); 19 | link.addEventListener('mouseleave', () => cursor.leave()); 20 | }); 21 | }); -------------------------------------------------------------------------------- /src/js/utils.js: -------------------------------------------------------------------------------- 1 | // Map number x from range [a, b] to [c, d] 2 | const imagesLoaded = require('imagesloaded'); 3 | 4 | // Linear interpolation 5 | const lerp = (a, b, n) => (1 - n) * a + n * b; 6 | 7 | // Gets the mouse position 8 | const getMousePos = e => { 9 | return { 10 | x : e.clientX, 11 | y : e.clientY 12 | }; 13 | }; 14 | 15 | const calcWinsize = () => { 16 | return {width: window.innerWidth, height: window.innerHeight}; 17 | }; 18 | 19 | const getRandomInteger = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); 20 | 21 | // Preload images 22 | const preloadImages = (selector = 'img') => { 23 | return new Promise((resolve) => { 24 | imagesLoaded(document.querySelectorAll(selector), {background: true}, resolve); 25 | }); 26 | }; 27 | 28 | // Preload fonts 29 | const preloadFonts = (id) => { 30 | return new Promise((resolve) => { 31 | WebFont.load({ 32 | typekit: { 33 | id: id 34 | }, 35 | active: resolve 36 | }); 37 | }); 38 | }; 39 | 40 | export { 41 | lerp, 42 | getMousePos, 43 | calcWinsize, 44 | getRandomInteger, 45 | preloadImages, 46 | preloadFonts 47 | }; --------------------------------------------------------------------------------