1/*! 2 * Jarallax v2.0.3 (https://github.com/nk-o/jarallax) 3 * Copyright 2022 nK <https://nkdev.info> 4 * Licensed under MIT (https://github.com/nk-o/jarallax/blob/master/LICENSE) 5 */ 6(function (global, factory) { 7 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 typeof define === 'function' && define.amd ? define(factory) : 9 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.jarallax = factory()); 10})(this, (function () { 'use strict'; 11 12 function ready(callback) { 13 if ('complete' === document.readyState || 'interactive' === document.readyState) { 14 // Already ready or interactive, execute callback 15 callback(); 16 } else { 17 document.addEventListener('DOMContentLoaded', callback, { 18 capture: true, 19 once: true, 20 passive: true 21 }); 22 } 23 } 24 25 /* eslint-disable import/no-mutable-exports */ 26 27 /* eslint-disable no-restricted-globals */ 28 let win; 29 30 if ('undefined' !== typeof window) { 31 win = window; 32 } else if ('undefined' !== typeof global) { 33 win = global; 34 } else if ('undefined' !== typeof self) { 35 win = self; 36 } else { 37 win = {}; 38 } 39 40 var global$1 = win; 41 42 const { 43 navigator 44 } = global$1; 45 const isMobile = /*#__PURE__*/ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 46 let $deviceHelper; 47 /** 48 * The most popular mobile browsers changes height after page scroll and this generates image jumping. 49 * We can fix it using this workaround with vh units. 50 */ 51 52 function getDeviceHeight() { 53 if (!$deviceHelper && document.body) { 54 $deviceHelper = document.createElement('div'); 55 $deviceHelper.style.cssText = 'position: fixed; top: -9999px; left: 0; height: 100vh; width: 0;'; 56 document.body.appendChild($deviceHelper); 57 } 58 59 return ($deviceHelper ? $deviceHelper.clientHeight : 0) || global$1.innerHeight || document.documentElement.clientHeight; 60 } // Window height data 61 62 63 let wndH; 64 65 function updateWndVars() { 66 if (isMobile) { 67 wndH = getDeviceHeight(); 68 } else { 69 wndH = global$1.innerHeight || document.documentElement.clientHeight; 70 } 71 } 72 73 updateWndVars(); 74 global$1.addEventListener('resize', updateWndVars); 75 global$1.addEventListener('orientationchange', updateWndVars); 76 global$1.addEventListener('load', updateWndVars); 77 ready(() => { 78 updateWndVars(); 79 }); // list with all jarallax instances 80 // need to render all in one scroll/resize event 81 82 const jarallaxList = []; // get all parents of the element. 83 84 function getParents(elem) { 85 const parents = []; 86 87 while (null !== elem.parentElement) { 88 elem = elem.parentElement; 89 90 if (1 === elem.nodeType) { 91 parents.push(elem); 92 } 93 } 94 95 return parents; 96 } 97 98 function updateParallax() { 99 if (!jarallaxList.length) { 100 return; 101 } 102 103 jarallaxList.forEach((data, k) => { 104 const { 105 instance, 106 oldData 107 } = data; 108 const clientRect = instance.$item.getBoundingClientRect(); 109 const newData = { 110 width: clientRect.width, 111 height: clientRect.height, 112 top: clientRect.top, 113 bottom: clientRect.bottom, 114 wndW: global$1.innerWidth, 115 wndH 116 }; 117 const isResized = !oldData || oldData.wndW !== newData.wndW || oldData.wndH !== newData.wndH || oldData.width !== newData.width || oldData.height !== newData.height; 118 const isScrolled = isResized || !oldData || oldData.top !== newData.top || oldData.bottom !== newData.bottom; 119 jarallaxList[k].oldData = newData; 120 121 if (isResized) { 122 instance.onResize(); 123 } 124 125 if (isScrolled) { 126 instance.onScroll(); 127 } 128 }); 129 global$1.requestAnimationFrame(updateParallax); 130 } 131 132 let instanceID = 0; // Jarallax class 133 134 class Jarallax { 135 constructor(item, userOptions) { 136 const self = this; 137 self.instanceID = instanceID; 138 instanceID += 1; 139 self.$item = item; 140 self.defaults = { 141 type: 'scroll', 142 // type of parallax: scroll, scale, opacity, scale-opacity, scroll-opacity 143 speed: 0.5, 144 // supported value from -1 to 2 145 imgSrc: null, 146 imgElement: '.jarallax-img', 147 imgSize: 'cover', 148 imgPosition: '50% 50%', 149 imgRepeat: 'no-repeat', 150 // supported only for background, not for <img> tag 151 keepImg: false, 152 // keep <img> tag in it's default place 153 elementInViewport: null, 154 zIndex: -100, 155 disableParallax: false, 156 disableVideo: false, 157 // video 158 videoSrc: null, 159 videoStartTime: 0, 160 videoEndTime: 0, 161 videoVolume: 0, 162 videoLoop: true, 163 videoPlayOnlyVisible: true, 164 videoLazyLoading: true, 165 // events 166 onScroll: null, 167 // function(calculations) {} 168 onInit: null, 169 // function() {} 170 onDestroy: null, 171 // function() {} 172 onCoverImage: null // function() {} 173 174 }; // prepare data-options 175 176 const dataOptions = self.$item.dataset || {}; 177 const pureDataOptions = {};
178 Object.keys(dataOptions).forEach(key => { 179 const loweCaseOption = key.substr(0, 1).toLowerCase() + key.substr(1); 180 181 if (loweCaseOption && 'undefined' !== typeof self.defaults[loweCaseOption]) { 182 pureDataOptions[loweCaseOption] = dataOptions[key]; 183 } 184 }); 185 self.options = self.extend({}, self.defaults, pureDataOptions, userOptions); 186 self.pureOptions = self.extend({}, self.options); // prepare 'true' and 'false' strings to boolean 187 188 Object.keys(self.options).forEach(key => { 189 if ('true' === self.options[key]) { 190 self.options[key] = true; 191 } else if ('false' === self.options[key]) { 192 self.options[key] = false; 193 } 194 }); // fix speed option [-1.0, 2.0] 195 196 self.options.speed = Math.min(2, Math.max(-1, parseFloat(self.options.speed))); // prepare disableParallax callback 197 198 if ('string' === typeof self.options.disableParallax) { 199 self.options.disableParallax = new RegExp(self.options.disableParallax); 200 } 201 202 if (self.options.disableParallax instanceof RegExp) { 203 const disableParallaxRegexp = self.options.disableParallax; 204 205 self.options.disableParallax = () => disableParallaxRegexp.test(navigator.userAgent); 206 } 207 208 if ('function' !== typeof self.options.disableParallax) { 209 self.options.disableParallax = () => false; 210 } // prepare disableVideo callback 211 212 213 if ('string' === typeof self.options.disableVideo) { 214 self.options.disableVideo = new RegExp(self.options.disableVideo); 215 } 216 217 if (self.options.disableVideo instanceof RegExp) { 218 const disableVideoRegexp = self.options.disableVideo; 219 220 self.options.disableVideo = () => disableVideoRegexp.test(navigator.userAgent); 221 } 222 223 if ('function' !== typeof self.options.disableVideo) { 224 self.options.disableVideo = () => false; 225 } // custom element to check if parallax in viewport 226 227 228 let elementInVP = self.options.elementInViewport; // get first item from array 229 230 if (elementInVP && 'object' === typeof elementInVP && 'undefined' !== typeof elementInVP.length) { 231 [elementInVP] = elementInVP; 232 } // check if dom element 233 234 235 if (!(elementInVP instanceof Element)) { 236 elementInVP = null; 237 } 238 239 self.options.elementInViewport = elementInVP; 240 self.image = { 241 src: self.options.imgSrc || null, 242 $container: null, 243 useImgTag: false, 244 // 1. Position fixed is needed for the most of browsers because absolute position have glitches 245 // 2. On MacOS with smooth scroll there is a huge lags with absolute position - https://github.com/nk-o/jarallax/issues/75 246 // 3. Previously used 'absolute' for mobile devices. But we re-tested on iPhone 12 and 'fixed' position is working better, then 'absolute', so for now position is always 'fixed' 247 position: 'fixed' 248 }; 249 250 if (self.initImg() && self.canInitParallax()) { 251 self.init(); 252 } 253 } // add styles to element 254 // eslint-disable-next-line class-methods-use-this 255 256 257 css(el, styles) { 258 if ('string' === typeof styles) { 259 return global$1.getComputedStyle(el).getPropertyValue(styles); 260 } 261 262 Object.keys(styles).forEach(key => { 263 el.style[key] = styles[key]; 264 }); 265 return el; 266 } // Extend like jQuery.extend 267 // eslint-disable-next-line class-methods-use-this 268 269 270 extend(out, ...args) { 271 out = out || {}; 272 Object.keys(args).forEach(i => { 273 if (!args[i]) { 274 return; 275 } 276 277 Object.keys(args[i]).forEach(key => { 278 out[key] = args[i][key]; 279 }); 280 }); 281 return out; 282 } // get window size and scroll position. Useful for extensions 283 // eslint-disable-next-line class-methods-use-this 284 285 286 getWindowData() { 287 return { 288 width: global$1.innerWidth || document.documentElement.clientWidth, 289 height: wndH, 290 y: document.documentElement.scrollTop 291 }; 292 } // Jarallax functions 293 294 295 initImg() { 296 const self = this; // find image element 297 298 let $imgElement = self.options.imgElement; 299 300 if ($imgElement && 'string' === typeof $imgElement) { 301 $imgElement = self.$item.querySelector($imgElement); 302 } // check if dom element 303 304 305 if (!($imgElement instanceof Element)) { 306 if (self.options.imgSrc) { 307 $imgElement = new Image(); 308 $imgElement.src = self.options.imgSrc; 309 } else { 310 $imgElement = null; 311 } 312 } 313 314 if ($imgElement) { 315 if (self.options.keepImg) { 316 self.image.$item = $imgElement.cloneNode(true); 317 } else { 318 self.image.$item = $imgElement; 319 self.image.$itemParent = $imgElement.parentNode; 320 } 321 322 self.image.useImgTag = true; 323 } // true if there is img tag 324 325 326 if (self.image.$item) { 327 return true; 328 } // get image src 329 330 331 if (null === self.image.src) { 332 self.image.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; 333 self.image.bgImage = self.css(self.$item, 'background-image'); 334 } 335 336 return !(!self.image.bgImage || 'none' === self.image.bgImage); 337 } 338 339 canInitParallax() { 340 return !this.options.disableParallax(); 341 } 342 343 init() { 344 const self = this; 345 const containerStyles = { 346 position: 'absolute', 347 top: 0, 348 left: 0, 349 width: '100%', 350 height: '100%', 351 overflow: 'hidden' 352 }; 353 let imageStyles = { 354 pointerEvents: 'none', 355 transformStyle: 'preserve-3d', 356 backfaceVisibility: 'hidden', 357 willChange: 'transform,opacity' 358 }; 359 360 if (!self.options.keepImg) { 361 // save default user styles 362 const curStyle = self.$item.getAttribute('style'); 363 364 if (curStyle) { 365 self.$item.setAttribute('data-jarallax-original-styles', curStyle); 366 } 367 368 if (self.image.useImgTag) { 369 const curImgStyle = self.image.$item.getAttribute('style'); 370 371 if (curImgStyle) { 372 self.image.$item.setAttribute('data-jarallax-original-styles', curImgStyle); 373 } 374 } 375 } // set relative position and z-index to the parent 376 377 378 if ('static' === self.css(self.$item, 'position')) { 379 self.css(self.$item, { 380 position: 'relative' 381 }); 382 } 383 384 if ('auto' === self.css(self.$item, 'z-index')) { 385 self.css(self.$item, { 386 zIndex: 0 387 }); 388 } // container for parallax image 389 390 391 self.image.$container = document.createElement('div'); 392 self.css(self.image.$container, containerStyles); 393 self.css(self.image.$container, { 394 'z-index': self.options.zIndex 395 }); // it will remove some image overlapping 396 // overlapping occur due to an image position fixed inside absolute position element 397 // needed only when background in fixed position 398 399 if ('fixed' === this.image.position) { 400 self.css(self.image.$container, {
401 '-webkit-clip-path': 'polygon(0 0, 100% 0, 100% 100%, 0 100%)', 402 'clip-path': 'polygon(0 0, 100% 0, 100% 100%, 0 100%)' 403 }); 404 } 405 406 self.image.$container.setAttribute('id', `jarallax-container-${self.instanceID}`); 407 self.$item.appendChild(self.image.$container); // use img tag 408 409 if (self.image.useImgTag) { 410 imageStyles = self.extend({ 411 'object-fit': self.options.imgSize, 412 'object-position': self.options.imgPosition, 413 'max-width': 'none' 414 }, containerStyles, imageStyles); // use div with background image 415 } else { 416 self.image.$item = document.createElement('div'); 417 418 if (self.image.src) { 419 imageStyles = self.extend({ 420 'background-position': self.options.imgPosition, 421 'background-size': self.options.imgSize, 422 'background-repeat': self.options.imgRepeat, 423 'background-image': self.image.bgImage || `url("${self.image.src}")` 424 }, containerStyles, imageStyles); 425 } 426 } 427 428 if ('opacity' === self.options.type || 'scale' === self.options.type || 'scale-opacity' === self.options.type || 1 === self.options.speed) { 429 self.image.position = 'absolute'; 430 } // 1. Check if one of parents have transform style (without this check, scroll transform will be inverted if used parallax with position fixed) 431 // discussion - https://github.com/nk-o/jarallax/issues/9 432 // 2. Check if parents have overflow scroll 433 434 435 if ('fixed' === self.image.position) { 436 const $parents = getParents(self.$item).filter(el => { 437 const styles = global$1.getComputedStyle(el); 438 const parentTransform = styles['-webkit-transform'] || styles['-moz-transform'] || styles.transform; 439 const overflowRegex = /(auto|scroll)/; 440 return parentTransform && 'none' !== parentTransform || overflowRegex.test(styles.overflow + styles['overflow-y'] + styles['overflow-x']); 441 }); 442 self.image.position = $parents.length ? 'absolute' : 'fixed'; 443 } // add position to parallax block 444 445 446 imageStyles.position = self.image.position; // insert parallax image 447 448 self.css(self.image.$item, imageStyles); 449 self.image.$container.appendChild(self.image.$item); // set initial position and size 450 451 self.onResize(); 452 self.onScroll(true); // call onInit event 453 454 if (self.options.onInit) { 455 self.options.onInit.call(self); 456 } // remove default user background 457 458 459 if ('none' !== self.css(self.$item, 'background-image')) { 460 self.css(self.$item, { 461 'background-image': 'none' 462 }); 463 } 464 465 self.addToParallaxList(); 466 } // add to parallax instances list 467 468 469 addToParallaxList() { 470 jarallaxList.push({ 471 instance: this 472 }); 473 474 if (1 === jarallaxList.length) { 475 global$1.requestAnimationFrame(updateParallax); 476 } 477 } // remove from parallax instances list 478 479 480 removeFromParallaxList() { 481 const self = this; 482 jarallaxList.forEach((data, key) => { 483 if (data.instance.instanceID === self.instanceID) { 484 jarallaxList.splice(key, 1); 485 } 486 }); 487 } 488 489 destroy() { 490 const self = this; 491 self.removeFromParallaxList(); // return styles on container as before jarallax init 492 493 const originalStylesTag = self.$item.getAttribute('data-jarallax-original-styles'); 494 self.$item.removeAttribute('data-jarallax-original-styles'); // null occurs if there is no style tag before jarallax init 495 496 if (!originalStylesTag) { 497 self.$item.removeAttribute('style'); 498 } else { 499 self.$item.setAttribute('style', originalStylesTag); 500 } 501 502 if (self.image.useImgTag) { 503 // return styles on img tag as before jarallax init 504 const originalStylesImgTag = self.image.$item.getAttribute('data-jarallax-original-styles'); 505 self.image.$item.removeAttribute('data-jarallax-original-styles'); // null occurs if there is no style tag before jarallax init 506 507 if (!originalStylesImgTag) { 508 self.image.$item.removeAttribute('style'); 509 } else { 510 self.image.$item.setAttribute('style', originalStylesTag); 511 } // move img tag to its default position 512 513 514 if (self.image.$itemParent) { 515 self.image.$itemParent.appendChild(self.image.$item); 516 } 517 } // remove additional dom elements 518 519 520 if (self.image.$container) { 521 self.image.$container.parentNode.removeChild(self.image.$container); 522 } // call onDestroy event 523 524 525 if (self.options.onDestroy) { 526 self.options.onDestroy.call(self); 527 } // delete jarallax from item 528 529 530 delete self.$item.jarallax; 531 } // Fallback for removed function. 532 // Does nothing now. 533 // eslint-disable-next-line class-methods-use-this 534 535 536 clipContainer() {} 537 538 coverImage() { 539 const self = this; 540 const rect = self.image.$container.getBoundingClientRect(); 541 const contH = rect.height; 542 const { 543 speed 544 } = self.options; 545 const isScroll = 'scroll' === self.options.type || 'scroll-opacity' === self.options.type; 546 let scrollDist = 0; 547 let resultH = contH; 548 let resultMT = 0; // scroll parallax 549 550 if (isScroll) { 551 // scroll distance and height for image 552 if (0 > speed) { 553 scrollDist = speed * Math.max(contH, wndH); 554 555 if (wndH < contH) { 556 scrollDist -= speed * (contH - wndH); 557 } 558 } else { 559 scrollDist = speed * (contH + wndH); 560 } // size for scroll parallax 561 562 563 if (1 < speed) { 564 resultH = Math.abs(scrollDist - wndH); 565 } else if (0 > speed) { 566 resultH = scrollDist / speed + Math.abs(scrollDist); 567 } else { 568 resultH += (wndH - contH) * (1 - speed); 569 } 570 571 scrollDist /= 2; 572 } // store scroll distance 573 574 575 self.parallaxScrollDistance = scrollDist; // vertical center 576 577 if (isScroll) { 578 resultMT = (wndH - resultH) / 2; 579 } else { 580 resultMT = (contH - resultH) / 2; 581 } // apply result to item 582 583 584 self.css(self.image.$item, { 585 height: `${resultH}px`, 586 marginTop: `${resultMT}px`, 587 left: 'fixed' === self.image.position ? `${rect.left}px` : '0', 588 width: `${rect.width}px` 589 }); // call onCoverImage event 590 591 if (self.options.onCoverImage) { 592 self.options.onCoverImage.call(self); 593 } // return some useful data. Used in the video cover function 594 595 596 return { 597 image: { 598 height: resultH, 599 marginTop: resultMT 600 }, 601 container: rect 602 }; 603 } 604 605 isVisible() { 606 return this.isElementInViewport || false; 607 } 608 609 onScroll(force) { 610 const self = this; 611 const rect = self.$item.getBoundingClientRect(); 612 const contT = rect.top; 613 const contH = rect.height; 614 const styles = {}; // check if in viewport 615 616 let viewportRect = rect; 617 618 if (self.options.elementInViewport) { 619 viewportRect = self.options.elementInViewport.getBoundingClientRect(); 620 } 621 622 self.isElementInViewport = 0 <= viewportRect.bottom && 0 <= viewportRect.right && viewportRect.top <= wndH && viewportRect.left <= global$1.innerWidth; // stop calculations if item is not in viewport 623 624 if (force ? false : !self.isElementInViewport) { 625 return; 626 } // calculate parallax helping variables 627 628 629 const beforeTop = Math.max(0, contT); 630 const beforeTopEnd = Math.max(0, contH + contT); 631 const afterTop = Math.max(0, -contT); 632 const beforeBottom = Math.max(0, contT + contH - wndH); 633 const beforeBottomEnd = Math.max(0, contH - (contT + contH - wndH)); 634 const afterBottom = Math.max(0, -contT + wndH - contH); 635 const fromViewportCenter = 1 - 2 * ((wndH - contT) / (wndH + contH)); // calculate on how percent of section is visible 636 637 let visiblePercent = 1; 638 639 if (contH < wndH) { 640 visiblePercent = 1 - (afterTop || beforeBottom) / contH; 641 } else if (beforeTopEnd <= wndH) { 642 visiblePercent = beforeTopEnd / wndH; 643 } else if (beforeBottomEnd <= wndH) { 644 visiblePercent = beforeBottomEnd / wndH; 645 } // opacity 646 647 648 if ('opacity' === self.options.type || 'scale-opacity' === self.options.type || 'scroll-opacity' === self.options.type) {
649 styles.transform = 'translate3d(0,0,0)'; 650 styles.opacity = visiblePercent; 651 } // scale 652 653 654 if ('scale' === self.options.type || 'scale-opacity' === self.options.type) { 655 let scale = 1; 656 657 if (0 > self.options.speed) { 658 scale -= self.options.speed * visiblePercent; 659 } else { 660 scale += self.options.speed * (1 - visiblePercent); 661 } 662 663 styles.transform = `scale(${scale}) translate3d(0,0,0)`; 664 } // scroll 665 666 667 if ('scroll' === self.options.type || 'scroll-opacity' === self.options.type) { 668 let positionY = self.parallaxScrollDistance * fromViewportCenter; // fix if parallax block in absolute position 669 670 if ('absolute' === self.image.position) { 671 positionY -= contT; 672 } 673 674 styles.transform = `translate3d(0,${positionY}px,0)`; 675 } 676 677 self.css(self.image.$item, styles); // call onScroll event 678 679 if (self.options.onScroll) { 680 self.options.onScroll.call(self, { 681 section: rect, 682 beforeTop, 683 beforeTopEnd, 684 afterTop, 685 beforeBottom, 686 beforeBottomEnd, 687 afterBottom, 688 visiblePercent, 689 fromViewportCenter 690 }); 691 } 692 } 693 694 onResize() { 695 this.coverImage(); 696 } 697 698 } // global definition 699 700 701 const jarallax = function (items, options, ...args) { 702 // check for dom element 703 // thanks: http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object 704 if ('object' === typeof HTMLElement ? items instanceof HTMLElement : items && 'object' === typeof items && null !== items && 1 === items.nodeType && 'string' === typeof items.nodeName) { 705 items = [items]; 706 } 707 708 const len = items.length; 709 let k = 0; 710 let ret; 711 712 for (k; k < len; k += 1) { 713 if ('object' === typeof options || 'undefined' === typeof options) { 714 if (!items[k].jarallax) { 715 items[k].jarallax = new Jarallax(items[k], options); 716 } 717 } else if (items[k].jarallax) { 718 // eslint-disable-next-line prefer-spread 719 ret = items[k].jarallax[options].apply(items[k].jarallax, args); 720 } 721 722 if ('undefined' !== typeof ret) { 723 return ret; 724 } 725 } 726 727 return items; 728 }; 729 730 jarallax.constructor = Jarallax; 731 732 const $ = global$1.jQuery; // jQuery support 733 734 if ('undefined' !== typeof $) { 735 const $Plugin = function (...args) { 736 Array.prototype.unshift.call(args, this); 737 const res = jarallax.apply(global$1, args); 738 return 'object' !== typeof res ? res : this; 739 }; 740 741 $Plugin.constructor = jarallax.constructor; // no conflict 742 743 const old$Plugin = $.fn.jarallax; 744 $.fn.jarallax = $Plugin; 745 746 $.fn.jarallax.noConflict = function () { 747 $.fn.jarallax = old$Plugin; 748 return this; 749 }; 750 } // data-jarallax initialization 751 752 753 ready(() => { 754 jarallax(document.querySelectorAll('[data-jarallax]')); 755 }); 756 757 return jarallax; 758 759})); 760//# sourceMappingURL=jarallax.js.map
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.