1/** 2 * demo.js 3 * http://www.codrops.com 4 * 5 * Licensed under the MIT license. 6 * http://www.opensource.org/licenses/mit-license.php 7 * 8 * Copyright 2018, Codrops 9 * http://www.codrops.com 10 */ 11{ 12 // From http://www.quirksmode.org/js/events_properties.html#position 13 // Get the mouse position. 14 const getMousePos = (e) => { 15 let posx = 0; 16 let posy = 0; 17 if (!e) e = window.event; 18 if (e.pageX || e.pageY) { 19 posx = e.pageX; 20 posy = e.pageY; 21 } 22 else if (e.clientX || e.clientY) { 23 posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 24 posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 25 } 26 return { x : posx, y : posy } 27 }; 28 // Gets a random integer. 29 const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; 30 // Equation of a line (y = mx + b ). 31 const lineEq = (y2, y1, x2, x1, currentVal) => { 32 const m = (y2 - y1) / (x2 - x1); 33 const b = y1 - m * x1; 34 return m * currentVal + b; 35 }; 36 37 // Some random chars. 38 // const chars = ['$','%','#','&','=','*','a','b','c','d','e','f','g','h','
38i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','.',':',',','^']; 39 // const charsTotal = chars.length; 40 // 41 // // Randomize letters function. Used when navigating the slideshow to switch the curretn slide´s texts. 42 // const randomizeLetters = (letters) => { 43 // return new Promise((resolve, reject) => { 44 // const lettersTotal = letters.length; 45 // let cnt = 0; 46 // 47 // letters.forEach((letter, pos) => { 48 // let loopTimeout; 49 // const loop = () => { 50 // letter.innerHTML = chars[getRandomInt(0,charsTotal-1)]; 51 // loopTimeout = setTimeout(loop, getRandomInt(50,500)); 52 // }; 53 // loop(); 54 // 55 // const timeout = setTimeout(() => { 56 // clearTimeout(loopTimeout); 57 // letter.style.opacity = 1; 58 // letter.innerHTML = letter.dataset.initial; 59 // ++cnt; 60 // if ( cnt === lettersTotal ) { 61 // resolve(); 62 // } 63 // }, pos*lineEq(40,0,8,200,lettersTotal)); 64 // }); 65 // }); 66 // }; 67 68 // Hide each of the letters with random delays. Used when showing the current slide´s content. 69 // const disassembleLetters = (letters) => { 70 // return new Promise((resolve, reject) => { 71 // const lettersTotal = letters.length; 72 // let cnt = 0; 73 // 74 // letters.forEach((letter, pos) => { 75 // setTimeout(() => { 76 // letter.style.opacity = 0; 77 // ++cnt; 78 // if ( cnt === lettersTotal ) { 79 // resolve(); 80 // } 81 // }, pos*30); 82 // }); 83 // }); 84 // } 85 86 // The Slide class. 87 class Slide { 88 constructor(el) { 89 this.DOM = {el: el}; 90 // The image wrap element. 91 this.DOM.imgWrap = this.DOM.el.querySelector('.slide__img-wrap'); 92 // The image element. 93 this.DOM.img = this.DOM.imgWrap.querySelector('.slide__img'); 94 // The texts: the parent wrap, title, number and side text. 95 this.DOM.texts = { 96 wrap: this.DOM.el.querySelector('.slide__title-wrap'), 97 title: this.DOM.el.querySelector('.slide__title'), 98 number: this.DOM.el.querySelector('.slide__number'), 99 side: this.DOM.el.querySelector('.slide__side'), 100 }; 101 // Split the title and side texts into spans, one per letter. Sort these so we can later animate then with the 102 // randomizeLetters or disassembleLetters functions when navigating and showing the content. 103 // charming(this.DOM.texts.title); 104 // charming(this.DOM.texts.side); 105 // this.DOM.titleLetters = Array.from(this.DOM.texts.title.querySelectorAll('span')).sort(() => 0.5 - Math.random()); 106 // this.DOM.sideLetters = Array.from(this.DOM.texts.side.querySelectorAll('span')).sort(() => 0.5 - Math.random()); 107 // this.DOM.titleLetters.forEach(letter => letter.dataset.initial = letter.innerHTML); 108 // this.DOM.sideLetters.forEach(letter => letter.dataset.initial = letter.innerHTML); 109 // Calculate the sizes of the image wrap. 110 this.calcSizes(); 111 // And also the transforms needed per position. 112 // We have 5 different possible positions for a slide: center, bottom right, top left and outside the viewport (top left or bottom right). 113 this.calcTransforms(); 114 // Init/Bind events. 115 this.initEvents(); 116 } 117 // Gets the size of the image wrap. 118 calcSizes() { 119 this.width = this.DOM.imgWrap.offsetWidth; 120 this.height = this.DOM.imgWrap.offsetHeight; 121 } 122 // Gets the transforms per slide position. 123 calcTransforms() { 124 /* 125 Each position corresponds to the position of a given slide: 126 0: left top corner outside the viewport 127 1: left top corner (prev slide position) 128 2: center (current slide position) 129 3: right bottom corner (next slide position) 130 4: right bottom corner outside the viewport 131 5: left side, for when the content is shown 132 */ 133 this.transforms = [ 134 {x: -1*(winsize.width/2+this.width), y: -1*(winsize.height/2+this.height), rotation: -30}, 135 {x: -1*(winsize.width/2-this.width/3), y: -1*(winsize.height/2-this.height/3), rotation: 0}, 136 {x: 0, y: 0, rotation: 0}, 137 {x: winsize.width/2-this.width/3, y: winsize.height/2-this.height/3, rotation: 0}, 138 {x: winsize.width/2+this.width, y: winsize.height/2+this.height, rotation: 30}, 139 {x: -1*(winsize.width/2 - this.width/2 - winsize.width*0.075), y: 0, rotation: 0} 140 ]; 141 } 142 // Init events: 143 // Mouseevents for mousemove/tilt/scale on the current image, and window resize. 144 initEvents() { 145 this.mouseenterFn = () => { 146 if ( !this.isPositionedCenter() || !allowTilt ) return; 147 clearTimeout(this.mousetime); 148 this.mousetime = setTimeout(() => { 149 // Scale the image. 150 TweenMax.to(this.DOM.img, 0.8, { 151 ease: Power3.easeOut, 152 scale: 1.1 153 }); 154 }, 40); 155 }; 156 this.mousemoveFn = ev => requestAnimationFrame(() => { 157 // Tilt the current slide. 158 if ( !allowTilt || !this.isPositionedCenter() ) return; 159 this.tilt(ev); 160 }); 161 this.mouseleaveFn = (ev) => requestAnimationFrame(() => { 162 if ( !allowTilt || !this.isPositionedCenter() ) return; 163 clearTimeout(this.mousetime); 164 165 // Reset tilt and image scale. 166 TweenMax.to([this.DOM.imgWrap,this.DOM.texts.wrap], 1.8, { 167 ease: 'Power4.easeOut', 168 x: 0, 169 y: 0, 170 rotationX: 0, 171 rotationY: 0 172 }); 173 TweenMax.to(this.DOM.img, 1.8, { 174 ease: 'Power4.easeOut', 175 scale: 1 176 }); 177 }); 178 // When resizing the window recalculate size and transforms, since both will depend on the window size. 179 this.resizeFn = () => { 180 this.calcSizes(); 181 this.calcTransforms(); 182 }; 183 this.DOM.imgWrap.addEventListener('mouseenter', this.mouseenterFn); 184 this.DOM.imgWrap.addEventListener('mousemove', this.mousemoveFn); 185 this.DOM.imgWrap.addEventListener('mouseleave', this.mouseleaveFn); 186 window.addEventListener('resize', this.resizeFn); 187 } 188 // Tilt the image wrap and texts when mouse moving the current slide. 189 tilt(ev) { 190 const mousepos = getMousePos(ev); 191 // Document scrolls. 192 const docScrolls = { 193 left : document.body.scrollLeft + document.documentElement.scrollLeft, 194 top : document.body.scrollTop + document.documentElement.scrollTop 195 }; 196 const bounds = this.DOM.imgWrap.getBoundingClientRect();; 197 // Mouse position relative to the main element (this.DOM.el). 198 const relmousepos = { 199 x : mousepos.x - bounds.left - docScrolls.left, 200 y : mousepos.y - bounds.top - docScrolls.top 201 }; 202 203 // Move the element from -20 to 20 pixels in both x and y axis. 204 // Rotate the element from -15 to 15 degrees in both x and y axis. 205 let t = {x:[-20,20],y:[-20,20]}, 206 r = {x:[-15,15],y:[-15,15]}; 207 208 const transforms = { 209 translation : { 210 x: (t.x[1]-t.x[0])/bounds.width*relmousepos.x + t.x[0], 211 y: (t.y[1]-t.y[0])/bounds.height*relmousepos.y + t.y[0] 212 }, 213 rotation : { 214 x: (r.x[1]-r.x[0])/bounds.height*relmousepos.y + r.x[0], 215 y: (r.y[1]-r.y[0])/bounds.width*relmousepos.x + r.y[0] 216 } 217 }; 218 219 // Move the image wrap. 220 TweenMax.to(this.DOM.imgWrap, 1.5, { 221 ease: 'Power1.easeOut', 222 x: transforms.translation.x, 223 y: transforms.translation.y, 224 rotationX: transforms.rotation.x, 225 rotationY: transforms.rotation.y 226 }); 227 228 // Move the texts wrap. 229 TweenMax.to(this.DOM.texts.wrap, 1.5, { 230 ease: 'Power1.easeOut', 231 x: -1*transforms.translation.x, 232 y: -1*transforms.translation.y 233 }); 234 } 235 // Positions one slide (left, right or current) in the viewport. 236 position(pos) { 237 TweenMax.set(this.DOM.imgWrap, { 238 x: this.transforms[pos].x, 239 y: this.transforms[pos].y, 240 rotationX: 0, 241 rotationY: 0, 242 opacity: 1, 243 rotationZ: this.transforms[pos].rotation 244 }); 245 } 246 // Sets it as current. 247 setCurrent(isContentOpen) { 248 this.isCurrent = true; 249 this.DOM.el.classList.add('slide--current', 'slide--visible'); 250 // Position it on the current´s position. 251 this.position(isContentOpen ? 5 : 2); 252 } 253 // Position the slide on the left side. 254 setLeft(isContentOpen) { 255 this.isRight = this.isCurrent = false; 256 this.isLeft = true; 257 this.DOM.el.classList.add('slide--visible'); 258 // Position it on the left position. 259 this.position(isContentOpen ? 0 : 1); 260 } 261 // Position the slide on the right side. 262 setRight(isContentOpen) { 263 this.isLeft = this.isCurrent = false; 264 this.isRight = true; 265 this.DOM.el.classList.add('slide--visible'); 266 // Position it on the right position. 267 this.position(isContentOpen ? 4 : 3); 268 } 269 // Check if the slide is positioned on the right side (if it´s the next slide in the slideshow). 270 isPositionedRight() { 271 return this.isRight; 272 } 273 // Check if the slide is positioned on the left side (if it´s the previous slide in the slideshow). 274 isPositionedLeft() { 275 return this.isLeft; 276 } 277 // Check if the slide is the current one. 278 isPositionedCenter() { 279 return this.isCurrent; 280 } 281 // Reset classes and state. 282 reset() { 283 this.isRight = this.isLeft = this.isCurrent = false; 284 this.DOM.el.classList = 'slide'; 285 } 286 hide() { 287 TweenMax.set(this.DOM.imgWrap, {x:0, y:0, rotationX:0, rotationY:0, rotationZ:0, opacity:0}
287); 288 } 289 // Moves a slide to a specific position defined in settings.position. 290 // Also, moves it from position settings.from and if we need to reset the image scale when moving the slide then settings.resetImageScale should be true. 291 moveToPosition(settings) { 292 return new Promise((resolve, reject) => { 293 /* 294 Moves the slide to a specific position: 295 -2: left top corner outside the viewport 296 -1: left top corner (prev slide position) 297 0: center (current slide position) 298 1: right bottom corner (next slide position) 299 2: right bottom corner outside the viewport 300 3: left side, for when the content is shown 301 */ 302 TweenMax.to(this.DOM.imgWrap, .8, { 303 ease: Power4.easeInOut, 304 delay: settings.delay || 0, 305 startAt: settings.from !== undefined ? { 306 x: this.transforms[settings.from+2].x, 307 y: this.transforms[settings.from+2].y, 308 rotationX: 0, 309 rotationY: 0, 310 rotationZ: this.transforms[settings.from+2].rotation 311 } : {}, 312 x: this.transforms[settings.position+2].x, 313 y: this.transforms[settings.position+2].y, 314 rotationX: 0, 315 rotationY: 0, 316 rotationZ: this.transforms[settings.position+2].rotation, 317 onStart: settings.from !== undefined ? () => TweenMax.set(this.DOM.imgWrap, {opacity: 1}) : null, 318 onComplete: resolve 319 }); 320 321 // Reset image scale when showing the content of the current slide. 322 if ( settings.resetImageScale ) { 323 TweenMax.to(this.DOM.img, .8, { 324 ease: Power4.easeInOut, 325 scale: 1 326 }); 327 } 328 }); 329 } 330 // Hides the current slide´s texts. 331 hideTexts(animation = false) { 332 if ( animation ) { 333 disassembleLetters(this.DOM.titleLetters).then(() => TweenMax.set(this.DOM.texts.wrap, {opacity: 0})); 334 disassembleLetters(this.DOM.sideLetters).then(() => TweenMax.set(this.DOM.texts.side, {opacity: 0})); 335 } 336 else { 337 TweenMax.set(this.DOM.texts.wrap, {opacity: 0}); 338 TweenMax.set(this.DOM.texts.side, {opacity: 0}); 339 } 340 } 341 // Shows the current slide´s texts. 342 showTexts(animation = true) { 343 TweenMax.set(this.DOM.texts.wrap, {opacity: 1}); 344 TweenMax.set(this.DOM.texts.side, {opacity: 1}); 345 346 if ( animation ) { 347 // randomizeLetters(this.DOM.titleLetters); 348 // randomizeLetters(this.DOM.sideLetters); 349 TweenMax.to(this.DOM.texts.number, 0.6, { 350 ease: Elastic.easeOut.config(1,0.5), 351 startAt: {x: '-10%', opacity: 0}, 352 x: '0%', 353 opacity: 1 354 }); 355 } 356 } 357 } 358 359 // The Content class. Represents one content item per slide. 360 class Content { 361 constructor(el) { 362 this.DOM = {el: el}; 363 this.DOM.number = this.DOM.el.querySelector('.content__number'); 364 this.DOM.title = this.DOM.el.querySelector('.content__title'); 365 this.DOM.subtitle = this.DOM.el.querySelector('.content__subtitle'); 366 this.DOM.text = this.DOM.el.querySelector('.content__text'); 367 this.DOM.backCtrl = this.DOM.el.parentNode.querySelector('.content__close'); 368 this.DOM.backCtrl.addEventListener('click', () => slideshow.hideContent()); 369 } 370 show() { 371 this.DOM.el.classList.add('content__item--current'); 372 373 TweenMax.staggerTo([this.DOM.backCtrl,this.DOM.number,this.DOM.title,this.DOM.subtitle,this.DOM.text], 0.8, { 374 ease: Power4.easeOut, 375 delay: 0.4, 376 opacity: 1, 377 startAt: {y: 40}, 378 y: 0 379 }, 0.05); 380 } 381 hide() { 382 this.DOM.el.classList.remove('content__item--current'); 383 384 TweenMax.staggerTo([this.DOM.backCtrl,this.DOM.number,this.DOM.title,this.DOM.subtitle,this.DOM.text].reverse(), 0.3, { 385 ease: Power3.easeIn, 386 opacity: 0, 387 y: 10 388 }, 0.01); 389 } 390 } 391 392 // The Slideshow class. 393 class Slideshow { 394 constructor(el) { 395 this.DOM = {el: el}; 396 // The slides. 397 this.slides = []; 398 Array.from(this.DOM.el.querySelectorAll('.slide')).forEac
398h(slideEl => this.slides.push(new Slide(slideEl))); 399 // The total number of slides. 400 this.slidesTotal = this.slides.length; 401 // At least 4 slides to continue... 402 if ( this.slidesTotal < 4 ) { 403 return false; 404 } 405 // Current slide position. 406 this.current = 0; 407 this.DOM.deco = this.DOM.el.querySelector('.slideshow__deco'); 408 409 this.contents = []; 410 Array.from(document.querySelectorAll('.content > .content__item')).forEach(contentEl => this.contents.push(new Content(contentEl))); 411 412 // Set the current/next/previous slides. 413 this.render(); 414 this.currentSlide.showTexts(false); 415 // Init/Bind events. 416 this.initEvents(); 417 } 418 render() { 419 // The current, next, and previous slides. 420 this.currentSlide = this.slides[this.current]; 421 this.nextSlide = this.slides[this.current+1 <= this.slidesTotal-1 ? this.current+1 : 0]; 422 this.prevSlide = this.slides[this.current-1 >= 0 ? this.current-1 : this.slidesTotal-1]; 423 this.currentSlide.setCurrent(); 424 this.nextSlide.setRight(); 425 this.prevSlide.setLeft(); 426 } 427 initEvents() { 428 // Clicking the next and previous slide starts the navigation / clicking the current shows its content.. 429 this.clickFn = (slide) => { 430 if ( slide.isPositionedRight() ) { 431 this.navigate('next'); 432 } 433 else if ( slide.isPositionedLeft() ) { 434 this.navigate('prev'); 435 } 436 else { 437 this.showContent(); 438 } 439 }; 440 for (let slide of this.slides) { 441 slide.DOM.imgWrap.addEventListener('click', () => this.clickFn(slide)); 442 } 443 444 this.resizeFn = () => { 445 // Reposition the slides. 446 this.nextSlide.setRight(this.isContentOpen); 447 this.prevSlide.setLeft(this.isContentOpen); 448 this.currentSlide.setCurrent(this.isContentOpen); 449 450 if ( this.isContentOpen ) { 451 TweenMax.set(this.DOM.deco, { 452 scaleX: winsize.width/this.DOM.deco.offsetWidth, 453 scaleY: winsize.height/this.DOM.deco.offsetHeight, 454 x: -20, 455 y: 20 456 }); 457 } 458 }; 459 window.addEventListener('resize', this.resizeFn); 460 } 461 showContent() { 462 } 463 hideContent() { 464 } 465 // Animates the element behind the current slide. 466 bounceDeco(direction, delay) { 467 TweenMax.to(this.DOM.deco, .4, { 468 ease: 'Power2.easeIn', 469 delay: delay+delay*0.2, 470 x: direction === 'next' ? -40 : 40, 471 y: direction === 'next' ? -40 : 40, 472 onComplete: () => { 473 TweenMax.to(this.DOM.deco, 0.6, { 474 //ease: Elastic.easeOut.config(1, 0.65), 475 ease: 'Power2.easeOut', 476 x: 0, 477 y: 0 478 }); 479 } 480 }); 481 } 482 // Navigate the slideshow. 483 navigate(direction) { 484 // If animating return. 485 if ( this.isAnimating ) return; 486 this.isAnimating = true; 487 allowTilt = false; 488 489 const upcomingPos = direction === 'next' ? 490 this.current < this.slidesTotal-2 ? this.current+2 : Math.abs(this.slidesTotal-2-this.current): 491 this.current >= 2 ? this.current-2 : Math.abs(this.slidesTotal-2+this.current); 492 493 this.upcomingSlide = this.slides[upcomingPos]; 494 495 // Update current. 496 this.current = direction === 'next' ? 497 this.current < this.slidesTotal-1 ? this.current+1 : 0 : 498 this.current > 0 ? this.current-1 : this.slidesTotal-1; 499 500 // Move slides (the previous, current, next and upcoming slide). 501 this.prevSlide.moveToPosition({position: direction === 'next' ? -2 : 0, delay: direction === 'next' ? 0 : 0.14}).then(() => { 502 if ( direction === 'next' ) { 503 this.prevSlide.hide(); 504 } 505 }); 506 507 this.currentSlide.moveToPosition({position: direction === 'next' ? -1 : 1, delay: 0.07 }); 508 this.currentSlide.hideTexts(); 509 510 this.bounceDeco(direction, 0.07); 511 512 this.nextSlide.moveToPosition({position: direction === 'next' ? 0 : 2, delay: direction === 'next' ? 0.14 : 0 }).then(() => { 513 if ( direction === 'prev' ) { 514 this.nextSlide.hide(); 515 } 516 }); 517 518 if ( direction === 'next' ) { 519 this.nextSlide.showTexts(); 520 } 521 else { 522 this.prevSlide.showTexts(); 523 } 524 525 this.upcomingSlide.moveToPosition({position: direction === 'next' ? 1 : -1, from: direction === 'next' ? 2 : -2, delay: 0.21 }).then(() => { 526 // Reset classes. 527 [this.nextSlide,this.currentSlide,this.prevSlide].forEach(sli
527de => slide.reset()); 528 this.render(); 529 allowTilt = true; 530 this.isAnimating = false; 531 }); 532 } 533 } 534 535 // Window sizes. 536 let winsize; 537 const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 538 calcWinsize(); 539 window.addEventListener('resize', calcWinsize); 540 541 let allowTilt = true; 542 543 // Init slideshow. 544 const slideshow = new Slideshow(document.querySelector('.slideshow')); 545 546 // Preload all the images in the page.. 547 const loader = document.querySelector('.loader'); 548 imagesLoaded(document.querySelectorAll('.slide__img'), {background: true}, () => document.body.classList.remove('loading')); 549}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.