├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── css └── base.css ├── favicon.ico ├── img ├── 1.jpg ├── 10.jpg ├── 11.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg ├── 9.jpg └── star.svg ├── index.html ├── index2.html ├── index3.html ├── index4.html ├── index5.html ├── index6.html └── js ├── content.js ├── demo1 └── index.js ├── demo2 └── index.js ├── demo3 └── index.js ├── demo4 └── index.js ├── demo5 └── index.js ├── demo6 └── index.js ├── gsap.min.js ├── imagesloaded.pkgd.min.js ├── item.js └── utils.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | .parcel-cache 4 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2009 - 2022 [Codrops](https://tympanus.net/codrops) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Layer Animations with Clip-Path 2 | 3 | Some ideas for speedy page transition animations with layered images using clip-path. 4 | 5 | ![Image Title](https://tympanus.net/codrops/wp-content/uploads/2023/10/cliplayers_feat.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=74227) 8 | 9 | [Demo](http://tympanus.net/Development/LayersAnimation/) 10 | 11 | ## Installation 12 | 13 | Run this demo on a [local server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server). 14 | 15 | ## Credits 16 | 17 | - Images generated with [Midjourney](https://midjourney.com) 18 | 19 | ## Misc 20 | 21 | 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/) 22 | 23 | ## License 24 | [MIT](LICENSE) 25 | 26 | Made with :blue_heart: by [Codrops](http://www.codrops.com) 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 14px; 9 | --color-text: #000; 10 | --color-bg: #f07547; 11 | --color-link: rgb(0 0 0 / 70%); 12 | --color-link-hover: #000; 13 | --layer-width: 100vw; 14 | --layer-height: 100vh; 15 | --layer-radius: 0; 16 | --page-padding: 1rem; 17 | } 18 | 19 | .demo-2 { 20 | --color-bg: #94c3c1; 21 | } 22 | 23 | .demo-3 { 24 | --layer-width: 98vw; 25 | --layer-height: 96vh; 26 | --layer-radius: 20px; 27 | --color-bg: #f4b3b9; 28 | } 29 | 30 | .demo-4 { 31 | --layer-width: 98vw; 32 | --layer-height: 96vh; 33 | --color-bg: #b3f4d7; 34 | } 35 | 36 | .demo-5 { 37 | --color-bg: #d4b3f4; 38 | } 39 | 40 | .demo-6 { 41 | --color-bg: #f4f3b3; 42 | } 43 | 44 | .demo-7 { 45 | --color-bg: #f1b454; 46 | --layer-width: 100vw; 47 | --layer-height: 100vh; 48 | } 49 | 50 | body { 51 | margin: 0; 52 | color: var(--color-text); 53 | background-color: var(--color-bg); 54 | font-family: "alfabet",-apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif; 55 | -webkit-font-smoothing: antialiased; 56 | -moz-osx-font-smoothing: grayscale; 57 | width: 100%; 58 | min-height: 100vh; 59 | min-height: -webkit-fill-available; 60 | overflow: hidden; 61 | } 62 | 63 | /* Page Loader */ 64 | .js .loading::before, 65 | .js .loading::after { 66 | content: ''; 67 | position: fixed; 68 | z-index: 1000; 69 | } 70 | 71 | .js .loading::before { 72 | top: 0; 73 | left: 0; 74 | width: 100%; 75 | height: 100%; 76 | background: var(--color-bg); 77 | } 78 | 79 | .js .loading::after { 80 | top: 50%; 81 | left: 50%; 82 | width: 60px; 83 | height: 60px; 84 | margin: -30px 0 0 -30px; 85 | border-radius: 50%; 86 | opacity: 0.4; 87 | background: var(--color-link); 88 | animation: loaderAnim 0.7s linear infinite alternate forwards; 89 | 90 | } 91 | 92 | @keyframes loaderAnim { 93 | to { 94 | opacity: 1; 95 | transform: scale3d(0.5,0.5,1); 96 | } 97 | } 98 | 99 | a { 100 | text-decoration: none; 101 | color: var(--color-link); 102 | outline: none; 103 | cursor: pointer; 104 | } 105 | 106 | a:hover { 107 | color: var(--color-link-hover); 108 | outline: none; 109 | } 110 | 111 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */ 112 | a:focus { 113 | /* Provide a fallback style for browsers 114 | that don't support :focus-visible */ 115 | outline: none; 116 | background: lightgrey; 117 | } 118 | 119 | a:focus:not(:focus-visible) { 120 | /* Remove the focus indicator on mouse-focus for browsers 121 | that do support :focus-visible */ 122 | background: transparent; 123 | } 124 | 125 | a:focus-visible { 126 | /* Draw a very noticeable focus style for 127 | keyboard-focus on browsers that do support 128 | :focus-visible */ 129 | outline: 2px solid red; 130 | background: transparent; 131 | } 132 | 133 | main { 134 | display: grid; 135 | grid-template-areas: 'main'; 136 | width: 100%; 137 | height: 100vh; 138 | } 139 | 140 | main::before { 141 | position: fixed; 142 | top: 30vh; 143 | right: 4vw; 144 | content: 'Click da body'; 145 | background: url(../img/star.svg) no-repeat 50% 50%; 146 | background-size: 100%; 147 | font-size: 2vmax; 148 | padding: 10vmax; 149 | width: 30vmax; 150 | height: 30vmax; 151 | text-align: center; 152 | line-height: 0.8; 153 | display: grid; 154 | place-items: center; 155 | transform: rotate(12.5deg); 156 | font-family: "gabriella", sans-serif; 157 | text-transform: uppercase; 158 | font-weight: 900; 159 | } 160 | 161 | .unbutton { 162 | background: none; 163 | border: 0; 164 | padding: 0; 165 | margin: 0; 166 | font: inherit; 167 | cursor: pointer; 168 | } 169 | 170 | .unbutton:focus { 171 | outline: none; 172 | } 173 | 174 | .frame { 175 | grid-area: main; 176 | position: relative; 177 | padding: var(--page-padding); 178 | display: grid; 179 | z-index: 1000; 180 | grid-template-columns: auto 1fr; 181 | grid-template-areas: 'title title' 'prev back' 'sponsor sponsor' 'demos demos'; 182 | grid-row-gap: 1rem; 183 | grid-column-gap: 2rem; 184 | pointer-events: none; 185 | align-content: start; 186 | } 187 | 188 | .frame a { 189 | pointer-events: auto; 190 | } 191 | 192 | .frame__title { 193 | grid-area: title; 194 | font-size: inherit; 195 | margin: 0; 196 | } 197 | 198 | .frame__back { 199 | grid-area: back; 200 | justify-self: start; 201 | } 202 | 203 | .frame__prev { 204 | grid-area: prev; 205 | justify-self: start; 206 | } 207 | 208 | .frame__demos { 209 | grid-area: demos; 210 | position: relative; 211 | overflow: hidden; 212 | display: flex; 213 | align-items: center; 214 | gap: 0.5rem; 215 | align-self: start; 216 | justify-self: start; 217 | flex-wrap: wrap; 218 | } 219 | 220 | .frame__demos span { 221 | margin-right: 1rem; 222 | width: 100%; 223 | } 224 | 225 | .frame__demo { 226 | width: 3rem; 227 | aspect-ratio: 1; 228 | height: auto; 229 | display: block; 230 | flex: none; 231 | display: grid; 232 | place-items: center; 233 | } 234 | 235 | .frame__demo--current, 236 | .frame__demo--current:focus, 237 | .frame__demo--current:hover { 238 | background: var(--color-link-hover); 239 | color: var(--color-bg); 240 | } 241 | 242 | body #cdawrap { 243 | justify-self: start; 244 | } 245 | 246 | .content { 247 | grid-area: main; 248 | display: grid; 249 | width: 100vw; 250 | height: 100vh; 251 | position: relative; 252 | grid-template-areas: 'content'; 253 | } 254 | 255 | .content__inner { 256 | grid-area: content; 257 | width: 100%; 258 | display: grid; 259 | grid-template-areas: 'text' 'headline'; 260 | padding: 2rem var(--page-padding) var(--page-padding); 261 | grid-template-rows: 1fr auto; 262 | will-change: transform; 263 | } 264 | 265 | .hidden { 266 | visibility: hidden; 267 | pointer-events: none; 268 | } 269 | 270 | .content__inner h2 { 271 | grid-area: headline; 272 | font-size: clamp(2rem, 10vw,13rem); 273 | font-family: "gabriella", sans-serif; 274 | text-transform: uppercase; 275 | font-weight: 900; 276 | margin: 0; 277 | line-height: 1; 278 | } 279 | 280 | .content__inner p { 281 | grid-area: text; 282 | font-size: clamp(1rem,3vw,2.5rem); 283 | line-height: 1.2; 284 | font-weight: 300; 285 | align-self: end; 286 | } 287 | 288 | .layers { 289 | grid-area: content; 290 | align-self: center; 291 | justify-self: center; 292 | flex: none; 293 | position: relative; 294 | width: var(--layer-width); 295 | height: var(--layer-height); 296 | } 297 | 298 | .layers__item, 299 | .layers__item-img { 300 | position: absolute; 301 | width: 100%; 302 | height: 100%; 303 | } 304 | 305 | .layers__item { 306 | overflow: hidden; 307 | opacity: 0; 308 | will-change: clip-path; 309 | border-radius: var(--layer-radius); 310 | } 311 | 312 | .layers__item-img { 313 | background-size: cover; 314 | background-position: 50% 50%; 315 | } 316 | 317 | @media screen and (min-width: 53em) { 318 | :root { 319 | --page-padding: 3rem; 320 | } 321 | .frame { 322 | height: 100vh; 323 | width: 100%; 324 | grid-template-columns: auto auto auto 1fr; 325 | grid-template-rows: auto auto; 326 | grid-template-areas: 'title prev back sponsor' 'demos demos demos demos'; 327 | align-content: space-between; 328 | justify-items: start; 329 | grid-gap: 2rem; 330 | } 331 | .frame__demos { 332 | padding-left: 2rem; 333 | border: 1px solid; 334 | border-radius: 2rem; 335 | } 336 | .frame__demos span { 337 | width: auto; 338 | } 339 | .content { 340 | height: 100vh; 341 | } 342 | .content__inner { 343 | padding: 2rem var(--page-padding) calc(var(--page-padding) + 15vh); 344 | } 345 | main::before { 346 | top: 4vh; 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/favicon.ico -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/1.jpg -------------------------------------------------------------------------------- /img/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/10.jpg -------------------------------------------------------------------------------- /img/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/11.jpg -------------------------------------------------------------------------------- /img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/2.jpg -------------------------------------------------------------------------------- /img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/3.jpg -------------------------------------------------------------------------------- /img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/4.jpg -------------------------------------------------------------------------------- /img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/5.jpg -------------------------------------------------------------------------------- /img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/6.jpg -------------------------------------------------------------------------------- /img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/7.jpg -------------------------------------------------------------------------------- /img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/8.jpg -------------------------------------------------------------------------------- /img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/LayersAnimation/fec671735be6aa68949a5fabcda1967658afc717/img/9.jpg -------------------------------------------------------------------------------- /img/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | image/svg+xml 14 | 15 | 16 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layers Animation with Clip-path | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |

Layers Animation with Clip-path

20 | Back to the article 21 | Previous demo 22 | 31 |
32 |
33 |
34 |

act( )rs

35 |

Blinded by screens, in a narrative cage,
36 | But there's a call beyond, igniting our rage.
37 | Every show, every story, molding the pics in our head,
38 | Time to shatter the frames, live by our own thread.

39 |
40 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layers Animation with Clip-path | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |

Layers Animation with Clip-path

20 | Back to the article 21 | Previous demo 22 | 31 |
32 |
33 |
34 |

be@con

35 |

Masses walkin' blind, in a world of haze,
36 | But there's a call beyond, a beacon that blazes.
37 | It's the voice of freedom, the heart of the wild,
38 | Urging us to see, through the eyes of a child.

39 |
40 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /index3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layers Animation with Clip-path | Demo 3 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |

Layers Animation with Clip-path

20 | Back to the article 21 | Previous demo 22 | 31 |
32 |
33 |
34 |

Str#ngs

35 |

In the shadows, they dwell, with power to lease,
36 | Society’s strings, they pull in peace.
37 | In the grand show, they silently bark orders,
38 | While we walk the path, within set borders.

39 |
40 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /index4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layers Animation with Clip-path | Demo 4 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |

Layers Animation with Clip-path

20 | Back to the article 21 | Previous demo 22 | 31 |
32 |
33 |
34 |

Chain$

35 |

Concrete battles, where silence is gold,
36 | But a ripple in the pond, tales of old.
37 | To shatter the chains, to chart our own way,
38 | To light up the path, bring forth the day.

39 |
40 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /index5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layers Animation with Clip-path | Demo 5 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |

Layers Animation with Clip-path

20 | Back to the article 21 | Previous demo 22 | 31 |
32 |
33 |
34 |

Defi@nce

35 |

It ain't about chaos, but clarity so pure,
36 | With every act of defiance, the intent's sure.
37 | In this land of illusion, where truths are undone,
38 | We've got a duty to awaken, and become one.

39 |
40 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /index6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layers Animation with Clip-path | Demo 6 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |

Layers Animation with Clip-path

20 | Back to the article 21 | Previous demo 22 | 31 |
32 |
33 |
34 |

Tr@nce

35 |

In the city's trance, where perceptions are rehearsed,
36 | With conviction and clarity, their illusions we'll burst.
37 | Brother may craft the tales, but we pen the last list,
38 | With eyes wide open, in truth, we'll exist.

39 |
40 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /js/content.js: -------------------------------------------------------------------------------- 1 | 2 | export class Content { 3 | // Class property initialization with default values. 4 | // The DOM property holds references to the main and inner elements of the Content component. 5 | DOM = { 6 | el: null, // Holds the reference to the main DOM element with class 'content__inner'. 7 | title: null, // Holds the reference to the inner DOM element

. 8 | description: null, // Holds the reference to the inner DOM element

. 9 | }; 10 | 11 | /** 12 | * Constructor for the Content class. Initializes the instance, sets up DOM references, and binds events. 13 | * @param {HTMLElement} DOM_el - The main DOM element for the Content 14 | */ 15 | constructor(DOM_el) { 16 | // Assign the provided DOM element to the 'el' property of the 'DOM' object. 17 | this.DOM.el = DOM_el; 18 | this.DOM.title = this.DOM.el.querySelector('h2'); 19 | this.DOM.description = this.DOM.el.querySelector('p'); 20 | } 21 | } -------------------------------------------------------------------------------- /js/demo1/index.js: -------------------------------------------------------------------------------- 1 | // Importing necessary functions and classes from other files 2 | import { preloadImages } from '../utils.js'; // Utility function for preloading images 3 | import { Item } from '../item.js'; // Item class 4 | import { Content } from '../content.js'; // Content class 5 | 6 | // frame element 7 | const frameElement = document.querySelector('.frame'); 8 | 9 | // Selecting all elements with class 'layers__item' and converting NodeList to an array 10 | const DOMItems = [...document.querySelectorAll('.layers__item')]; 11 | const items = []; // Array to store instances of the Item class 12 | 13 | // Creating new instances of Item for each selected DOM element 14 | DOMItems.forEach(item => { 15 | items.push(new Item(item)); // Initializing a new object for each item 16 | }); 17 | 18 | // Selecting all elements with class 'content__inner' and converting NodeList to an array 19 | const DOMContentSections = [...document.querySelectorAll('.content > .content__inner')]; 20 | const contents = []; // Array to store instances of the Content class 21 | 22 | // Creating new instances of Content for each selected DOM element 23 | DOMContentSections.forEach(content => { 24 | contents.push(new Content(content)); // Initializing a new object for each content 25 | }); 26 | 27 | // Toggle the "hidden" class between two content elements 28 | const toggleContent = () => { 29 | // Assuming there are only two content elements 30 | const [content1, content2] = contents; 31 | 32 | // Toggle the 'hidden' class on the first content element 33 | if (content1.DOM.el.classList.contains('hidden')) { 34 | content1.DOM.el.classList.remove('hidden'); 35 | content2.DOM.el.classList.add('hidden'); 36 | } else { 37 | content1.DOM.el.classList.add('hidden'); 38 | content2.DOM.el.classList.remove('hidden'); 39 | } 40 | }; 41 | 42 | // GSAP timeline 43 | let tl = null; 44 | 45 | // Setting up the animation properties 46 | const animationSettings = { 47 | duration: 1.4, // Duration of the animation 48 | ease: 'power3.inOut', // Type of easing to use for the animation transition 49 | delayFactor: 0.2 // Delay between each item's animation 50 | }; 51 | 52 | // Event listener for click events on the document 53 | document.addEventListener('click', event => { 54 | // Check if the timeline is currently active (running) 55 | if (tl && tl.isActive() || frameElement.contains(event.target)) { 56 | return false; // Don't start a new animation 57 | } 58 | 59 | // The currently active content element 60 | const contentActive = contents.find(content => !content.DOM.el.classList.contains('hidden')); 61 | 62 | // Assuming there are only two content elements 63 | const contentInactive = contents.find(content => content !== contentActive); 64 | 65 | // Mapping each Item object to its actual DOM element for the animation 66 | const allItems = items.map(item => item.DOM.el); 67 | 68 | // Isolating the last item's DOM element for a separate animation effect 69 | const lastItem = items[items.length - 1].DOM.el; 70 | 71 | // Mapping each Item object to its 'inner' property (inner image) 72 | const allInnerItems = items.map(item => item.DOM.inner); 73 | 74 | // Creating a new GSAP timeline for managing a sequence of animations 75 | tl = gsap.timeline({ 76 | paused: true, // Create the timeline in a paused state 77 | defaults: { // Default settings applied to all animations within this timeline 78 | duration: animationSettings.duration, 79 | ease: animationSettings.ease, 80 | } 81 | }) 82 | .fromTo(allItems, { // Initial animation state 83 | opacity: 1, // Fully visible 84 | 'clip-path': 'polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)' // CSS clip-path shape 85 | }, { // Animation target state 86 | stagger: { // Settings for staggering animations for each item 87 | each: animationSettings.delayFactor, // Time between each item's animation 88 | onComplete: function() { // Callback after each item finishes animating 89 | const targetElement = this.targets()[0]; // The element that just finished animating 90 | // Determining the index of the animated element within the original DOM NodeList 91 | const index = DOMItems.indexOf(targetElement); 92 | if ( index ) { // If the element is not the first one (index 0) 93 | // Set the opacity of the previous element to 0 94 | gsap.set(items[index-1].DOM.el, {opacity: 0}); 95 | } 96 | }, 97 | }, 98 | 'clip-path': 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)', // Target shape of the clip-path 99 | }, 0) 100 | .fromTo(allInnerItems, { // Starting state for 'inner' elements' animation 101 | yPercent: 0, 102 | filter: 'brightness(30%)' // CSS filters to adjust color 103 | }, { // Animation target state 104 | stagger: animationSettings.delayFactor, // Stagger settings similar to above 105 | filter: 'brightness(100%)' // Full brightness 106 | }, 0) 107 | .to([contentActive.DOM.title, contentActive.DOM.description], { 108 | startAt: {yPercent: 0}, 109 | stagger: -0.04, 110 | yPercent: -200 111 | }, 0) 112 | .add((() => toggleContent())) 113 | .fromTo([contentInactive.DOM.title, contentInactive.DOM.description], { 114 | yPercent: 200 115 | }, { 116 | ease: 'expo', // Different easing effect 117 | stagger: -0.15, 118 | yPercent: 0 119 | }) 120 | .to(lastItem, { // Animation for the last item 121 | duration: 1, 122 | ease: 'power4', // Different easing effect 123 | 'clip-path': 'polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)', // Animating clip-path to different shape 124 | onComplete: () => gsap.set(lastItem, {opacity: 0}) // After animation, hide the last item 125 | }, '<'); 126 | 127 | // Start the animation 128 | tl.play(); 129 | 130 | }); 131 | 132 | // Preloading all images specified by the selector 133 | preloadImages('.layers__item-img').then(() => { 134 | // Once images are preloaded, remove the 'loading' indicator/class from the body 135 | document.body.classList.remove('loading'); 136 | }); 137 | -------------------------------------------------------------------------------- /js/demo2/index.js: -------------------------------------------------------------------------------- 1 | // Importing necessary functions and classes from other files 2 | import { preloadImages } from '../utils.js'; // Utility function for preloading images 3 | import { Item } from '../item.js'; // Item class 4 | import { Content } from '../content.js'; // Content class 5 | 6 | // frame element 7 | const frameElement = document.querySelector('.frame'); 8 | 9 | // Selecting the element with class 'layers' 10 | const DOMlayers = document.querySelector('.layers'); 11 | // Selecting all elements with class 'layers__item' and converting NodeList to an array 12 | const DOMItems = [...DOMlayers.querySelectorAll('.layers__item')]; 13 | const items = []; // Array to store instances of the Item class 14 | 15 | // Creating new instances of Item for each selected DOM element 16 | DOMItems.forEach(item => { 17 | items.push(new Item(item)); // Initializing a new object for each item 18 | }); 19 | 20 | // Selecting all elements with class 'content__inner' and converting NodeList to an array 21 | const DOMContentSections = [...document.querySelectorAll('.content > .content__inner')]; 22 | const contents = []; // Array to store instances of the Content class 23 | 24 | // Creating new instances of Content for each selected DOM element 25 | DOMContentSections.forEach(content => { 26 | contents.push(new Content(content)); // Initializing a new object for each content 27 | }); 28 | 29 | // Toggle the "hidden" class between two content elements 30 | const toggleContent = () => { 31 | // Assuming there are only two content elements 32 | const [content1, content2] = contents; 33 | 34 | // Toggle the 'hidden' class on the first content element 35 | if (content1.DOM.el.classList.contains('hidden')) { 36 | content1.DOM.el.classList.remove('hidden'); 37 | content2.DOM.el.classList.add('hidden'); 38 | } else { 39 | content1.DOM.el.classList.add('hidden'); 40 | content2.DOM.el.classList.remove('hidden'); 41 | } 42 | }; 43 | 44 | // GSAP timeline 45 | let tl = null; 46 | 47 | // Setting up the animation properties 48 | const animationSettings = { 49 | duration: 0.5, // Duration of the animation 50 | ease: 'power3.inOut', // Type of easing to use for the animation transition 51 | delayFactor: 0.13 // Delay between each item's animation 52 | }; 53 | 54 | // Event listener for click events on the document 55 | document.addEventListener('click', event => { 56 | // Check if the timeline is currently active (running) 57 | if (tl && tl.isActive() || frameElement.contains(event.target)) { 58 | return false; // Don't start a new animation 59 | } 60 | 61 | // The currently active content element 62 | const contentActive = contents.find(content => !content.DOM.el.classList.contains('hidden')); 63 | 64 | // Assuming there are only two content elements 65 | const contentInactive = contents.find(content => content !== contentActive); 66 | 67 | // Mapping each Item object to its actual DOM element for the animation 68 | const allItems = items.map(item => item.DOM.el); 69 | 70 | // Creating a new GSAP timeline for managing a sequence of animations 71 | tl = gsap.timeline({ 72 | paused: true, // Create the timeline in a paused state 73 | defaults: { // Default settings applied to all animations within this timeline 74 | duration: animationSettings.duration, 75 | ease: animationSettings.ease, 76 | } 77 | }) 78 | .fromTo(allItems, { // Initial animation state 79 | opacity: 1, // Fully visible 80 | 'clip-path': 'polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%)', // CSS clip-path shape 81 | }, { // Animation target state 82 | ease: 'power3.in', 83 | stagger: animationSettings.delayFactor, // Time between each item's animation 84 | 'clip-path': 'polygon(50% 0%, 83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%)', // Target shape of the clip-path 85 | }, 0) 86 | .to(allItems, { // Animation target state 87 | ease: 'power3', 88 | stagger: animationSettings.delayFactor, // Time between each item's animation 89 | 'clip-path': 'polygon(50% 0%, 100% 0%, 100% 43%, 100% 100%, 68% 100%, 32% 100%, 0% 100%, 0% 43%, 0% 0%)', // Target shape of the clip-path 90 | }, 0.5) 91 | .to(contentActive.DOM.el, { 92 | duration: 1, 93 | startAt: {scale: 1}, 94 | scale: 1.2, 95 | opacity: 0 96 | }, 0) 97 | .add((() => toggleContent())) 98 | .to(allItems, { // Animation for the last item 99 | ease: 'power3.in', 100 | stagger: -1*animationSettings.delayFactor, // Time between each item's animation 101 | 'clip-path': 'polygon(50% 0%, 83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%)', // Animating clip-path to different shape 102 | }, '<') 103 | .to(allItems, { // Animation for the last item 104 | ease: 'power3', 105 | stagger: -1*animationSettings.delayFactor, // Time between each item's animation 106 | 'clip-path': 'polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%)', // Animating clip-path to different shape 107 | }, '<+=0.5') 108 | .fromTo(contentInactive.DOM.el, { 109 | scale: 1.8, 110 | opacity: 1 111 | }, { 112 | duration: 1, 113 | ease: 'expo', 114 | scale: 1 115 | }, '<+=0.5'); 116 | 117 | // Start the animation 118 | tl.play(); 119 | 120 | }); 121 | 122 | // Preloading all images specified by the selector 123 | preloadImages('.layers__item-img').then(() => { 124 | // Once images are preloaded, remove the 'loading' indicator/class from the body 125 | document.body.classList.remove('loading'); 126 | }); 127 | -------------------------------------------------------------------------------- /js/demo3/index.js: -------------------------------------------------------------------------------- 1 | // Importing necessary functions and classes from other files 2 | import { preloadImages } from '../utils.js'; // Utility function for preloading images 3 | import { Item } from '../item.js'; // Item class 4 | import { Content } from '../content.js'; // Content class 5 | 6 | // frame element 7 | const frameElement = document.querySelector('.frame'); 8 | 9 | // Selecting the element with class 'layers' 10 | const DOMlayers = document.querySelector('.layers'); 11 | // Selecting all elements with class 'layers__item' and converting NodeList to an array 12 | const DOMItems = [...DOMlayers.querySelectorAll('.layers__item')]; 13 | const items = []; // Array to store instances of the Item class 14 | 15 | // Creating new instances of Item for each selected DOM element 16 | DOMItems.forEach(item => { 17 | items.push(new Item(item)); // Initializing a new object for each item 18 | }); 19 | 20 | // Selecting all elements with class 'content__inner' and converting NodeList to an array 21 | const DOMContentSections = [...document.querySelectorAll('.content > .content__inner')]; 22 | const contents = []; // Array to store instances of the Content class 23 | 24 | // Creating new instances of Content for each selected DOM element 25 | DOMContentSections.forEach(content => { 26 | contents.push(new Content(content)); // Initializing a new object for each content 27 | }); 28 | 29 | // Toggle the "hidden" class between two content elements 30 | const toggleContent = () => { 31 | // Assuming there are only two content elements 32 | const [content1, content2] = contents; 33 | 34 | // Toggle the 'hidden' class on the first content element 35 | if (content1.DOM.el.classList.contains('hidden')) { 36 | content1.DOM.el.classList.remove('hidden'); 37 | content2.DOM.el.classList.add('hidden'); 38 | } else { 39 | content1.DOM.el.classList.add('hidden'); 40 | content2.DOM.el.classList.remove('hidden'); 41 | } 42 | }; 43 | 44 | // GSAP timeline 45 | let tl = null; 46 | 47 | // Setting up the animation properties 48 | const animationSettings = { 49 | duration: 1.4, // Duration of the animation 50 | ease: 'power3.inOut', // Type of easing to use for the animation transition 51 | delayFactor: 0.2 // Delay between each item's animation 52 | }; 53 | 54 | // Event listener for click events on the document 55 | document.addEventListener('click', event => { 56 | // Check if the timeline is currently active (running) 57 | if (tl && tl.isActive() || frameElement.contains(event.target)) { 58 | return false; // Don't start a new animation 59 | } 60 | 61 | // The currently active content element 62 | const contentActive = contents.find(content => !content.DOM.el.classList.contains('hidden')); 63 | 64 | // Assuming there are only two content elements 65 | const contentInactive = contents.find(content => content !== contentActive); 66 | 67 | // Mapping each Item object to its actual DOM element for the animation 68 | const allItems = items.map(item => item.DOM.el); 69 | 70 | // Isolating the last item's DOM element for a separate animation effect 71 | const lastItem = items[items.length - 1].DOM.el; 72 | 73 | // Mapping each Item object to its 'inner' property (inner image) 74 | const allInnerItems = items.map(item => item.DOM.inner); 75 | 76 | const lastInner = items[items.length - 1].DOM.inner; 77 | 78 | // Creating a new GSAP timeline for managing a sequence of animations 79 | tl = gsap.timeline({ 80 | paused: true, // Create the timeline in a paused state 81 | defaults: { // Default settings applied to all animations within this timeline 82 | duration: animationSettings.duration, 83 | ease: animationSettings.ease, 84 | } 85 | }) 86 | .fromTo(DOMlayers, { 87 | scale: 0.9 88 | }, { 89 | duration: animationSettings.duration + animationSettings.delayFactor*(items.length-1) , 90 | ease: 'linear', 91 | scale: 1.1 92 | }, 0) 93 | .fromTo(allItems, { // Initial animation state 94 | opacity: 1, // Fully visible 95 | 'clip-path': 'polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%)' // CSS clip-path shape 96 | }, { // Animation target state 97 | stagger: { // Settings for staggering animations for each item 98 | each: animationSettings.delayFactor, // Time between each item's animation 99 | onComplete: function() { // Callback after each item finishes animating 100 | const targetElement = this.targets()[0]; // The element that just finished animating 101 | // Determining the index of the animated element within the original DOM NodeList 102 | const index = DOMItems.indexOf(targetElement); 103 | if ( index ) { // If the element is not the first one (index 0) 104 | // Set the opacity of the previous element to 0 105 | gsap.set(items[index-1].DOM.el, {opacity: 0}); 106 | } 107 | } 108 | }, 109 | 'clip-path': 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)', // Target shape of the clip-path 110 | }, 0) 111 | .fromTo(allInnerItems, { // Starting state for 'inner' elements' animation 112 | xPercent: 0, 113 | filter: 'brightness(15%)' // CSS filters to adjust color 114 | }, { // Animation target state 115 | stagger: animationSettings.delayFactor, // Stagger settings similar to above 116 | filter: 'brightness(120%)' // Full brightness 117 | }, 0) 118 | .to([contentActive.DOM.title, contentActive.DOM.description], { 119 | startAt: {xPercent: 0, opacity: 1}, 120 | stagger: 0.08, 121 | xPercent: -100, 122 | opacity: 0 123 | }, 0) 124 | .addLabel('reveal', animationSettings.duration + animationSettings.delayFactor*(items.length-1)) 125 | .add(() => toggleContent(), 'reveal') 126 | .to(lastItem, { // Animation for the last item 127 | duration: 1, 128 | ease: 'power3', // Different easing effect 129 | 'clip-path': 'polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%)', // Animating clip-path to different shape 130 | onComplete: () => gsap.set(lastItem, {opacity: 0}) // After animation, hide the last item 131 | }, 'reveal') 132 | .to(lastInner, { 133 | duration: 1, 134 | ease: 'power3', // Different easing effect 135 | xPercent: -20, 136 | filter: 'brightness(50%)', 137 | }, '<') 138 | .fromTo([contentInactive.DOM.title, contentInactive.DOM.description], { 139 | xPercent: 100, 140 | opacity: 0 141 | }, { 142 | duration: .8, 143 | ease: 'power3', // Different easing effect 144 | stagger: -0.15, 145 | opacity: 1, 146 | xPercent: 0 147 | }, '<') 148 | 149 | // Start the animation 150 | tl.play(); 151 | 152 | }); 153 | 154 | // Preloading all images specified by the selector 155 | preloadImages('.layers__item-img').then(() => { 156 | // Once images are preloaded, remove the 'loading' indicator/class from the body 157 | document.body.classList.remove('loading'); 158 | }); 159 | -------------------------------------------------------------------------------- /js/demo4/index.js: -------------------------------------------------------------------------------- 1 | // Importing necessary functions and classes from other files 2 | import { preloadImages } from '../utils.js'; // Utility function for preloading images 3 | import { Item } from '../item.js'; // Item class 4 | import { Content } from '../content.js'; // Content class 5 | 6 | // frame element 7 | const frameElement = document.querySelector('.frame'); 8 | 9 | // Selecting all elements with class 'layers__item' and converting NodeList to an array 10 | const DOMItems = [...document.querySelectorAll('.layers__item')]; 11 | const items = []; // Array to store instances of the Item class 12 | 13 | // Creating new instances of Item for each selected DOM element 14 | DOMItems.forEach(item => { 15 | items.push(new Item(item)); // Initializing a new object for each item 16 | }); 17 | 18 | // Selecting all elements with class 'content__inner' and converting NodeList to an array 19 | const DOMContentSections = [...document.querySelectorAll('.content > .content__inner')]; 20 | const contents = []; // Array to store instances of the Content class 21 | 22 | // Creating new instances of Content for each selected DOM element 23 | DOMContentSections.forEach(content => { 24 | contents.push(new Content(content)); // Initializing a new object for each content 25 | }); 26 | 27 | // Toggle the "hidden" class between two content elements 28 | const toggleContent = () => { 29 | // Assuming there are only two content elements 30 | const [content1, content2] = contents; 31 | 32 | // Toggle the 'hidden' class on the first content element 33 | if (content1.DOM.el.classList.contains('hidden')) { 34 | content1.DOM.el.classList.remove('hidden'); 35 | content2.DOM.el.classList.add('hidden'); 36 | } else { 37 | content1.DOM.el.classList.add('hidden'); 38 | content2.DOM.el.classList.remove('hidden'); 39 | } 40 | }; 41 | 42 | // GSAP timeline 43 | let tl = null; 44 | 45 | // Setting up the animation properties 46 | const animationSettings = { 47 | duration: 1.2, // Duration of the animation 48 | ease: 'power3.inOut', // Type of easing to use for the animation transition 49 | delayFactor: 0.1 // Delay between each item's animation 50 | }; 51 | 52 | // Event listener for click events on the document 53 | document.addEventListener('click', event => { 54 | // Check if the timeline is currently active (running) 55 | if (tl && tl.isActive() || frameElement.contains(event.target)) { 56 | return false; // Don't start a new animation 57 | } 58 | 59 | // The currently active content element 60 | const contentActive = contents.find(content => !content.DOM.el.classList.contains('hidden')); 61 | 62 | // Assuming there are only two content elements 63 | const contentInactive = contents.find(content => content !== contentActive); 64 | 65 | // Mapping each Item object to its actual DOM element for the animation 66 | const allItems = items.map(item => item.DOM.el); 67 | 68 | // Creating a new GSAP timeline for managing a sequence of animations 69 | tl = gsap.timeline({ 70 | paused: true, // Create the timeline in a paused state 71 | defaults: { // Default settings applied to all animations within this timeline 72 | duration: animationSettings.duration, 73 | ease: animationSettings.ease, 74 | } 75 | }) 76 | .fromTo(allItems, { // Initial animation state 77 | opacity: 1, // Fully visible 78 | 'clip-path': 'polygon(100% 100%, 100% 0%, 100% 100%, 0% 100%)' // CSS clip-path shape 79 | }, { // Animation target state 80 | stagger: animationSettings.delayFactor, // Time between each item's animation 81 | 'clip-path': 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)', // Target shape of the clip-path 82 | }, 0) 83 | .to(contentActive.DOM.el, { 84 | startAt: {xPercent: 0, opacity: 1}, 85 | xPercent: -20, 86 | yPercent: -50, 87 | opacity: 0 88 | }, 0) 89 | .add(() => toggleContent()) 90 | .to(allItems, { // Animation for the last item 91 | duration: 1, 92 | stagger: -1*animationSettings.delayFactor, // Time between each item's animation 93 | 'clip-path': 'polygon(0% 0%, 0% 100%, 100% 100%, 0% 100%)', // Animating clip-path to different shape 94 | }, '>') 95 | .fromTo(contentInactive.DOM.el, { 96 | xPercent: 20, 97 | yPercent: -50, 98 | opacity: 0 99 | }, { 100 | duration: 1, 101 | opacity: 1, 102 | xPercent: 0, 103 | yPercent: 0 104 | }, `>-=${.8*animationSettings.duration}`) 105 | 106 | // Start the animation 107 | tl.play(); 108 | 109 | }); 110 | 111 | // Preloading all images specified by the selector 112 | preloadImages('.layers__item-img').then(() => { 113 | // Once images are preloaded, remove the 'loading' indicator/class from the body 114 | document.body.classList.remove('loading'); 115 | }); 116 | -------------------------------------------------------------------------------- /js/demo5/index.js: -------------------------------------------------------------------------------- 1 | // Importing necessary functions and classes from other files 2 | import { preloadImages } from '../utils.js'; // Utility function for preloading images 3 | import { Item } from '../item.js'; // Item class 4 | import { Content } from '../content.js'; // Content class 5 | 6 | // frame element 7 | const frameElement = document.querySelector('.frame'); 8 | 9 | // Selecting all elements with class 'layers__item' and converting NodeList to an array 10 | const DOMItems = [...document.querySelectorAll('.layers__item')]; 11 | const items = []; // Array to store instances of the Item class 12 | 13 | // Creating new instances of Item for each selected DOM element 14 | DOMItems.forEach(item => { 15 | items.push(new Item(item)); // Initializing a new object for each item 16 | }); 17 | 18 | // Selecting all elements with class 'content__inner' and converting NodeList to an array 19 | const DOMContentSections = [...document.querySelectorAll('.content > .content__inner')]; 20 | const contents = []; // Array to store instances of the Content class 21 | 22 | // Creating new instances of Content for each selected DOM element 23 | DOMContentSections.forEach(content => { 24 | contents.push(new Content(content)); // Initializing a new object for each content 25 | }); 26 | 27 | // Toggle the "hidden" class between two content elements 28 | const toggleContent = () => { 29 | // Assuming there are only two content elements 30 | const [content1, content2] = contents; 31 | 32 | // Toggle the 'hidden' class on the first content element 33 | if (content1.DOM.el.classList.contains('hidden')) { 34 | content1.DOM.el.classList.remove('hidden'); 35 | content2.DOM.el.classList.add('hidden'); 36 | } else { 37 | content1.DOM.el.classList.add('hidden'); 38 | content2.DOM.el.classList.remove('hidden'); 39 | } 40 | }; 41 | 42 | // GSAP timeline 43 | let tl = null; 44 | 45 | // Setting up the animation properties 46 | const animationSettings = { 47 | duration: 1.4, // Duration of the animation 48 | ease: 'power3.inOut', // Type of easing to use for the animation transition 49 | delayFactor: 0.14 // Delay between each item's animation 50 | }; 51 | 52 | // Event listener for click events on the document 53 | document.addEventListener('click', event => { 54 | // Check if the timeline is currently active (running) 55 | if (tl && tl.isActive() || frameElement.contains(event.target)) { 56 | return false; // Don't start a new animation 57 | } 58 | 59 | // The currently active content element 60 | const contentActive = contents.find(content => !content.DOM.el.classList.contains('hidden')); 61 | 62 | // Assuming there are only two content elements 63 | const contentInactive = contents.find(content => content !== contentActive); 64 | 65 | // Mapping each Item object to its actual DOM element for the animation 66 | const allItems = items.map(item => item.DOM.el); 67 | 68 | // Isolating the last item's DOM element for a separate animation effect 69 | const lastItem = items[items.length - 1].DOM.el; 70 | 71 | // Mapping each Item object to its 'inner' property (inner image) 72 | const allInnerItems = items.map(item => item.DOM.inner); 73 | 74 | const lastInner = items[items.length - 1].DOM.inner; 75 | 76 | // Creating a new GSAP timeline for managing a sequence of animations 77 | tl = gsap.timeline({ 78 | paused: true, // Create the timeline in a paused state 79 | defaults: { // Default settings applied to all animations within this timeline 80 | duration: animationSettings.duration, 81 | ease: animationSettings.ease, 82 | } 83 | }) 84 | .fromTo(allItems, { // Initial animation state 85 | opacity: 1, // Fully visible 86 | 'clip-path': 'polygon(45% 50%, 55% 50%, 50% 50%, 50% 50%)' // CSS clip-path shape 87 | }, { // Animation target state 88 | stagger: { // Settings for staggering animations for each item 89 | each: animationSettings.delayFactor, // Time between each item's animation 90 | onComplete: function() { // Callback after each item finishes animating 91 | const targetElement = this.targets()[0]; // The element that just finished animating 92 | // Determining the index of the animated element within the original DOM NodeList 93 | const index = DOMItems.indexOf(targetElement); 94 | if ( index ) { // If the element is not the first one (index 0) 95 | // Set the opacity of the previous element to 0 96 | gsap.set(items[index-1].DOM.el, {opacity: 0}); 97 | } 98 | }, 99 | }, 100 | 'clip-path': 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)', // Target shape of the clip-path 101 | }, 0) 102 | .fromTo(allInnerItems, { // Starting state for 'inner' elements' animation 103 | scale: 1.2, 104 | filter: 'brightness(30%)' // CSS filters to adjust color 105 | }, { // Animation target state 106 | stagger: animationSettings.delayFactor, // Stagger settings similar to above 107 | filter: 'brightness(100%)' // Full brightness 108 | }, 0) 109 | .to(contentActive.DOM.el, { 110 | startAt: {scale: 1}, 111 | scale: 1.4, 112 | onComplete: () => gsap.to(contentActive.DOM.el, {scale: 1}) 113 | }, 0) 114 | .add((() => toggleContent())) 115 | .fromTo([contentInactive.DOM.title, contentInactive.DOM.description], { 116 | yPercent: -100 117 | }, { 118 | ease: 'expo', // Different easing effect 119 | stagger: 0.05, 120 | yPercent: 0 121 | }) 122 | .to(lastItem, { // Animation for the last item 123 | duration: 1, 124 | ease: 'expo', 125 | 'clip-path': 'polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)', // Animating clip-path to different shape 126 | onComplete: () => gsap.set(lastItem, {opacity: 0}) // After animation, hide the last item 127 | }, '<') 128 | .to(lastInner, { 129 | duration: 1.4, 130 | ease: 'expo', 131 | scale: 2 132 | }, '<'); 133 | 134 | // Start the animation 135 | tl.play(); 136 | 137 | }); 138 | 139 | // Preloading all images specified by the selector 140 | preloadImages('.layers__item-img').then(() => { 141 | // Once images are preloaded, remove the 'loading' indicator/class from the body 142 | document.body.classList.remove('loading'); 143 | }); 144 | -------------------------------------------------------------------------------- /js/demo6/index.js: -------------------------------------------------------------------------------- 1 | // Importing necessary functions and classes from other files 2 | import { preloadImages } from '../utils.js'; // Utility function for preloading images 3 | import { Item } from '../item.js'; // Item class 4 | import { Content } from '../content.js'; // Content class 5 | 6 | // frame element 7 | const frameElement = document.querySelector('.frame'); 8 | 9 | // Selecting the element with class 'layers' 10 | const DOMlayers = document.querySelector('.layers'); 11 | // Selecting all elements with class 'layers__item' and converting NodeList to an array 12 | const DOMItems = [...DOMlayers.querySelectorAll('.layers__item')]; 13 | const items = []; // Array to store instances of the Item class 14 | 15 | // Creating new instances of Item for each selected DOM element 16 | DOMItems.forEach(item => { 17 | items.push(new Item(item)); // Initializing a new object for each item 18 | }); 19 | 20 | // Selecting all elements with class 'content__inner' and converting NodeList to an array 21 | const DOMContentSections = [...document.querySelectorAll('.content > .content__inner')]; 22 | const contents = []; // Array to store instances of the Content class 23 | 24 | // Creating new instances of Content for each selected DOM element 25 | DOMContentSections.forEach(content => { 26 | contents.push(new Content(content)); // Initializing a new object for each content 27 | }); 28 | 29 | // Toggle the "hidden" class between two content elements 30 | const toggleContent = () => { 31 | // Assuming there are only two content elements 32 | const [content1, content2] = contents; 33 | 34 | // Toggle the 'hidden' class on the first content element 35 | if (content1.DOM.el.classList.contains('hidden')) { 36 | content1.DOM.el.classList.remove('hidden'); 37 | content2.DOM.el.classList.add('hidden'); 38 | } else { 39 | content1.DOM.el.classList.add('hidden'); 40 | content2.DOM.el.classList.remove('hidden'); 41 | } 42 | }; 43 | 44 | // GSAP timeline 45 | let tl = null; 46 | 47 | // Setting up the animation properties 48 | const animationSettings = { 49 | duration: 0.9, // Duration of the animation 50 | ease: 'power2.inOut', // Type of easing to use for the animation transition 51 | delayFactor: 0.08 // Delay between each item's animation 52 | }; 53 | 54 | // Event listener for click events on the document 55 | document.addEventListener('click', event => { 56 | // Check if the timeline is currently active (running) 57 | if (tl && tl.isActive() || frameElement.contains(event.target)) { 58 | return false; // Don't start a new animation 59 | } 60 | 61 | // The currently active content element 62 | const contentActive = contents.find(content => !content.DOM.el.classList.contains('hidden')); 63 | 64 | // Assuming there are only two content elements 65 | const contentInactive = contents.find(content => content !== contentActive); 66 | 67 | // Mapping each Item object to its actual DOM element for the animation 68 | const allItems = items.map(item => item.DOM.el); 69 | 70 | // Creating a new GSAP timeline for managing a sequence of animations 71 | tl = gsap.timeline({ 72 | paused: true, // Create the timeline in a paused state 73 | defaults: { // Default settings applied to all animations within this timeline 74 | duration: animationSettings.duration, 75 | ease: animationSettings.ease, 76 | } 77 | }) 78 | .fromTo(DOMlayers, { 79 | scale: 0.8 80 | }, { 81 | duration: animationSettings.duration + animationSettings.delayFactor*items.length, 82 | scale: 1 83 | }, 0) 84 | .fromTo(allItems, { // Initial animation state 85 | opacity: 1, // Fully visible 86 | 'clip-path': 'polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)' // CSS clip-path shape 87 | }, { // Animation target state 88 | stagger: animationSettings.delayFactor, 89 | 'clip-path': 'polygon(25% 0%, 75% 0%, 75% 100%, 25% 100%)', // Target shape of the clip-path 90 | }, 0) 91 | .to(contentActive.DOM.el, { 92 | startAt: {scale: 1, opacity: 1}, 93 | stagger: -0.04, 94 | scale: 1.2, 95 | opacity: 0 96 | }, 0) 97 | .add((() => toggleContent())) 98 | .to(allItems, { // Animation target state 99 | ease: 'power3', 100 | stagger: animationSettings.delayFactor, 101 | 'clip-path': 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)', // Target shape of the clip-path 102 | }, '>') 103 | .to(allItems, { // Animation target state 104 | stagger: -1*animationSettings.delayFactor*.7, 105 | 'clip-path': 'polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)', // Target shape of the clip-path 106 | }, '>-=0.2') 107 | .fromTo(contentInactive.DOM.el, { 108 | scale: 1.8, 109 | opacity: 0 110 | }, { 111 | duration: 1.2, 112 | ease: 'elastic.out(.4)', // Different easing effect 113 | scale: 1, 114 | opacity: 1, 115 | }, `>-=${.6*animationSettings.duration}`) 116 | 117 | // Start the animation 118 | tl.play(); 119 | }); 120 | 121 | // Preloading all images specified by the selector 122 | preloadImages('.layers__item-img').then(() => { 123 | // Once images are preloaded, remove the 'loading' indicator/class from the body 124 | document.body.classList.remove('loading'); 125 | }); 126 | -------------------------------------------------------------------------------- /js/gsap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * GSAP 3.12.2 3 | * https://greensock.com 4 | * 5 | * @license Copyright 2023, GreenSock. All rights reserved. 6 | * Subject to the terms at https://greensock.com/standard-license or for Club GreenSock members, the agreement issued with that membership. 7 | * @author: Jack Doyle, jack@greensock.com 8 | */ 9 | 10 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function r(t){return"string"==typeof t}function s(t){return"function"==typeof t}function t(t){return"number"==typeof t}function u(t){return void 0===t}function v(t){return"object"==typeof t}function w(t){return!1!==t}function x(){return"undefined"!=typeof window}function y(t){return s(t)||r(t)}function P(t){return(i=yt(t,ot))&&Ee}function Q(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function R(t,e){return!e&&console.warn(t)}function S(t,e){return t&&(ot[t]=e)&&i&&(i[t]=e)||ot}function T(){return 0}function ea(t){var e,r,i=t[0];if(v(i)||s(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=gt.length;r--&&!gt[r].targetTest(i););e=gt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Vt(t[r],e)))||t.splice(r,1);return t}function fa(t){return t._gsap||ea(Ot(t))[0]._gsap}function ga(t,e,r){return(r=t[e])&&s(r)?t[e]():u(r)&&t.getAttribute&&t.getAttribute(e)||r}function ha(t,e){return(t=t.split(",")).forEach(e)||t}function ia(t){return Math.round(1e5*t)/1e5||0}function ja(t){return Math.round(1e7*t)/1e7||0}function ka(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function la(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++ia;)s=s._prev;return s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[i]=e,e._prev=s,e.parent=e._dp=t,e}function ya(t,e,r,i){void 0===r&&(r="_first"),void 0===i&&(i="_last");var n=e._prev,a=e._next;n?n._next=a:t[r]===e&&(t[r]=a),a?a._prev=n:t[i]===e&&(t[i]=n),e._next=e._prev=e.parent=null}function za(t,e){t.parent&&(!e||t.parent.autoRemoveChildren)&&t.parent.remove&&t.parent.remove(t),t._act=0}function Aa(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var r=t;r;)r._dirty=1,r=r.parent;return t}function Ca(t,e,r,i){return t._startAt&&(L?t._startAt.revert(ht):t.vars.immediateRender&&!t.vars.autoRevert||t._startAt.render(e,!0,i))}function Ea(t){return t._repeat?Tt(t._tTime,t=t.duration()+t._rDelay)*t:0}function Ga(t,e){return(t-e._start)*e._ts+(0<=e._ts?0:e._dirty?e.totalDuration():e._tDur)}function Ha(t){return t._end=ja(t._start+(t._tDur/Math.abs(t._ts||t._rts||X)||0))}function Ia(t,e){var r=t._dp;return r&&r.smoothChildTiming&&t._ts&&(t._start=ja(r._time-(0X)&&e.render(r,!0)),Aa(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur(n=Math.abs(n))&&(a=i,o=n);return a}function tb(t){return za(t),t.scrollTrigger&&t.scrollTrigger.kill(!!L),t.progress()<1&&At(t,"onInterrupt"),t}function wb(t){if(x()&&t){var e=(t=!t.name&&t.default||t).name,r=s(t),i=e&&!r&&t.init?function(){this._props=[]}:t,n={init:T,render:he,add:Qt,kill:ce,modifier:fe,rawVars:0},a={targetTest:0,get:0,getSetter:ne,aliases:{},register:0};if(Ft(),t!==i){if(pt[e])return;qa(i,qa(ua(t,n),a)),yt(i.prototype,yt(n,ua(t,a))),pt[i.prop=e]=i,t.targetTest&&(gt.push(i),ft[e]=1),e=("css"===e?"CSS":e.charAt(0).toUpperCase()+e.substr(1))+"Plugin"}S(e,i),t.register&&t.register(Ee,i,_e)}else t&&Ct.push(t)}function zb(t,e,r){return(6*(t+=t<0?1:1>16,e>>8&St,e&St]:0:Et.black;if(!p){if(","===e.substr(-1)&&(e=e.substr(0,e.length-1)),Et[e])p=Et[e];else if("#"===e.charAt(0)){if(e.length<6&&(e="#"+(n=e.charAt(1))+n+(a=e.charAt(2))+a+(s=e.charAt(3))+s+(5===e.length?e.charAt(4)+e.charAt(4):"")),9===e.length)return[(p=parseInt(e.substr(1,6),16))>>16,p>>8&St,p&St,parseInt(e.substr(7),16)/255];p=[(e=parseInt(e.substr(1),16))>>16,e>>8&St,e&St]}else if("hsl"===e.substr(0,3))if(p=d=e.match(tt),r){if(~e.indexOf("="))return p=e.match(et),i&&p.length<4&&(p[3]=1),p}else o=+p[0]%360/360,u=p[1]/100,n=2*(h=p[2]/100)-(a=h<=.5?h*(u+1):h+u-h*u),3=U?u.endTime(!1):t._dur;return r(e)&&(isNaN(e)||e in o)?(a=e.charAt(0),s="%"===e.substr(-1),n=e.indexOf("="),"<"===a||">"===a?(0<=n&&(e=e.replace(/=/,"")),("<"===a?u._start:u.endTime(0<=u._repeat))+(parseFloat(e.substr(1))||0)*(s?(n<0?u:i).totalDuration()/100:1)):n<0?(e in o||(o[e]=h),o[e]):(a=parseFloat(e.charAt(n-1)+e.substr(n+1)),s&&i&&(a=a/100*($(i)?i[0]:i).totalDuration()),1=r&&te)return i;i=i._next}else for(i=t._last;i&&i._start>=r;){if("isPause"===i.data&&i._start=n._start)&&n._ts&&h!==n){if(n.parent!==this)return this.render(t,e,r);if(n.render(0=this.totalDuration()||!v&&_)&&(f!==this._start&&Math.abs(l)===Math.abs(this._ts)||this._lock||(!t&&g||!(v===m&&0=i&&(a instanceof Zt?e&&n.push(a):(r&&n.push(a),t&&n.push.apply(n,a.getChildren(!0,e,r)))),a=a._next;return n},e.getById=function getById(t){for(var e=this.getChildren(1,1,1),r=e.length;r--;)if(e[r].vars.id===t)return e[r]},e.remove=function remove(t){return r(t)?this.removeLabel(t):s(t)?this.killTweensOf(t):(ya(this,t),t===this._recent&&(this._recent=this._last),Aa(this))},e.totalTime=function totalTime(t,e){return arguments.length?(this._forcing=1,!this._dp&&this._ts&&(this._start=ja(Rt.time-(0r:!r||s.isActive())&&n.push(s):(i=s.getTweensOf(a,r)).length&&n.push.apply(n,i),s=s._next;return n},e.tweenTo=function tweenTo(t,e){e=e||{};var r,i=this,n=xt(i,t),a=e.startAt,s=e.onStart,o=e.onStartParams,u=e.immediateRender,h=Zt.to(i,qa({ease:e.ease||"none",lazy:!1,immediateRender:!1,time:n,overwrite:"auto",duration:e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale())||X,onStart:function onStart(){if(i.pause(),!r){var t=e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale());h._dur!==t&&Ra(h,t,0,1).render(h._time,!0,!0),r=1}s&&s.apply(h,o||[])}},e));return u?h.render(0):h},e.tweenFromTo=function tweenFromTo(t,e,r){return this.tweenTo(e,qa({startAt:{time:xt(this,t)}},r))},e.recent=function recent(){return this._recent},e.nextLabel=function nextLabel(t){return void 0===t&&(t=this._time),rb(this,xt(this,t))},e.previousLabel=function previousLabel(t){return void 0===t&&(t=this._time),rb(this,xt(this,t),1)},e.currentLabel=function currentLabel(t){return arguments.length?this.seek(t,!0):this.previousLabel(this._time+X)},e.shiftChildren=function shiftChildren(t,e,r){void 0===r&&(r=0);for(var i,n=this._first,a=this.labels;n;)n._start>=r&&(n._start+=t,n._end+=t),n=n._next;if(e)for(i in a)a[i]>=r&&(a[i]+=t);return Aa(this)},e.invalidate=function invalidate(t){var e=this._first;for(this._lock=0;e;)e.invalidate(t),e=e._next;return i.prototype.invalidate.call(this,t)},e.clear=function clear(t){void 0===t&&(t=!0);for(var e,r=this._first;r;)e=r._next,this.remove(r),r=e;return this._dp&&(this._time=this._tTime=this._pTime=0),t&&(this.labels={}),Aa(this)},e.totalDuration=function totalDuration(t){var e,r,i,n=0,a=this,s=a._last,o=U;if(arguments.length)return a.timeScale((a._repeat<0?a.duration():a.totalDuration())/(a.reversed()?-t:t));if(a._dirty){for(i=a.parent;s;)e=s._prev,s._dirty&&s.totalDuration(),o<(r=s._start)&&a._sort&&s._ts&&!a._lock?(a._lock=1,Ka(a,s,r-s._delay,1)._lock=0):o=r,r<0&&s._ts&&(n-=r,(!i&&!a._dp||i&&i.smoothChildTiming)&&(a._start+=r/a._ts,a._time-=r,a._tTime-=r),a.shiftChildren(-r,!1,-Infinity),o=0),s._end>n&&s._ts&&(n=s._end),s=e;Ra(a,a===I&&a._time>n?a._time:n,1,1),a._dirty=0}return a._tDur},Timeline.updateRoot=function updateRoot(t){if(I._ts&&(na(I,Ga(t,I)),f=Rt.frame),Rt.frame>=mt){mt+=q.autoSleep||120;var e=I._first;if((!e||!e._ts)&&q.autoSleep&&Rt._listeners.length<2){for(;e&&!e._ts;)e=e._next;e||Rt.sleep()}}},Timeline}(Ut);qa(Xt.prototype,{_lock:0,_hasPause:0,_forcing:0});function ac(t,e,i,n,a,o){var u,h,l,f;if(pt[t]&&!1!==(u=new pt[t]).init(a,u.rawVars?e[t]:function _processVars(t,e,i,n,a){if(s(t)&&(t=Kt(t,a,e,i,n)),!v(t)||t.style&&t.nodeType||$(t)||Z(t))return r(t)?Kt(t,a,e,i,n):t;var o,u={};for(o in t)u[o]=Kt(t[o],a,e,i,n);return u}(e[t],n,a,o,i),i,n,o)&&(i._pt=h=new _e(i._pt,a,t,0,1,u.render,u,0,u.priority),i!==c))for(l=i._ptLookup[i._targets.indexOf(a)],f=u._props.length;f--;)l[u._props[f]]=h;return u}function gc(t,r,e,i){var n,a,s=r.ease||i||"power1.inOut";if($(r))a=e[t]||(e[t]=[]),r.forEach(function(t,e){return a.push({t:e/(r.length-1)*100,v:t,e:s})});else for(n in r)a=e[n]||(e[n]=[]),"ease"===n||a.push({t:parseFloat(t),v:r[n],e:s})}var Nt,Wt,Qt=function _addPropTween(t,e,i,n,a,o,u,h,l,f){s(n)&&(n=n(a||0,t,o));var c,d=t[e],p="get"!==i?i:s(d)?l?t[e.indexOf("set")||!s(t["get"+e.substr(3)])?e:"get"+e.substr(3)](l):t[e]():d,_=s(d)?l?re:te:$t;if(r(n)&&(~n.indexOf("random(")&&(n=ob(n)),"="===n.charAt(1)&&(!(c=ka(p,n)+(Ya(p)||0))&&0!==c||(n=c))),!f||p!==n||Wt)return isNaN(p*n)||""===n?(d||e in t||Q(e,n),function _addComplexStringPropTween(t,e,r,i,n,a,s){var o,u,h,l,f,c,d,p,_=new _e(this._pt,t,e,0,1,ue,null,n),m=0,g=0;for(_.b=r,_.e=i,r+="",(d=~(i+="").indexOf("random("))&&(i=ob(i)),a&&(a(p=[r,i],t,e),r=p[0],i=p[1]),u=r.match(it)||[];o=it.exec(i);)l=o[0],f=i.substring(m,o.index),h?h=(h+1)%5:"rgba("===f.substr(-5)&&(h=1),l!==u[g++]&&(c=parseFloat(u[g-1])||0,_._pt={_next:_._pt,p:f||1===g?f:",",s:c,c:"="===l.charAt(1)?ka(c,l)-c:parseFloat(l)-c,m:h&&h<4?Math.round:0},m=it.lastIndex);return _.c=m")}),s.duration();else{for(l in u={},x)"ease"===l||"easeEach"===l||gc(l,x[l],u,x.easeEach);for(l in u)for(C=u[l].sort(function(t,e){return t.t-e.t}),o=D=0;o=t._tDur||e<0)&&t.ratio===u&&(u&&za(t,1),r||L||(At(t,u?"onComplete":"onReverseComplete",!0),t._prom&&t._prom()))}else t._zTime||(t._zTime=e)}(this,t,e,r);return this},e.targets=function targets(){return this._targets},e.invalidate=function invalidate(t){return t&&this.vars.runBackwards||(this._startAt=0),this._pt=this._op=this._onUpdate=this._lazy=this.ratio=0,this._ptLookup=[],this.timeline&&this.timeline.invalidate(t),z.prototype.invalidate.call(this,t)},e.resetTo=function resetTo(t,e,r,i){d||Rt.wake(),this._ts||this.play();var n,a=Math.min(this._dur,(this._dp._time-this._start)*this._ts);return this._initted||Gt(this,a),n=this._ease(a/this._dur),function _updatePropTweens(t,e,r,i,n,a,s){var o,u,h,l,f=(t._pt&&t._ptCache||(t._ptCache={}))[e];if(!f)for(f=t._ptCache[e]=[],h=t._ptLookup,l=t._targets.length;l--;){if((o=h[l][e])&&o.d&&o.d._pt)for(o=o.d._pt;o&&o.p!==e&&o.fp!==e;)o=o._next;if(!o)return Wt=1,t.vars[e]="+=0",Gt(t,s),Wt=0,1;f.push(o)}for(l=f.length;l--;)(o=(u=f[l])._pt||u).s=!i&&0!==i||n?o.s+(i||0)+a*o.c:i,o.c=r-o.s,u.e&&(u.e=ia(r)+Ya(u.e)),u.b&&(u.b=o.s+Ya(u.b))}(this,t,e,r,i,n,a)?this.resetTo(t,e,r,i):(Ia(this,0),this.parent||xa(this._dp,this,"_first","_last",this._dp._sort?"_start":0),this.render(0))},e.kill=function kill(t,e){if(void 0===e&&(e="all"),!(t||e&&"all"!==e))return this._lazy=this._pt=0,this.parent?tb(this):this;if(this.timeline){var i=this.timeline.totalDuration();return this.timeline.killTweensOf(t,e,Nt&&!0!==Nt.vars.overwrite)._first||tb(this),this.parent&&i!==this.timeline.totalDuration()&&Ra(this,this._dur*this.timeline._tDur/i,0,1),this}var n,a,s,o,u,h,l,f=this._targets,c=t?Ot(t):f,d=this._ptLookup,p=this._pt;if((!e||"all"===e)&&function _arraysMatch(t,e){for(var r=t.length,i=r===e.length;i&&r--&&t[r]===e[r];);return r<0}(f,c))return"all"===e&&(this._pt=0),tb(this);for(n=this._op=this._op||[],"all"!==e&&(r(e)&&(u={},ha(e,function(t){return u[t]=1}),e=u),e=function _addAliasesToVars(t,e){var r,i,n,a,s=t[0]?fa(t[0]).harness:0,o=s&&s.aliases;if(!o)return e;for(i in r=yt({},e),o)if(i in r)for(n=(a=o[i].split(",")).length;n--;)r[a[n]]=r[i];return r}(f,e)),l=f.length;l--;)if(~c.indexOf(f[l]))for(u in a=d[l],"all"===e?(n[l]=e,o=a,s={}):(s=n[l]=n[l]||{},o=e),o)(h=a&&a[u])&&("kill"in h.d&&!0!==h.d.kill(u)||ya(this,h,"_pt"),delete a[u]),"all"!==s&&(s[u]=1);return this._initted&&!this._pt&&p&&tb(this),this},Tween.to=function to(t,e,r){return new Tween(t,e,r)},Tween.from=function from(t,e){return Va(1,arguments)},Tween.delayedCall=function delayedCall(t,e,r,i){return new Tween(e,0,{immediateRender:!1,lazy:!1,overwrite:!1,delay:t,onComplete:e,onReverseComplete:e,onCompleteParams:r,onReverseCompleteParams:r,callbackScope:i})},Tween.fromTo=function fromTo(t,e,r){return Va(2,arguments)},Tween.set=function set(t,e){return e.duration=0,e.repeatDelay||(e.repeat=0),new Tween(t,e)},Tween.killTweensOf=function killTweensOf(t,e,r){return I.killTweensOf(t,e,r)},Tween}(Ut);qa(Zt.prototype,{_targets:[],_lazy:0,_startAt:0,_op:0,_onInit:0}),ha("staggerTo,staggerFrom,staggerFromTo",function(r){Zt[r]=function(){var t=new Xt,e=Mt.call(arguments,0);return e.splice("staggerFromTo"===r?5:4,0,0),t[r].apply(t,e)}});function oc(t,e,r){return t.setAttribute(e,r)}function wc(t,e,r,i){i.mSet(t,e,i.m.call(i.tween,r,i.mt),i)}var $t=function _setterPlain(t,e,r){return t[e]=r},te=function _setterFunc(t,e,r){return t[e](r)},re=function _setterFuncWithParam(t,e,r,i){return t[e](i.fp,r)},ne=function _getSetter(t,e){return s(t[e])?te:u(t[e])&&t.setAttribute?oc:$t},ae=function _renderPlain(t,e){return e.set(e.t,e.p,Math.round(1e6*(e.s+e.c*t))/1e6,e)},se=function _renderBoolean(t,e){return e.set(e.t,e.p,!!(e.s+e.c*t),e)},ue=function _renderComplexString(t,e){var r=e._pt,i="";if(!t&&e.b)i=e.b;else if(1===t&&e.e)i=e.e;else{for(;r;)i=r.p+(r.m?r.m(r.s+r.c*t):Math.round(1e4*(r.s+r.c*t))/1e4)+i,r=r._next;i+=e.c}e.set(e.t,e.p,i,e)},he=function _renderPropTweens(t,e){for(var r=e._pt;r;)r.r(t,r.d),r=r._next},fe=function _addPluginModifier(t,e,r,i){for(var n,a=this._pt;a;)n=a._next,a.p===i&&a.modifier(t,e,r),a=n},ce=function _killPropTweensOf(t){for(var e,r,i=this._pt;i;)r=i._next,i.p===t&&!i.op||i.op===t?ya(this,i,"_pt"):i.dep||(e=1),i=r;return!e},pe=function _sortPropTweensByPriority(t){for(var e,r,i,n,a=t._pt;a;){for(e=a._next,r=i;r&&r.pr>a.pr;)r=r._next;(a._prev=r?r._prev:n)?a._prev._next=a:i=a,(a._next=r)?r._prev=a:n=a,a=e}t._pt=i},_e=(PropTween.prototype.modifier=function modifier(t,e,r){this.mSet=this.mSet||this.set,this.set=wc,this.m=t,this.mt=r,this.tween=e},PropTween);function PropTween(t,e,r,i,n,a,s,o,u){this.t=e,this.s=i,this.c=n,this.p=r,this.r=a||ae,this.d=s||this,this.set=o||$t,this.pr=u||0,(this._next=t)&&(t._prev=this)}ha(vt+"parent,duration,ease,delay,overwrite,runBackwards,startAt,yoyo,immediateRender,repeat,repeatDelay,data,paused,reversed,lazy,callbackScope,stringFilter,id,yoyoEase,stagger,inherit,repeatRefresh,keyframes,autoRevert,scrollTrigger",function(t){return ft[t]=1}),ot.TweenMax=ot.TweenLite=Zt,ot.TimelineLite=ot.TimelineMax=Xt,I=new Xt({sortChildren:!1,defaults:V,autoRemoveChildren:!0,id:"root",smoothChildTiming:!0}),q.stringFilter=Fb;function Ec(t){return(ye[t]||Te).map(function(t){return t()})}function Fc(){var t=Date.now(),o=[];2{setTimeout((()=>{this.progress(t,e,i)}))};this.images.forEach((function(e){e.once("progress",t),e.check()}))},n.prototype.progress=function(t,e,i){this.progressedCount++,this.hasAnyBroken=this.hasAnyBroken||!t.isLoaded,this.emitEvent("progress",[this,t,e]),this.jqDeferred&&this.jqDeferred.notify&&this.jqDeferred.notify(this,t),this.progressedCount===this.images.length&&this.complete(),this.options.debug&&s&&s.log(`progress: ${i}`,t,e)},n.prototype.complete=function(){let t=this.hasAnyBroken?"fail":"done";if(this.isComplete=!0,this.emitEvent(t,[this]),this.emitEvent("always",[this]),this.jqDeferred){let t=this.hasAnyBroken?"reject":"resolve";this.jqDeferred[t](this)}},h.prototype=Object.create(e.prototype),h.prototype.check=function(){this.getIsImageComplete()?this.confirm(0!==this.img.naturalWidth,"naturalWidth"):(this.proxyImage=new Image,this.img.crossOrigin&&(this.proxyImage.crossOrigin=this.img.crossOrigin),this.proxyImage.addEventListener("load",this),this.proxyImage.addEventListener("error",this),this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.proxyImage.src=this.img.currentSrc||this.img.src)},h.prototype.getIsImageComplete=function(){return this.img.complete&&this.img.naturalWidth},h.prototype.confirm=function(t,e){this.isLoaded=t;let{parentNode:i}=this.img,s="PICTURE"===i.nodeName?i:this.img;this.emitEvent("progress",[this,s,e])},h.prototype.handleEvent=function(t){let e="on"+t.type;this[e]&&this[e](t)},h.prototype.onload=function(){this.confirm(!0,"onload"),this.unbindEvents()},h.prototype.onerror=function(){this.confirm(!1,"onerror"),this.unbindEvents()},h.prototype.unbindEvents=function(){this.proxyImage.removeEventListener("load",this),this.proxyImage.removeEventListener("error",this),this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype=Object.create(h.prototype),d.prototype.check=function(){this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.img.src=this.url,this.getIsImageComplete()&&(this.confirm(0!==this.img.naturalWidth,"naturalWidth"),this.unbindEvents())},d.prototype.unbindEvents=function(){this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype.confirm=function(t,e){this.isLoaded=t,this.emitEvent("progress",[this,this.element,e])},n.makeJQueryPlugin=function(e){(e=e||t.jQuery)&&(i=e,i.fn.imagesLoaded=function(t,e){return new n(this,t,e).jqDeferred.promise(i(this))})},n.makeJQueryPlugin(),n})); -------------------------------------------------------------------------------- /js/item.js: -------------------------------------------------------------------------------- 1 | 2 | export class Item { 3 | // Class property initialization with default values. 4 | // The DOM property holds references to the main and inner elements of the Item component. 5 | DOM = { 6 | el: null, // Holds the reference to the main DOM element with class 'layers__item'. 7 | inner: null, // Holds the reference to the inner DOM element with class 'layers__item-img'. 8 | }; 9 | 10 | /** 11 | * Constructor for the Item class. Initializes the instance, sets up DOM references, and binds events. 12 | * @param {HTMLElement} DOM_el - The main DOM element for the Item, expected to have a child with class 'layers__item-img'. 13 | */ 14 | constructor(DOM_el) { 15 | // Assign the provided DOM element to the 'el' property of the 'DOM' object. 16 | this.DOM.el = DOM_el; 17 | this.DOM.inner = this.DOM.el.querySelector('.layers__item-img'); 18 | } 19 | } -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Preloads images specified by the CSS selector. 3 | * @function 4 | * @param {string} [selector='img'] - CSS selector for target images. 5 | * @returns {Promise} - Resolves when all specified images are loaded. 6 | */ 7 | const preloadImages = (selector = 'img') => { 8 | return new Promise((resolve) => { 9 | // The imagesLoaded library is used to ensure all images (including backgrounds) are fully loaded. 10 | imagesLoaded(document.querySelectorAll(selector), {background: true}, resolve); 11 | }); 12 | }; 13 | 14 | // Exporting utility functions for use in other modules. 15 | export { 16 | preloadImages, 17 | }; 18 | --------------------------------------------------------------------------------