├── .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 | 
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | };
--------------------------------------------------------------------------------