├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── 1.dc197a9a.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 ├── demo2.44794d1a.js ├── demo3.b516845c.js ├── demo4.e22d7780.js ├── favicon.26242483.ico ├── index.html ├── index2.html ├── index3.html └── index4.html ├── index.html ├── package.json └── src ├── css └── base.css ├── favicon.ico ├── img ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg └── 9.jpg ├── index.html ├── index2.html ├── index3.html ├── index4.html └── js ├── demo1 ├── index.js └── slideshow.js ├── demo2 ├── index.js └── slideshow.js ├── demo3 ├── index.js └── slideshow.js ├── demo4 ├── index.js └── slideshow.js ├── navigation.js ├── slide.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 - 2021 [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 | # Shape Slideshow with Clip-path 2 | 3 | An experimental slideshow using clip-path to create shape transitions between slides. 4 | 5 | ![Shape Slideshow](https://tympanus.net/codrops/wp-content/uploads/2021/03/ShapeSlideshow02.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=53694) 8 | 9 | [Demo](http://tympanus.net/Development/ShapesSlideshow/) 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 from [Unsplash](https://unsplash.com/) 35 | 36 | ## Misc 37 | 38 | 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/) 39 | 40 | ## License 41 | [MIT](LICENSE) 42 | 43 | Made with :blue_heart: by [Codrops](http://www.codrops.com) 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /dist/1.dc197a9a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/1.dc197a9a.jpg -------------------------------------------------------------------------------- /dist/2.3ca6bb44.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/2.3ca6bb44.jpg -------------------------------------------------------------------------------- /dist/3.a433b89d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/3.a433b89d.jpg -------------------------------------------------------------------------------- /dist/4.c6d96be5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/4.c6d96be5.jpg -------------------------------------------------------------------------------- /dist/5.689b68fd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/5.689b68fd.jpg -------------------------------------------------------------------------------- /dist/6.e96dcfff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/6.e96dcfff.jpg -------------------------------------------------------------------------------- /dist/7.2d66e3ed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/7.2d66e3ed.jpg -------------------------------------------------------------------------------- /dist/8.f4323fe0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/8.f4323fe0.jpg -------------------------------------------------------------------------------- /dist/9.c9233dac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/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: 18px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #fff; 14 | --color-bg: #000; 15 | --color-link: #fff; 16 | --color-link-hover: #fff; 17 | --color-frame: #ff5ba4; 18 | color: var(--color-text); 19 | background-color: var(--color-bg); 20 | font-family: rucksack, sans-serif; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | /* Page Loader */ 26 | .js .loading::before, 27 | .js .loading::after { 28 | content: ''; 29 | position: fixed; 30 | z-index: 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 | position: relative; 66 | } 67 | 68 | a::after { 69 | content: ''; 70 | position: absolute; 71 | width: 50%; 72 | height: 1px; 73 | background: currentColor; 74 | left: 0; 75 | bottom: -25%; 76 | transform: scale3d(0,1,1); 77 | opacity: 0; 78 | transform-origin: 0% 50%; 79 | transition: transform 0.3s, opacity 0s 0.3s; 80 | } 81 | 82 | a:hover::after { 83 | opacity: 1; 84 | transition: transform 0.3s; 85 | transform: scale3d(1,1,1); 86 | } 87 | 88 | a:hover { 89 | color: var(--color-link-hover); 90 | outline: none; 91 | } 92 | 93 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */ 94 | a:focus { 95 | /* Provide a fallback style for browsers 96 | that don't support :focus-visible */ 97 | outline: none; 98 | background: lightgrey; 99 | } 100 | 101 | a:focus:not(:focus-visible) { 102 | /* Remove the focus indicator on mouse-focus for browsers 103 | that do support :focus-visible */ 104 | background: transparent; 105 | } 106 | 107 | a:focus-visible { 108 | /* Draw a very noticeable focus style for 109 | keyboard-focus on browsers that do support 110 | :focus-visible */ 111 | outline: 2px solid red; 112 | background: transparent; 113 | } 114 | 115 | .frame { 116 | padding: 3rem 5vw; 117 | text-align: center; 118 | position: relative; 119 | z-index: 1000; 120 | color: var(--color-frame); 121 | } 122 | 123 | .frame a { 124 | color: var(--color-frame); 125 | } 126 | 127 | .frame__title { 128 | font-size: 1.25rem; 129 | margin: 0 0 1rem; 130 | } 131 | 132 | .frame__title span { 133 | position: absolute; 134 | pointer-events: none; 135 | opacity: 0; 136 | } 137 | 138 | .frame__links { 139 | display: inline; 140 | } 141 | 142 | .frame__links a:not(:last-child), 143 | .frame__demos a:not(:last-child) { 144 | margin-right: 1rem; 145 | } 146 | 147 | .frame__demos { 148 | margin: 1rem 0; 149 | } 150 | 151 | a.frame__demo--current, 152 | a.frame__demo--current:hover { 153 | color: var(--color-text); 154 | } 155 | 156 | .slideshow { 157 | width: 100vw; 158 | height: calc(100vh - 13rem); 159 | position: relative; 160 | overflow: hidden; 161 | } 162 | 163 | .slide { 164 | margin: 0; 165 | } 166 | 167 | .slide, 168 | .slide__img-wrap, 169 | .slide__img { 170 | position: absolute; 171 | width: 100%; 172 | height: 100%; 173 | top: 0; 174 | left: 0; 175 | } 176 | 177 | .slide { 178 | display: flex; 179 | align-items: center; 180 | justify-content: center; 181 | } 182 | 183 | .js .slide { 184 | opacity: 0; 185 | pointer-events: none; 186 | } 187 | 188 | .js .slide--current { 189 | opacity: 1; 190 | pointer-events: auto; 191 | } 192 | 193 | .slide__img-wrap { 194 | will-change: transform; 195 | overflow: hidden; 196 | } 197 | 198 | .slide__img { 199 | background-size: cover; 200 | will-change: transform; 201 | -webkit-backspace-visibility: hidden; 202 | } 203 | 204 | .slide__caption { 205 | position: relative; 206 | padding: 0 10vw; 207 | cursor: default; 208 | width: 100%; 209 | } 210 | 211 | .slides__caption-headline { 212 | font-size: 6.5vw; 213 | font-size: clamp(2rem,6.5vw,6rem); 214 | line-height: 0.9; 215 | margin: 0; 216 | text-transform: uppercase; 217 | font-weight: normal; 218 | } 219 | 220 | .text-row { 221 | position: relative; 222 | overflow: hidden; 223 | display: block; 224 | white-space: nowrap; 225 | } 226 | 227 | .text-row > span { 228 | display: block; 229 | position: relative; 230 | padding: 0.5rem 0; 231 | } 232 | 233 | .slides__caption-headline { 234 | font-weight: 300; 235 | } 236 | 237 | .slides__caption-headline em, 238 | .slides__caption-headline strong { 239 | font-family: freight-big-pro, serif; 240 | font-size: 6.75vw; 241 | font-size: clamp(2rem,7vw,6rem); 242 | } 243 | 244 | .slides__caption-headline em { 245 | font-weight: 400; 246 | font-style: italic; 247 | } 248 | 249 | .slides__caption-headline strong { 250 | font-weight: 400; 251 | } 252 | 253 | .slides__caption-link { 254 | display: inline-block; 255 | margin-top: 1rem; 256 | text-indent: 0.2vw; 257 | font-size: 1.75rem; 258 | font-weight: 300; 259 | font-size: clamp(1rem, 5vw, 1.75rem); 260 | } 261 | 262 | .slides-nav { 263 | display: flex; 264 | align-items: center; 265 | justify-content: center; 266 | } 267 | 268 | .slides-nav__button { 269 | display: block; 270 | cursor: pointer; 271 | background: none; 272 | border: 0; 273 | width: 55px; 274 | height: 24px; 275 | padding: 0; 276 | margin: 0 0.75rem; 277 | -webkit-appearance: none; 278 | -moz-appearance: none; 279 | } 280 | 281 | .slides-nav__button:focus { 282 | outline: none; 283 | } 284 | 285 | .slides-nav svg { 286 | display: block; 287 | width: 100%; 288 | height: 100%; 289 | fill: #fff; 290 | } 291 | 292 | .slides-nav__index { 293 | margin-left: 2rem; 294 | white-space: nowrap; 295 | color: var(--color-text); 296 | border: 1px solid var(--color-text); 297 | border-radius: 50%; 298 | padding: 1rem 1.5rem; 299 | text-align: center; 300 | display: flex; 301 | align-items: center; 302 | } 303 | 304 | .slides-nav__index > span { 305 | width: 2rem; 306 | } 307 | 308 | .slides-nav__index-current { 309 | position: relative; 310 | overflow: hidden; 311 | } 312 | 313 | .slides-nav__index-current span { 314 | display: inline-block; 315 | } 316 | 317 | @media screen and (min-width: 53em) { 318 | .frame { 319 | position: fixed; 320 | text-align: left; 321 | z-index: 100; 322 | top: 0; 323 | left: 0; 324 | display: grid; 325 | align-content: space-between; 326 | width: 100%; 327 | max-width: none; 328 | height: 100vh; 329 | padding: 2rem 3.5rem; 330 | pointer-events: none; 331 | grid-template-columns: 25% 50% 25%; 332 | grid-template-rows: auto auto auto; 333 | grid-template-areas: 'title links info' 334 | '... ... ...' 335 | 'demos demos nav'; 336 | } 337 | .frame__title { 338 | margin: 0; 339 | grid-area: title; 340 | } 341 | .frame__demos { 342 | margin: 0; 343 | grid-area: demos; 344 | justify-self: start; 345 | align-self: end; 346 | } 347 | .frame__links { 348 | grid-area: links; 349 | padding: 0; 350 | justify-self: center; 351 | } 352 | .frame__info { 353 | grid-area: info; 354 | justify-self: end; 355 | align-self: start; 356 | } 357 | .slides-nav { 358 | grid-area: nav; 359 | justify-self: end; 360 | align-self: end; 361 | } 362 | .frame a, 363 | .frame button { 364 | pointer-events: auto; 365 | } 366 | .slideshow { 367 | height: 100vh; 368 | } 369 | .slides-nav { 370 | margin-bottom: -1rem; 371 | } 372 | .slides-nav__index { 373 | margin-right: -2rem; 374 | } 375 | .slideshow { 376 | background: var(--color-bg); 377 | } 378 | 379 | } 380 | -------------------------------------------------------------------------------- /dist/favicon.26242483.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/dist/favicon.26242483.ico -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 |
25 |
26 |

Mooz

27 | 32 | More demos 33 | 39 | 54 |
55 |
56 |
57 |
58 |
59 |

60 | Far from Venice 61 | sunset in her gaze 62 |

63 | Explore 64 |
65 |
66 |
67 |
68 |
69 |

70 | Temptation 71 | a desire to engage 72 |

73 | Explore 74 |
75 |
76 |
77 |
78 |
79 |

80 | Somebody's game 81 | in ancient dreams 82 |

83 | Explore 84 |
85 |
86 |
87 |
88 |
89 |

90 | Heartful acts 91 | when passion calls 92 |

93 | Explore 94 |
95 |
96 |
97 |
98 |
99 |

100 | High freedom 101 | when tears are gone 102 |

103 | Explore 104 |
105 |
106 |
107 |
108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /dist/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 |
25 |
26 |

Mooz Shape Slideshow

27 | 32 | More demos 33 | 39 | 54 |
55 |
56 |
57 |
58 |
59 |

60 | Temptation 61 | a desire to engage 62 |

63 | Explore 64 |
65 |
66 |
67 |
68 |
69 |

70 | Heartful acts 71 | when passion calls 72 |

73 | Explore 74 |
75 |
76 |
77 |
78 |
79 |

80 | Somebody's game 81 | in ancient dreams 82 |

83 | Explore 84 |
85 |
86 |
87 |
88 |
89 |

90 | Far from Venice 91 | sunset in her gaze 92 |

93 | Explore 94 |
95 |
96 |
97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /dist/index3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 3 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 |
25 |
26 |

Mooz Shape Slideshow

27 | 32 | More demos 33 | 39 | 54 |
55 |
56 |
57 |
58 |
59 |

60 | Temptation 61 | a desire to engage 62 |

63 | Explore 64 |
65 |
66 |
67 |
68 |
69 |

70 | Heartful acts 71 | when passion calls 72 |

73 | Explore 74 |
75 |
76 |
77 |
78 |
79 |

80 | Somebody's game 81 | in ancient dreams 82 |

83 | Explore 84 |
85 |
86 |
87 |
88 |
89 |

90 | Far from Venice 91 | sunset in her gaze 92 |

93 | Explore 94 |
95 |
96 |
97 |
98 |
99 |

100 | Endless night 101 | one shepherd gone 102 |

103 | Explore 104 |
105 |
106 |
107 |
108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /dist/index4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 4 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 |
25 |
26 |

Mooz Shape Slideshow

27 | 32 | More demos 33 | 39 | 54 |
55 |
56 |
57 |
58 |
59 |

60 | Temptation 61 | a desire to engage 62 |

63 | Explore 64 |
65 |
66 |
67 |
68 |
69 |

70 | Heartful acts 71 | when passion calls 72 |

73 | Explore 74 |
75 |
76 |
77 |
78 |
79 |

80 | Somebody's game 81 | in ancient dreams 82 |

83 | Explore 84 |
85 |
86 |
87 |
88 |
89 |

90 | Far from Venice 91 | sunset in her gaze 92 |

93 | Explore 94 |
95 |
96 |
97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |

Mooz

20 | 25 | 26 | 30 | 38 |
39 |
40 |
41 |
42 |
43 |

44 | Temptation, 45 | a desire to engage 46 |

47 | Explore 48 |
49 |
50 |
51 |
52 |
53 |

54 | Heartful 55 | when hiraeth begins 56 |

57 | Explore 58 |
59 |
60 |
61 |
62 |
63 |

64 | Somnambulist 65 | Whenever you dream 66 |

67 | Explore 68 |
69 |
70 |
71 |
72 |
73 |

74 | Supernova 75 | for she resurrected 76 |

77 | Explore 78 |
79 |
80 |
81 |
82 |
83 |

84 | Heartfelt 85 | in good company 86 |

87 | Explore 88 |
89 |
90 |
91 |
92 | 93 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ShapesSlideshow", 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/[NAME].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.6.0", 25 | "imagesloaded": "^4.1.4", 26 | "parcel-bundler": "1.12.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 18px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #fff; 14 | --color-bg: #000; 15 | --color-link: #fff; 16 | --color-link-hover: #fff; 17 | --color-frame: #ff5ba4; 18 | color: var(--color-text); 19 | background-color: var(--color-bg); 20 | font-family: rucksack, sans-serif; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | /* Page Loader */ 26 | .js .loading::before, 27 | .js .loading::after { 28 | content: ''; 29 | position: fixed; 30 | z-index: 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 | position: relative; 66 | } 67 | 68 | a::after { 69 | content: ''; 70 | position: absolute; 71 | width: 50%; 72 | height: 1px; 73 | background: currentColor; 74 | left: 0; 75 | bottom: -25%; 76 | transform: scale3d(0,1,1); 77 | opacity: 0; 78 | transform-origin: 0% 50%; 79 | transition: transform 0.3s, opacity 0s 0.3s; 80 | } 81 | 82 | a:hover::after { 83 | opacity: 1; 84 | transition: transform 0.3s; 85 | transform: scale3d(1,1,1); 86 | } 87 | 88 | a:hover { 89 | color: var(--color-link-hover); 90 | outline: none; 91 | } 92 | 93 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */ 94 | a:focus { 95 | /* Provide a fallback style for browsers 96 | that don't support :focus-visible */ 97 | outline: none; 98 | background: lightgrey; 99 | } 100 | 101 | a:focus:not(:focus-visible) { 102 | /* Remove the focus indicator on mouse-focus for browsers 103 | that do support :focus-visible */ 104 | background: transparent; 105 | } 106 | 107 | a:focus-visible { 108 | /* Draw a very noticeable focus style for 109 | keyboard-focus on browsers that do support 110 | :focus-visible */ 111 | outline: 2px solid red; 112 | background: transparent; 113 | } 114 | 115 | .frame { 116 | padding: 3rem 5vw; 117 | text-align: center; 118 | position: relative; 119 | z-index: 1000; 120 | color: var(--color-frame); 121 | } 122 | 123 | .frame a { 124 | color: var(--color-frame); 125 | } 126 | 127 | .frame__title { 128 | font-size: 1.25rem; 129 | margin: 0 0 1rem; 130 | } 131 | 132 | .frame__title span { 133 | position: absolute; 134 | pointer-events: none; 135 | opacity: 0; 136 | } 137 | 138 | .frame__links { 139 | display: inline; 140 | } 141 | 142 | .frame__links a:not(:last-child), 143 | .frame__demos a:not(:last-child) { 144 | margin-right: 1rem; 145 | } 146 | 147 | .frame__demos { 148 | margin: 1rem 0; 149 | } 150 | 151 | a.frame__demo--current, 152 | a.frame__demo--current:hover { 153 | color: var(--color-text); 154 | } 155 | 156 | .slideshow { 157 | width: 100vw; 158 | height: calc(100vh - 13rem); 159 | position: relative; 160 | overflow: hidden; 161 | } 162 | 163 | .slide { 164 | margin: 0; 165 | } 166 | 167 | .slide, 168 | .slide__img-wrap, 169 | .slide__img { 170 | position: absolute; 171 | width: 100%; 172 | height: 100%; 173 | top: 0; 174 | left: 0; 175 | } 176 | 177 | .slide { 178 | display: flex; 179 | align-items: center; 180 | justify-content: center; 181 | } 182 | 183 | .js .slide { 184 | opacity: 0; 185 | pointer-events: none; 186 | } 187 | 188 | .js .slide--current { 189 | opacity: 1; 190 | pointer-events: auto; 191 | } 192 | 193 | .slide__img-wrap { 194 | will-change: transform; 195 | overflow: hidden; 196 | } 197 | 198 | .slide__img { 199 | background-size: cover; 200 | will-change: transform; 201 | -webkit-backspace-visibility: hidden; 202 | } 203 | 204 | .slide__caption { 205 | position: relative; 206 | padding: 0 10vw; 207 | cursor: default; 208 | width: 100%; 209 | } 210 | 211 | .slides__caption-headline { 212 | font-size: 6.5vw; 213 | font-size: clamp(2rem,6.5vw,6rem); 214 | line-height: 0.9; 215 | margin: 0; 216 | text-transform: uppercase; 217 | font-weight: normal; 218 | } 219 | 220 | .text-row { 221 | position: relative; 222 | overflow: hidden; 223 | display: block; 224 | white-space: nowrap; 225 | } 226 | 227 | .text-row > span { 228 | display: block; 229 | position: relative; 230 | padding: 0.5rem 0; 231 | } 232 | 233 | .slides__caption-headline { 234 | font-weight: 300; 235 | } 236 | 237 | .slides__caption-headline em, 238 | .slides__caption-headline strong { 239 | font-family: freight-big-pro, serif; 240 | font-size: 6.75vw; 241 | font-size: clamp(2rem,7vw,6rem); 242 | } 243 | 244 | .slides__caption-headline em { 245 | font-weight: 400; 246 | font-style: italic; 247 | } 248 | 249 | .slides__caption-headline strong { 250 | font-weight: 400; 251 | } 252 | 253 | .slides__caption-link { 254 | display: inline-block; 255 | margin-top: 1rem; 256 | text-indent: 0.2vw; 257 | font-size: 1.75rem; 258 | font-weight: 300; 259 | font-size: clamp(1rem, 5vw, 1.75rem); 260 | } 261 | 262 | .slides-nav { 263 | display: flex; 264 | align-items: center; 265 | justify-content: center; 266 | } 267 | 268 | .slides-nav__button { 269 | display: block; 270 | cursor: pointer; 271 | background: none; 272 | border: 0; 273 | width: 55px; 274 | height: 24px; 275 | padding: 0; 276 | margin: 0 0.75rem; 277 | -webkit-appearance: none; 278 | -moz-appearance: none; 279 | } 280 | 281 | .slides-nav__button:focus { 282 | outline: none; 283 | } 284 | 285 | .slides-nav svg { 286 | display: block; 287 | width: 100%; 288 | height: 100%; 289 | fill: #fff; 290 | } 291 | 292 | .slides-nav__index { 293 | margin-left: 2rem; 294 | white-space: nowrap; 295 | color: var(--color-text); 296 | border: 1px solid var(--color-text); 297 | border-radius: 50%; 298 | padding: 1rem 1.5rem; 299 | text-align: center; 300 | display: flex; 301 | align-items: center; 302 | } 303 | 304 | .slides-nav__index > span { 305 | width: 2rem; 306 | } 307 | 308 | .slides-nav__index-current { 309 | position: relative; 310 | overflow: hidden; 311 | } 312 | 313 | .slides-nav__index-current span { 314 | display: inline-block; 315 | } 316 | 317 | @media screen and (min-width: 53em) { 318 | .frame { 319 | position: fixed; 320 | text-align: left; 321 | z-index: 100; 322 | top: 0; 323 | left: 0; 324 | display: grid; 325 | align-content: space-between; 326 | width: 100%; 327 | max-width: none; 328 | height: 100vh; 329 | padding: 2rem 3.5rem; 330 | pointer-events: none; 331 | grid-template-columns: 25% 50% 25%; 332 | grid-template-rows: auto auto auto; 333 | grid-template-areas: 'title links info' 334 | '... ... ...' 335 | 'demos demos nav'; 336 | } 337 | .frame__title { 338 | margin: 0; 339 | grid-area: title; 340 | } 341 | .frame__demos { 342 | margin: 0; 343 | grid-area: demos; 344 | justify-self: start; 345 | align-self: end; 346 | } 347 | .frame__links { 348 | grid-area: links; 349 | padding: 0; 350 | justify-self: center; 351 | } 352 | .frame__info { 353 | grid-area: info; 354 | justify-self: end; 355 | align-self: start; 356 | } 357 | .slides-nav { 358 | grid-area: nav; 359 | justify-self: end; 360 | align-self: end; 361 | } 362 | .frame a, 363 | .frame button { 364 | pointer-events: auto; 365 | } 366 | .slideshow { 367 | height: 100vh; 368 | } 369 | .slides-nav { 370 | margin-bottom: -1rem; 371 | } 372 | .slides-nav__index { 373 | margin-right: -2rem; 374 | } 375 | .slideshow { 376 | background: var(--color-bg); 377 | } 378 | 379 | } 380 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/favicon.ico -------------------------------------------------------------------------------- /src/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/1.jpg -------------------------------------------------------------------------------- /src/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/2.jpg -------------------------------------------------------------------------------- /src/img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/3.jpg -------------------------------------------------------------------------------- /src/img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/4.jpg -------------------------------------------------------------------------------- /src/img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/5.jpg -------------------------------------------------------------------------------- /src/img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/6.jpg -------------------------------------------------------------------------------- /src/img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/7.jpg -------------------------------------------------------------------------------- /src/img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/8.jpg -------------------------------------------------------------------------------- /src/img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/ShapesSlideshow/793c6d005a897e4545a33bbd9de4cb4b013117ef/src/img/9.jpg -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Mooz

19 | 24 | More demos 25 | 31 | 46 |
47 |
48 |
49 |
50 |
51 |

52 | Far from Venice 53 | sunset in her gaze 54 |

55 | Explore 56 |
57 |
58 |
59 |
60 |
61 |

62 | Temptation 63 | a desire to engage 64 |

65 | Explore 66 |
67 |
68 |
69 |
70 |
71 |

72 | Somebody's game 73 | in ancient dreams 74 |

75 | Explore 76 |
77 |
78 |
79 |
80 |
81 |

82 | Heartful acts 83 | when passion calls 84 |

85 | Explore 86 |
87 |
88 |
89 |
90 |
91 |

92 | High freedom 93 | when tears are gone 94 |

95 | Explore 96 |
97 |
98 |
99 |
100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Mooz Shape Slideshow

19 | 24 | More demos 25 | 31 | 46 |
47 |
48 |
49 |
50 |
51 |

52 | Temptation 53 | a desire to engage 54 |

55 | Explore 56 |
57 |
58 |
59 |
60 |
61 |

62 | Heartful acts 63 | when passion calls 64 |

65 | Explore 66 |
67 |
68 |
69 |
70 |
71 |

72 | Somebody's game 73 | in ancient dreams 74 |

75 | Explore 76 |
77 |
78 |
79 |
80 |
81 |

82 | Far from Venice 83 | sunset in her gaze 84 |

85 | Explore 86 |
87 |
88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/index3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 3 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Mooz Shape Slideshow

19 | 24 | More demos 25 | 31 | 46 |
47 |
48 |
49 |
50 |
51 |

52 | Temptation 53 | a desire to engage 54 |

55 | Explore 56 |
57 |
58 |
59 |
60 |
61 |

62 | Heartful acts 63 | when passion calls 64 |

65 | Explore 66 |
67 |
68 |
69 |
70 |
71 |

72 | Somebody's game 73 | in ancient dreams 74 |

75 | Explore 76 |
77 |
78 |
79 |
80 |
81 |

82 | Far from Venice 83 | sunset in her gaze 84 |

85 | Explore 86 |
87 |
88 |
89 |
90 |
91 |

92 | Endless night 93 | one shepherd gone 94 |

95 | Explore 96 |
97 |
98 |
99 |
100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/index4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shapes Slideshow | Demo 4 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Mooz Shape Slideshow

19 | 24 | More demos 25 | 31 | 46 |
47 |
48 |
49 |
50 |
51 |

52 | Temptation 53 | a desire to engage 54 |

55 | Explore 56 |
57 |
58 |
59 |
60 |
61 |

62 | Heartful acts 63 | when passion calls 64 |

65 | Explore 66 |
67 |
68 |
69 |
70 |
71 |

72 | Somebody's game 73 | in ancient dreams 74 |

75 | Explore 76 |
77 |
78 |
79 |
80 |
81 |

82 | Far from Venice 83 | sunset in her gaze 84 |

85 | Explore 86 |
87 |
88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/js/demo1/index.js: -------------------------------------------------------------------------------- 1 | import { preloadImages } from '../utils'; 2 | import { Navigation } from '../navigation'; 3 | import { Slideshow } from './slideshow'; 4 | 5 | // Preload all images 6 | preloadImages('.slide__img').then(() => { 7 | // remove loader (loading class) 8 | document.body.classList.remove('loading'); 9 | // initialize the slideshow and navigation 10 | const slideshow = new Slideshow(document.querySelector('.slideshow')); 11 | const navigation = new Navigation(document.querySelector('.slides-nav')); 12 | // navigation events 13 | navigation.DOM.ctrls.next.addEventListener('click', () => slideshow.next()); 14 | navigation.DOM.ctrls.prev.addEventListener('click', () => slideshow.prev()); 15 | // set the initial navigation current slide value 16 | navigation.updateCurrent(slideshow.current); 17 | // set the navigation total number of slides 18 | navigation.DOM.total.innerHTML = slideshow.current < 10 ? `0${slideshow.slidesTotal}` : slideshow.slidesTotal; 19 | // when a new slide is shown, update the navigation current slide value 20 | slideshow.on('updateCurrent', position => navigation.updateCurrent(position)); 21 | }); -------------------------------------------------------------------------------- /src/js/demo1/slideshow.js: -------------------------------------------------------------------------------- 1 | import { Slide } from '../slide'; 2 | import { EventEmitter } from 'events'; 3 | import { gsap } from 'gsap'; 4 | 5 | export class Slideshow extends EventEmitter { 6 | constructor(el) { 7 | super(); 8 | // the main wrapper
9 | this.DOM = {el}; 10 | // the slides 11 | this.DOM.slides = [...this.DOM.el.querySelectorAll('.slide')]; 12 | // array of Slide obj instances 13 | this.slides = []; 14 | this.DOM.slides.forEach(slide => this.slides.push(new Slide(slide))); 15 | // total number of Slides 16 | this.slidesTotal = this.slides.length; 17 | // current position 18 | this.current = 0; 19 | // some settings, like the clip paths 20 | this.config = { 21 | clipPath: { 22 | initial: 'circle(55% at 70% 50%)', 23 | final: 'circle(15% at 70% 50%)', 24 | hover: 'circle(20% at 30% 50%)' 25 | } 26 | }; 27 | this.init(); 28 | } 29 | init() { 30 | // start with the first slide as the current slide 31 | this.DOM.slides[this.current].classList.add('slide--current'); 32 | // set the initial clip path 33 | gsap.set(this.slides[this.current].DOM.imgWrap, {clipPath: this.config.clipPath.initial}); 34 | // when hovering over the "explore" link on each slide, we animate the clip path from this.config.clipPath.initial to this.config.clipPath.hover 35 | for (const slide of this.slides) { 36 | slide.DOM.link.addEventListener('mouseenter', () => { 37 | gsap.killTweensOf(slide.DOM.imgWrap); 38 | gsap.to(slide.DOM.imgWrap, { 39 | duration: 1, 40 | ease: 'expo', 41 | clipPath: this.config.clipPath.hover 42 | }); 43 | }); 44 | slide.DOM.link.addEventListener('mouseleave', () => { 45 | gsap.killTweensOf(slide.DOM.imgWrap); 46 | gsap.to(slide.DOM.imgWrap, { 47 | duration: 1, 48 | ease: 'expo', 49 | clipPath: this.config.clipPath.initial 50 | }); 51 | }); 52 | } 53 | } 54 | // navigate to the next slide 55 | next() { 56 | this.navigate('next'); 57 | } 58 | // navigate to the previous slide 59 | prev() { 60 | this.navigate('prev'); 61 | } 62 | navigate(direction) { 63 | // if animating do nothing 64 | if ( this.isAnimating ) { 65 | return false; 66 | } 67 | this.isAnimating = true; 68 | // get the current slide 69 | const currentSlide = this.slides[this.current]; 70 | // update current 71 | if ( direction === 'next' ) { 72 | this.current = this.current < this.slidesTotal-1 ? this.current+1 : 0; 73 | } 74 | else { 75 | this.current = this.current > 0 ? this.current-1 : this.slidesTotal-1; 76 | } 77 | // now get the upcoming slide 78 | const upcomingSlide = this.slides[this.current]; 79 | 80 | // animate things... 81 | gsap 82 | .timeline({ 83 | // add class current to the upcoming slide (pointer events related) 84 | onStart: () => upcomingSlide.DOM.el.classList.add('slide--current'), 85 | // and remove that class from the currentSlide when the animation ends 86 | onComplete: () => { 87 | this.isAnimating = false; 88 | currentSlide.DOM.el.classList.remove('slide--current'); 89 | } 90 | }) 91 | .addLabel('start', 0) 92 | // set the initial styles for the upcoming slide imgWrap: clip path and translateY position 93 | .set(upcomingSlide.DOM.imgWrap, { 94 | y: direction === 'next' ? '100%' : '-100%', 95 | clipPath: this.config.clipPath.final 96 | }, 'start') 97 | // also set the opacity of the upcoming slide to 1 98 | .set(upcomingSlide.DOM.el, {opacity: 1}, 'start') 99 | // set the initial styles for the upcoming slide img: translateY position 100 | // same for the texts and link elements 101 | .set(upcomingSlide.DOM.img, {y: direction === 'next' ? '-50%' : '50%'}, 'start') 102 | .set(upcomingSlide.DOM.text, {y: direction === 'next' ? '100%' : '-100%'}, 'start') 103 | .set(upcomingSlide.DOM.link, {opacity: 0}, 'start') 104 | // animate the clip path from this.config.clipPath.initial to this.config.clipPath.final 105 | .to(currentSlide.DOM.imgWrap, { 106 | duration: 1, 107 | ease: 'power3', 108 | clipPath: this.config.clipPath.final, 109 | rotation: 0.001 // bugfix 110 | }, 'start') 111 | // animate the current slide texts out 112 | .to(currentSlide.DOM.text, { 113 | duration: 1, 114 | ease: 'power3', 115 | y: direction === 'next' ? '-100%' : '100%', 116 | }, 'start') 117 | // animate the current slide link out 118 | .to(currentSlide.DOM.link, { 119 | duration: 0.5, 120 | ease: 'power3', 121 | opacity: 0 122 | }, 'start') 123 | // move the current slide away 124 | .to(currentSlide.DOM.imgWrap, { 125 | duration: 1, 126 | ease: 'power2.inOut', 127 | y: direction === 'next' ? '-100%' : '100%', 128 | rotation: 0.001 129 | }, 'start+=0.6') 130 | .to(currentSlide.DOM.img, { 131 | duration: 1, 132 | ease: 'power2.inOut', 133 | y: direction === 'next' ? '50%' : '-50%' 134 | }, 'start+=0.6') 135 | // and the upcoming slide in 136 | .to(upcomingSlide.DOM.imgWrap, { 137 | duration: 1, 138 | ease: 'power2.inOut', 139 | y: '0%', 140 | rotation: 0.001 141 | }, 'start+=0.6') 142 | .to(upcomingSlide.DOM.img, { 143 | duration: 1, 144 | ease: 'power2.inOut', 145 | y: '0%' 146 | }, 'start+=0.6') 147 | // animate the upcoming slide clip path to the initial shape 148 | .to(upcomingSlide.DOM.imgWrap, { 149 | duration: 1.5, 150 | ease: 'expo.inOut', 151 | clipPath: this.config.clipPath.initial 152 | }, 'start+=1.2') 153 | // animate the upcoming slide texts in 154 | .to(upcomingSlide.DOM.text, { 155 | duration: 1.5, 156 | ease: 'expo.inOut', 157 | y: '0%', 158 | rotation: 0.001, 159 | stagger: direction === 'next' ? 0.1 : -0.1 160 | }, 'start+=1.1') 161 | // animate the upcoming slide link in 162 | .to(upcomingSlide.DOM.link, { 163 | duration: 1, 164 | ease: 'expo.in', 165 | opacity: 1 166 | }, 'start+=1.4') 167 | 168 | // update the slideshow current value 169 | this.emit('updateCurrent', this.current); 170 | } 171 | } -------------------------------------------------------------------------------- /src/js/demo2/index.js: -------------------------------------------------------------------------------- 1 | import { preloadImages } from '../utils'; 2 | import { Navigation } from '../navigation'; 3 | import { Slideshow } from './slideshow'; 4 | 5 | // Preload all images 6 | preloadImages('.slide__img').then(() => { 7 | // remove loader (loading class) 8 | document.body.classList.remove('loading'); 9 | 10 | const slideshow = new Slideshow(document.querySelector('.slideshow')); 11 | const navigation = new Navigation(document.querySelector('.slides-nav')); 12 | // navigation events 13 | navigation.DOM.ctrls.next.addEventListener('click', () => slideshow.next()); 14 | navigation.DOM.ctrls.prev.addEventListener('click', () => slideshow.prev()); 15 | // set the initial navigation current slide value 16 | navigation.updateCurrent(slideshow.current); 17 | // set the navigation total number of slides 18 | navigation.DOM.total.innerHTML = slideshow.current < 10 ? `0${slideshow.slidesTotal}` : slideshow.slidesTotal; 19 | // when a new slide is shown, update the navigation current slide value 20 | slideshow.on('updateCurrent', position => navigation.updateCurrent(position)); 21 | }); -------------------------------------------------------------------------------- /src/js/demo2/slideshow.js: -------------------------------------------------------------------------------- 1 | import { Slide } from '../slide'; 2 | import { EventEmitter } from 'events'; 3 | import { gsap } from 'gsap'; 4 | 5 | export class Slideshow extends EventEmitter { 6 | constructor(el) { 7 | super(); 8 | this.DOM = {el}; 9 | 10 | this.DOM.slides = [...this.DOM.el.querySelectorAll('.slide')]; 11 | this.slides = []; 12 | this.DOM.slides.forEach(slide => this.slides.push(new Slide(slide))); 13 | this.slidesTotal = this.slides.length; 14 | 15 | this.current = 0; 16 | 17 | this.config = { 18 | clipPath: { 19 | initial: 'inset(10% 30.1% 0% 30% round 25vw 25vw 0vw 0vw)', //two equal values cause problems because browser uses shorthand 20 | final: 'inset(15% 40.1% 15% 40% round 25vw 25vw 25.01vw 25.01vw)', 21 | hover: 'inset(20% 40% 20.01% 40.01% round 25vw 25vw 25.01vw 25.01vw)' 22 | } 23 | }; 24 | 25 | this.init(); 26 | } 27 | init() { 28 | this.DOM.slides[this.current].classList.add('slide--current'); 29 | gsap.set(this.slides[this.current].DOM.imgWrap, {clipPath: this.config.clipPath.initial}); 30 | 31 | for (const slide of this.slides) { 32 | slide.DOM.link.addEventListener('mouseenter', () => { 33 | gsap.killTweensOf(slide.DOM.imgWrap); 34 | gsap.to(slide.DOM.imgWrap, { 35 | duration: 0.7, 36 | ease: 'expo', 37 | clipPath: this.config.clipPath.hover 38 | }); 39 | }); 40 | slide.DOM.link.addEventListener('mouseleave', () => { 41 | gsap.killTweensOf(slide.DOM.imgWrap); 42 | gsap.to(slide.DOM.imgWrap, { 43 | duration: 0.7, 44 | ease: 'expo', 45 | clipPath: this.config.clipPath.initial 46 | }); 47 | }); 48 | } 49 | } 50 | next() { 51 | this.navigate('next'); 52 | } 53 | prev() { 54 | this.navigate('prev'); 55 | } 56 | navigate(direction) { 57 | if ( this.isAnimating ) { 58 | return false; 59 | } 60 | this.isAnimating = true; 61 | 62 | const currentSlide = this.slides[this.current]; 63 | // update current 64 | if ( direction === 'next' ) { 65 | this.current = this.current < this.slidesTotal-1 ? this.current+1 : 0; 66 | } 67 | else { 68 | this.current = this.current > 0 ? this.current-1 : this.slidesTotal-1; 69 | } 70 | const upcomingSlide = this.slides[this.current]; 71 | 72 | gsap 73 | .timeline({ 74 | onStart: () => upcomingSlide.DOM.el.classList.add('slide--current'), 75 | onComplete: () => { 76 | this.isAnimating = false; 77 | currentSlide.DOM.el.classList.remove('slide--current'); 78 | } 79 | }) 80 | .addLabel('start', 0) 81 | .set(upcomingSlide.DOM.imgWrap, { 82 | //x: direction === 'next' ? '100%' : '-100%', 83 | y: direction === 'next' ? '-100%' : '100%', 84 | clipPath: this.config.clipPath.final 85 | }, 'start') 86 | .set(upcomingSlide.DOM.el, {opacity: 1}, 'start') 87 | .set(upcomingSlide.DOM.img, { 88 | //x: direction === 'next' ? '-100%' : '100%', 89 | y: direction === 'next' ? '100%' : '-100%' 90 | }, 'start') 91 | .set(upcomingSlide.DOM.text, {y: direction === 'next' ? '100%' : '-100%'}, 'start') 92 | .set(upcomingSlide.DOM.link, {opacity: 0}, 'start') 93 | // animate the shape from initial to its final state (clip-path) 94 | .to(currentSlide.DOM.imgWrap, { 95 | duration: 1, 96 | ease: 'expo', 97 | clipPath: this.config.clipPath.final, 98 | rotation: 0.001 99 | }, 'start') 100 | // animate the current slide texts out 101 | .to(currentSlide.DOM.text, { 102 | duration: 1, 103 | ease: 'power3', 104 | y: direction === 'next' ? '-100%' : '100%' 105 | }, 'start') 106 | // animate the current slide link out 107 | .to(currentSlide.DOM.link, { 108 | duration: 0.5, 109 | ease: 'power3', 110 | opacity: 0 111 | }, 'start') 112 | // move the current slide out (img and imgWrap) 113 | .to(currentSlide.DOM.imgWrap, { 114 | duration: 0.7, 115 | ease: 'expo.inOut', 116 | //x: direction === 'next' ? '-100%' : '100%', 117 | y: direction === 'next' ? '100%' : '-100%', 118 | rotation: 0.001 119 | }, 'start+=0.4') 120 | .to(currentSlide.DOM.img, { 121 | duration: 0.7, 122 | ease: 'expo.inOut', 123 | //x: direction === 'next' ? '100%' : '-100%', 124 | y: direction === 'next' ? '-100%' : '100%' 125 | }, 'start+=0.4') 126 | // and the upcoming slide in 127 | .to(upcomingSlide.DOM.imgWrap, { 128 | duration: 0.7, 129 | ease: 'expo', 130 | //x: '0%', 131 | y: '0%', 132 | rotation: 0.001 133 | }, 'start+=0.7') 134 | .to(upcomingSlide.DOM.img, { 135 | duration: 0.7, 136 | ease: 'expo', 137 | //x: '0%', 138 | y: '0%' 139 | }, 'start+=0.7') 140 | // animate the upcoming slide shape to initial 141 | .to(upcomingSlide.DOM.imgWrap, { 142 | duration: 1, 143 | ease: 'expo.inOut', 144 | clipPath: this.config.clipPath.initial 145 | }, 'start+=0.9') 146 | // animate the upcoming slide texts in 147 | .to(upcomingSlide.DOM.text, { 148 | duration: 1, 149 | ease: 'power3.inOut', 150 | rotation: 0.001, 151 | y: '0%', 152 | stagger: direction === 'next' ? 0.1 : -0.1 153 | }, 'start+=0.8') 154 | // animate the upcoming slide link in 155 | .to(upcomingSlide.DOM.link, { 156 | duration: 0.2, 157 | ease: 'power3.in', 158 | opacity: 1 159 | }, 'start+=1.3') 160 | 161 | 162 | this.emit('updateCurrent', this.current); 163 | } 164 | } -------------------------------------------------------------------------------- /src/js/demo3/index.js: -------------------------------------------------------------------------------- 1 | import { preloadImages } from '../utils'; 2 | import { Navigation } from '../navigation'; 3 | import { Slideshow } from './slideshow'; 4 | 5 | // Preload all images 6 | preloadImages('.slide__img').then(() => { 7 | // remove loader (loading class) 8 | document.body.classList.remove('loading'); 9 | 10 | const slideshow = new Slideshow(document.querySelector('.slideshow')); 11 | const navigation = new Navigation(document.querySelector('.slides-nav')); 12 | // navigation events 13 | navigation.DOM.ctrls.next.addEventListener('click', () => slideshow.next()); 14 | navigation.DOM.ctrls.prev.addEventListener('click', () => slideshow.prev()); 15 | // set the initial navigation current slide value 16 | navigation.updateCurrent(slideshow.current); 17 | // set the navigation total number of slides 18 | navigation.DOM.total.innerHTML = slideshow.current < 10 ? `0${slideshow.slidesTotal}` : slideshow.slidesTotal; 19 | // when a new slide is shown, update the navigation current slide value 20 | slideshow.on('updateCurrent', position => navigation.updateCurrent(position)); 21 | }); -------------------------------------------------------------------------------- /src/js/demo3/slideshow.js: -------------------------------------------------------------------------------- 1 | import { Slide } from '../slide'; 2 | import { EventEmitter } from 'events'; 3 | import { gsap } from 'gsap'; 4 | 5 | export class Slideshow extends EventEmitter { 6 | constructor(el) { 7 | super(); 8 | this.DOM = {el}; 9 | 10 | this.DOM.slides = [...this.DOM.el.querySelectorAll('.slide')]; 11 | this.slides = []; 12 | this.DOM.slides.forEach(slide => this.slides.push(new Slide(slide))); 13 | this.slidesTotal = this.slides.length; 14 | 15 | this.current = 0; 16 | 17 | this.config = { 18 | clipPath: { 19 | initial: 'polygon(100% 0%, 100% 50%, 100% 100%, 0% 100%, 0% 50%, 0% 0%)', 20 | final: { 21 | right: 'polygon(75% 10%, 90% 50%, 75% 90%, 10% 90%, 20% 50%, 10% 10%)', 22 | left: 'polygon(90% 10%, 75% 50%, 90% 90%, 30% 90%, 10% 50%, 30% 10%)' 23 | }, 24 | hover: 'polygon(97% 10%, 97% 50%, 97% 87%, 3% 87%, 3% 50%, 3% 10%)' 25 | } 26 | }; 27 | 28 | this.init(); 29 | } 30 | init() { 31 | this.DOM.slides[this.current].classList.add('slide--current'); 32 | gsap.set(this.slides[this.current].DOM.imgWrap, {clipPath: this.config.clipPath.initial}); 33 | 34 | for (const slide of this.slides) { 35 | slide.DOM.link.addEventListener('mouseenter', () => { 36 | gsap.killTweensOf(slide.DOM.imgWrap); 37 | gsap.to(slide.DOM.imgWrap, { 38 | duration: 1, 39 | ease: 'expo', 40 | clipPath: this.config.clipPath.hover 41 | }); 42 | }); 43 | slide.DOM.link.addEventListener('mouseleave', () => { 44 | gsap.killTweensOf(slide.DOM.imgWrap); 45 | gsap.to(slide.DOM.imgWrap, { 46 | duration: 1, 47 | ease: 'expo', 48 | clipPath: this.config.clipPath.initial 49 | }); 50 | }); 51 | } 52 | } 53 | next() { 54 | this.navigate('next'); 55 | } 56 | prev() { 57 | this.navigate('prev'); 58 | } 59 | navigate(direction) { 60 | if ( this.isAnimating ) { 61 | return false; 62 | } 63 | this.isAnimating = true; 64 | 65 | const currentSlide = this.slides[this.current]; 66 | // update current 67 | if ( direction === 'next' ) { 68 | this.current = this.current < this.slidesTotal-1 ? this.current+1 : 0; 69 | } 70 | else { 71 | this.current = this.current > 0 ? this.current-1 : this.slidesTotal-1; 72 | } 73 | const upcomingSlide = this.slides[this.current]; 74 | 75 | gsap 76 | .timeline({ 77 | onStart: () => upcomingSlide.DOM.el.classList.add('slide--current'), 78 | onComplete: () => { 79 | this.isAnimating = false; 80 | currentSlide.DOM.el.classList.remove('slide--current'); 81 | } 82 | }) 83 | .addLabel('start', 0) 84 | .set(upcomingSlide.DOM.imgWrap, { 85 | x: direction === 'next' ? '-100%' : '100%', 86 | clipPath: direction === 'next' ? this.config.clipPath.final.right : this.config.clipPath.final.left 87 | }, 'start') 88 | .set(upcomingSlide.DOM.el, {opacity: 1}, 'start') 89 | .set(upcomingSlide.DOM.img, {x: direction === 'next' ? '100%' : '-100%'}, 'start') 90 | .set(upcomingSlide.DOM.text, {y: direction === 'next' ? '100%' : '-100%'}, 'start') 91 | .set(upcomingSlide.DOM.link, {opacity: 0}, 'start') 92 | // animate the shape from initial to its final state (clip-path) 93 | .to(currentSlide.DOM.imgWrap, { 94 | duration: 0.7, 95 | ease: 'expo', 96 | clipPath: direction === 'next' ? this.config.clipPath.final.right : this.config.clipPath.final.left, 97 | rotation: 0.001 98 | }, 'start') 99 | // animate the current slide texts out 100 | .to(currentSlide.DOM.text, { 101 | duration: 1, 102 | ease: 'power3', 103 | y: direction === 'next' ? '-100%' : '100%', 104 | //stagger: 0.15 105 | }, 'start') 106 | // animate the current slide link out 107 | .to(currentSlide.DOM.link, { 108 | duration: 0.5, 109 | ease: 'power3', 110 | opacity: 0 111 | }, 'start') 112 | .to(currentSlide.DOM.imgWrap, { 113 | duration: 0.5, 114 | ease: 'expo.inOut', 115 | x: direction === 'next' ? '100%' : '-100%', 116 | rotation: 0.001 117 | }, 'start+=0.4') 118 | .to(currentSlide.DOM.img, { 119 | duration: 0.5, 120 | ease: 'expo.inOut', 121 | x: direction === 'next' ? '-100%' : '100%' 122 | }, 'start+=0.4') 123 | // and the upcoming slide in 124 | .to(upcomingSlide.DOM.imgWrap, { 125 | duration: 0.5, 126 | ease: 'expo', 127 | x: '0%', 128 | rotation: 0.001 129 | }, 'start+=0.7') 130 | .to(upcomingSlide.DOM.img, { 131 | duration: 0.5, 132 | ease: 'expo', 133 | x: '0%' 134 | }, 'start+=0.7') 135 | // animate the upcoming slide clip path to the initial shape 136 | .to(upcomingSlide.DOM.imgWrap, { 137 | duration: 0.7, 138 | ease: 'expo.inOut', 139 | clipPath: this.config.clipPath.initial 140 | }, 'start+=0.7') 141 | // animate the upcoming slide texts in 142 | .to(upcomingSlide.DOM.text, { 143 | duration: 1.5, 144 | ease: 'expo.inOut', 145 | y: '0%', 146 | rotation: 0.001, 147 | stagger: direction === 'next' ? 0.1 : -0.1 148 | }, 'start+=0.4') 149 | // animate the upcoming slide link in 150 | .to(upcomingSlide.DOM.link, { 151 | duration: 0.5, 152 | ease: 'power3.in', 153 | opacity: 1 154 | }, 'start+=1') 155 | 156 | 157 | this.emit('updateCurrent', this.current); 158 | } 159 | } -------------------------------------------------------------------------------- /src/js/demo4/index.js: -------------------------------------------------------------------------------- 1 | import { preloadImages } from '../utils'; 2 | import { Navigation } from '../navigation'; 3 | import { Slideshow } from './slideshow'; 4 | 5 | // Preload all images 6 | preloadImages('.slide__img').then(() => { 7 | // remove loader (loading class) 8 | document.body.classList.remove('loading'); 9 | 10 | const slideshow = new Slideshow(document.querySelector('.slideshow')); 11 | const navigation = new Navigation(document.querySelector('.slides-nav')); 12 | // navigation events 13 | navigation.DOM.ctrls.next.addEventListener('click', () => slideshow.next()); 14 | navigation.DOM.ctrls.prev.addEventListener('click', () => slideshow.prev()); 15 | // set the initial navigation current slide value 16 | navigation.updateCurrent(slideshow.current); 17 | // set the navigation total number of slides 18 | navigation.DOM.total.innerHTML = slideshow.current < 10 ? `0${slideshow.slidesTotal}` : slideshow.slidesTotal; 19 | // when a new slide is shown, update the navigation current slide value 20 | slideshow.on('updateCurrent', position => navigation.updateCurrent(position)); 21 | }); -------------------------------------------------------------------------------- /src/js/demo4/slideshow.js: -------------------------------------------------------------------------------- 1 | import { Slide } from '../slide'; 2 | import { EventEmitter } from 'events'; 3 | import { gsap } from 'gsap'; 4 | 5 | export class Slideshow extends EventEmitter { 6 | constructor(el) { 7 | super(); 8 | this.DOM = {el}; 9 | 10 | this.DOM.slides = [...this.DOM.el.querySelectorAll('.slide')]; 11 | this.slides = []; 12 | this.DOM.slides.forEach(slide => this.slides.push(new Slide(slide))); 13 | this.slidesTotal = this.slides.length; 14 | 15 | this.current = 0; 16 | 17 | this.config = { 18 | clipPath: { 19 | initial: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)', //two equal values cause problems because browser uses shorthand 20 | final: 'polygon(50% 10%, 60% 50%, 50% 90%, 40% 50%)', 21 | hover: 'polygon(50% 10%, 60% 50%, 50% 90%, 40% 50%)' 22 | } 23 | }; 24 | 25 | this.init(); 26 | } 27 | init() { 28 | this.DOM.slides[this.current].classList.add('slide--current'); 29 | gsap.set(this.slides[this.current].DOM.imgWrap, {clipPath: this.config.clipPath.initial}); 30 | 31 | for (const slide of this.slides) { 32 | slide.DOM.link.addEventListener('mouseenter', () => { 33 | gsap.killTweensOf(slide.DOM.imgWrap); 34 | gsap.to(slide.DOM.imgWrap, { 35 | duration: 0.7, 36 | ease: 'expo', 37 | clipPath: this.config.clipPath.hover 38 | }); 39 | }); 40 | slide.DOM.link.addEventListener('mouseleave', () => { 41 | gsap.killTweensOf(slide.DOM.imgWrap); 42 | gsap.to(slide.DOM.imgWrap, { 43 | duration: 0.7, 44 | ease: 'expo', 45 | clipPath: this.config.clipPath.initial 46 | }); 47 | }); 48 | } 49 | } 50 | next() { 51 | this.navigate('next'); 52 | } 53 | prev() { 54 | this.navigate('prev'); 55 | } 56 | navigate(direction) { 57 | if ( this.isAnimating ) { 58 | return false; 59 | } 60 | this.isAnimating = true; 61 | 62 | const currentSlide = this.slides[this.current]; 63 | // update current 64 | if ( direction === 'next' ) { 65 | this.current = this.current < this.slidesTotal-1 ? this.current+1 : 0; 66 | } 67 | else { 68 | this.current = this.current > 0 ? this.current-1 : this.slidesTotal-1; 69 | } 70 | const upcomingSlide = this.slides[this.current]; 71 | 72 | gsap 73 | .timeline({ 74 | onStart: () => upcomingSlide.DOM.el.classList.add('slide--current'), 75 | onComplete: () => { 76 | this.isAnimating = false; 77 | currentSlide.DOM.el.classList.remove('slide--current'); 78 | } 79 | }) 80 | .addLabel('start', 0) 81 | .set(upcomingSlide.DOM.imgWrap, { 82 | x: direction === 'next' ? '100%' : '-100%', 83 | y: direction === 'next' ? '-100%' : '100%', 84 | clipPath: this.config.clipPath.final 85 | }, 'start') 86 | .set(upcomingSlide.DOM.el, {opacity: 1}, 'start') 87 | .set(upcomingSlide.DOM.img, { 88 | x: direction === 'next' ? '-100%' : '100%', 89 | y: direction === 'next' ? '100%' : '-100%' 90 | }, 'start') 91 | .set(upcomingSlide.DOM.text, {y: direction === 'next' ? '100%' : '-100%'}, 'start') 92 | .set(upcomingSlide.DOM.link, {opacity: 0}, 'start') 93 | // animate the shape from initial to its final state (clip-path) 94 | .to(currentSlide.DOM.imgWrap, { 95 | duration: 1, 96 | ease: 'expo', 97 | clipPath: this.config.clipPath.final, 98 | rotation: 0.001 99 | }, 'start') 100 | // animate the current slide texts out 101 | .to(currentSlide.DOM.text, { 102 | duration: 0.7, 103 | ease: 'power3', 104 | y: direction === 'next' ? '-100%' : '100%' 105 | }, 'start') 106 | // animate the current slide link out 107 | .to(currentSlide.DOM.link, { 108 | duration: 0.5, 109 | ease: 'power3', 110 | opacity: 0 111 | }, 'start') 112 | // move the current slide out (img and imgWrap) 113 | .to(currentSlide.DOM.imgWrap, { 114 | duration: 0.7, 115 | ease: 'expo.inOut', 116 | x: direction === 'next' ? '-100%' : '100%', 117 | y: direction === 'next' ? '100%' : '-100%', 118 | rotation: 0.001 119 | }, 'start+=0.4') 120 | .to(currentSlide.DOM.img, { 121 | duration: 0.7, 122 | ease: 'expo.inOut', 123 | x: direction === 'next' ? '100%' : '-100%', 124 | y: direction === 'next' ? '-100%' : '100%', 125 | }, 'start+=0.4') 126 | // and the upcoming slide in 127 | .to(upcomingSlide.DOM.imgWrap, { 128 | duration: 0.7, 129 | ease: 'expo', 130 | x: '0%', 131 | y: '0%', 132 | rotation: 0.001 133 | }, 'start+=0.7') 134 | .to(upcomingSlide.DOM.img, { 135 | duration: 0.7, 136 | ease: 'expo', 137 | x: '0%', 138 | y: '0%' 139 | }, 'start+=0.7') 140 | // animate the upcoming slide shape to initial 141 | .to(upcomingSlide.DOM.imgWrap, { 142 | duration: 1, 143 | ease: 'expo.inOut', 144 | clipPath: this.config.clipPath.initial 145 | }, 'start+=0.9') 146 | // animate the upcoming slide texts in 147 | .to(upcomingSlide.DOM.text, { 148 | duration: 0.7, 149 | ease: 'power3.inOut', 150 | rotation: 0.001, 151 | y: '0%', 152 | stagger: direction === 'next' ? 0.1 : -0.1 153 | }, 'start+=1') 154 | // animate the upcoming slide link in 155 | .to(upcomingSlide.DOM.link, { 156 | duration: 0.2, 157 | ease: 'power3.in', 158 | opacity: 1 159 | }, 'start+=1.3') 160 | 161 | 162 | this.emit('updateCurrent', this.current); 163 | } 164 | } -------------------------------------------------------------------------------- /src/js/navigation.js: -------------------------------------------------------------------------------- 1 | export class Navigation { 2 | constructor(el) { 3 | this.DOM = {el}; 4 | this.DOM.ctrls = { 5 | next: this.DOM.el.querySelector('.slides-nav__button--next'), 6 | prev: this.DOM.el.querySelector('.slides-nav__button--prev') 7 | }; 8 | this.DOM.current = this.DOM.el.querySelector('.slides-nav__index-current'); 9 | this.DOM.total = this.DOM.el.querySelector('.slides-nav__index-total'); 10 | } 11 | // updates the current value 12 | updateCurrent(position) { 13 | this.DOM.current.innerHTML = position < 10 ? `0${position+1}` : position; 14 | } 15 | } -------------------------------------------------------------------------------- /src/js/slide.js: -------------------------------------------------------------------------------- 1 | export class Slide { 2 | constructor(el) { 3 | this.DOM = {el}; 4 | this.DOM.imgWrap = this.DOM.el.querySelector('.slide__img-wrap'); 5 | this.DOM.img = this.DOM.imgWrap.querySelector('.slide__img'); 6 | this.DOM.headline = this.DOM.el.querySelector('.slides__caption-headline'); 7 | this.DOM.text = this.DOM.headline.querySelectorAll('.text-row > span'); 8 | this.DOM.link = this.DOM.el.querySelector('.slides__caption-link'); 9 | } 10 | } -------------------------------------------------------------------------------- /src/js/utils.js: -------------------------------------------------------------------------------- 1 | const imagesLoaded = require('imagesloaded'); 2 | 3 | // Preload images 4 | const preloadImages = (selector = 'img') => { 5 | return new Promise((resolve) => { 6 | imagesLoaded(document.querySelectorAll(selector), {background: true}, resolve); 7 | }); 8 | }; 9 | 10 | export { 11 | preloadImages 12 | }; --------------------------------------------------------------------------------