1/*! 2 * Jarallax v2.0.4 (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 }; 358 359 if (!self.options.keepImg) { 360 // save default user styles 361 const curStyle = self.$item.getAttribute('style'); 362 363 if (curStyle) { 364 self.$item.setAttribute('data-jarallax-original-styles', curStyle); 365 } 366 367 if (self.image.useImgTag) { 368 const curImgStyle = self.image.$item.getAttribute('style'); 369 370 if (curImgStyle) { 371 self.image.$item.setAttribute('data-jarallax-original-styles', curImgStyle); 372 } 373 } 374 } // set relative position and z-index to the parent 375 376 377 if ('static' === self.css(self.$item, 'position')) { 378 self.css(self.$item, { 379 position: 'relative' 380 }); 381 } 382 383 if ('auto' === self.css(self.$item, 'z-index')) { 384 self.css(self.$item, { 385 zIndex: 0 386 }); 387 } // container for parallax image 388 389 390 self.image.$container = document.createElement('div'); 391 self.css(self.image.$container, containerStyles); 392 self.css(self.image.$container, { 393 'z-index': self.options.zIndex 394 }); // it will remove some image overlapping 395 // overlapping occur due to an image position fixed inside absolute position element 396 // needed only when background in fixed position 397 398 if ('fixed' === this.image.position) { 399 self.css(self.image.$container, {
400 '-webkit-clip-path': 'polygon(0 0, 100% 0, 100% 100%, 0 100%)', 401 'clip-path': 'polygon(0 0, 100% 0, 100% 100%, 0 100%)' 402 }); 403 } 404 405 self.image.$container.setAttribute('id', `jarallax-container-${self.instanceID}`); 406 self.$item.appendChild(self.image.$container); // use img tag 407 408 if (self.image.useImgTag) { 409 imageStyles = self.extend({ 410 'object-fit': self.options.imgSize, 411 'object-position': self.options.imgPosition, 412 'max-width': 'none' 413 }, containerStyles, imageStyles); // use div with background image 414 } else { 415 self.image.$item = document.createElement('div'); 416 417 if (self.image.src) { 418 imageStyles = self.extend({ 419 'background-position': self.options.imgPosition, 420 'background-size': self.options.imgSize, 421 'background-repeat': self.options.imgRepeat, 422 'background-image': self.image.bgImage || `url("${self.image.src}")` 423 }, containerStyles, imageStyles); 424 } 425 } 426 427 if ('opacity' === self.options.type || 'scale' === self.options.type || 'scale-opacity' === self.options.type || 1 === self.options.speed) { 428 self.image.position = 'absolute'; 429 } // 1. Check if one of parents have transform style (without this check, scroll transform will be inverted if used parallax with position fixed) 430 // discussion - https://github.com/nk-o/jarallax/issues/9 431 // 2. Check if parents have overflow scroll 432 433 434 if ('fixed' === self.image.position) { 435 const $parents = getParents(self.$item).filter(el => { 436 const styles = global$1.getComputedStyle(el); 437 const parentTransform = styles['-webkit-transform'] || styles['-moz-transform'] || styles.transform; 438 const overflowRegex = /(auto|scroll)/; 439 return parentTransform && 'none' !== parentTransform || overflowRegex.test(styles.overflow + styles['overflow-y'] + styles['overflow-x']); 440 }); 441 self.image.position = $parents.length ? 'absolute' : 'fixed'; 442 } // add position to parallax block 443 444 445 imageStyles.position = self.image.position; // insert parallax image 446 447 self.css(self.image.$item, imageStyles); 448 self.image.$container.appendChild(self.image.$item); // set initial position and size 449 450 self.onResize(); 451 self.onScroll(true); // call onInit event 452 453 if (self.options.onInit) { 454 self.options.onInit.call(self); 455 } // remove default user background 456 457 458 if ('none' !== self.css(self.$item, 'background-image')) { 459 self.css(self.$item, { 460 'background-image': 'none' 461 }); 462 } 463 464 self.addToParallaxList(); 465 } // add to parallax instances list 466 467 468 addToParallaxList() { 469 jarallaxList.push({ 470 instance: this 471 }); 472 473 if (1 === jarallaxList.length) { 474 global$1.requestAnimationFrame(updateParallax); 475 } 476 } // remove from parallax instances list 477 478 479 removeFromParallaxList() { 480 const self = this; 481 jarallaxList.forEach((data, key) => { 482 if (data.instance.instanceID === self.instanceID) { 483 jarallaxList.splice(key, 1); 484 } 485 }); 486 } 487 488 destroy() { 489 const self = this; 490 self.removeFromParallaxList(); // return styles on container as before jarallax init 491 492 const originalStylesTag = self.$item.getAttribute('data-jarallax-original-styles'); 493 self.$item.removeAttribute('data-jarallax-original-styles'); // null occurs if there is no style tag before jarallax init 494 495 if (!originalStylesTag) { 496 self.$item.removeAttribute('style'); 497 } else { 498 self.$item.setAttribute('style', originalStylesTag); 499 } 500 501 if (self.image.useImgTag) { 502 // return styles on img tag as before jarallax init 503 const originalStylesImgTag = self.image.$item.getAttribute('data-jarallax-original-styles'); 504 self.image.$item.removeAttribute('data-jarallax-original-styles'); // null occurs if there is no style tag before jarallax init 505 506 if (!originalStylesImgTag) { 507 self.image.$item.removeAttribute('style'); 508 } else { 509 self.image.$item.setAttribute('style', originalStylesTag); 510 } // move img tag to its default position 511 512 513 if (self.image.$itemParent) { 514 self.image.$itemParent.appendChild(self.image.$item); 515 } 516 } // remove additional dom elements 517 518 519 if (self.image.$container) { 520 self.image.$container.parentNode.removeChild(self.image.$container); 521 } // call onDestroy event 522 523 524 if (self.options.onDestroy) { 525 self.options.onDestroy.call(self); 526 } // delete jarallax from item 527 528 529 delete self.$item.jarallax; 530 } // Fallback for removed function. 531 // Does nothing now. 532 // eslint-disable-next-line class-methods-use-this 533 534 535 clipContainer() {} 536 537 coverImage() { 538 const self = this; 539 const rect = self.image.$container.getBoundingClientRect(); 540 const contH = rect.height; 541 const { 542 speed 543 } = self.options; 544 const isScroll = 'scroll' === self.options.type || 'scroll-opacity' === self.options.type; 545 let scrollDist = 0; 546 let resultH = contH; 547 let resultMT = 0; // scroll parallax 548 549 if (isScroll) { 550 // scroll distance and height for image 551 if (0 > speed) { 552 scrollDist = speed * Math.max(contH, wndH); 553 554 if (wndH < contH) { 555 scrollDist -= speed * (contH - wndH); 556 } 557 } else { 558 scrollDist = speed * (contH + wndH); 559 } // size for scroll parallax 560 561 562 if (1 < speed) { 563 resultH = Math.abs(scrollDist - wndH); 564 } else if (0 > speed) { 565 resultH = scrollDist / speed + Math.abs(scrollDist); 566 } else { 567 resultH += (wndH - contH) * (1 - speed); 568 } 569 570 scrollDist /= 2; 571 } // store scroll distance 572 573 574 self.parallaxScrollDistance = scrollDist; // vertical center 575 576 if (isScroll) { 577 resultMT = (wndH - resultH) / 2; 578 } else { 579 resultMT = (contH - resultH) / 2; 580 } // apply result to item 581 582 583 self.css(self.image.$item, { 584 height: `${resultH}px`, 585 marginTop: `${resultMT}px`, 586 left: 'fixed' === self.image.position ? `${rect.left}px` : '0', 587 width: `${rect.width}px` 588 }); // call onCoverImage event 589 590 if (self.options.onCoverImage) { 591 self.options.onCoverImage.call(self); 592 } // return some useful data. Used in the video cover function 593 594 595 return { 596 image: { 597 height: resultH, 598 marginTop: resultMT 599 }, 600 container: rect 601 }; 602 } 603 604 isVisible() { 605 return this.isElementInViewport || false; 606 } 607 608 onScroll(force) { 609 const self = this; 610 const rect = self.$item.getBoundingClientRect(); 611 const contT = rect.top; 612 const contH = rect.height; 613 const styles = {}; // check if in viewport 614 615 let viewportRect = rect; 616 617 if (self.options.elementInViewport) { 618 viewportRect = self.options.elementInViewport.getBoundingClientRect(); 619 } 620 621 self.isElementInViewport = 0 <= viewportRect.bottom && 0 <= viewportRect.right && viewportRect.top <= wndH && viewportRect.left <= global$1.innerWidth; // stop calculations if item is not in viewport 622 623 if (force ? false : !self.isElementInViewport) { 624 return; 625 } // calculate parallax helping variables 626 627 628 const beforeTop = Math.max(0, contT); 629 const beforeTopEnd = Math.max(0, contH + contT); 630 const afterTop = Math.max(0, -contT); 631 const beforeBottom = Math.max(0, contT + contH - wndH); 632 const beforeBottomEnd = Math.max(0, contH - (contT + contH - wndH)); 633 const afterBottom = Math.max(0, -contT + wndH - contH); 634 const fromViewportCenter = 1 - 2 * ((wndH - contT) / (wndH + contH)); // calculate on how percent of section is visible 635 636 let visiblePercent = 1; 637 638 if (contH < wndH) { 639 visiblePercent = 1 - (afterTop || beforeBottom) / contH; 640 } else if (beforeTopEnd <= wndH) { 641 visiblePercent = beforeTopEnd / wndH; 642 } else if (beforeBottomEnd <= wndH) { 643 visiblePercent = beforeBottomEnd / wndH; 644 } // opacity 645 646 647 if ('opacity' === self.options.type || 'scale-opacity' === self.options.type || 'scroll-opacity' === self.options.type) {
648 styles.transform = 'translate3d(0,0,0)'; 649 styles.opacity = visiblePercent; 650 } // scale 651 652 653 if ('scale' === self.options.type || 'scale-opacity' === self.options.type) { 654 let scale = 1; 655 656 if (0 > self.options.speed) { 657 scale -= self.options.speed * visiblePercent; 658 } else { 659 scale += self.options.speed * (1 - visiblePercent); 660 } 661 662 styles.transform = `scale(${scale}) translate3d(0,0,0)`; 663 } // scroll 664 665 666 if ('scroll' === self.options.type || 'scroll-opacity' === self.options.type) { 667 let positionY = self.parallaxScrollDistance * fromViewportCenter; // fix if parallax block in absolute position 668 669 if ('absolute' === self.image.position) { 670 positionY -= contT; 671 } 672 673 styles.transform = `translate3d(0,${positionY}px,0)`; 674 } 675 676 self.css(self.image.$item, styles); // call onScroll event 677 678 if (self.options.onScroll) { 679 self.options.onScroll.call(self, { 680 section: rect, 681 beforeTop, 682 beforeTopEnd, 683 afterTop, 684 beforeBottom, 685 beforeBottomEnd, 686 afterBottom, 687 visiblePercent, 688 fromViewportCenter 689 }); 690 } 691 } 692 693 onResize() { 694 this.coverImage(); 695 } 696 697 } // global definition 698 699 700 const jarallax = function (items, options, ...args) { 701 // check for dom element 702 // thanks: http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object 703 if ('object' === typeof HTMLElement ? items instanceof HTMLElement : items && 'object' === typeof items && null !== items && 1 === items.nodeType && 'string' === typeof items.nodeName) { 704 items = [items]; 705 } 706 707 const len = items.length; 708 let k = 0; 709 let ret; 710 711 for (k; k < len; k += 1) { 712 if ('object' === typeof options || 'undefined' === typeof options) { 713 if (!items[k].jarallax) { 714 items[k].jarallax = new Jarallax(items[k], options); 715 } 716 } else if (items[k].jarallax) { 717 // eslint-disable-next-line prefer-spread 718 ret = items[k].jarallax[options].apply(items[k].jarallax, args); 719 } 720 721 if ('undefined' !== typeof ret) { 722 return ret; 723 } 724 } 725 726 return items; 727 }; 728 729 jarallax.constructor = Jarallax; 730 731 const $ = global$1.jQuery; // jQuery support 732 733 if ('undefined' !== typeof $) { 734 const $Plugin = function (...args) { 735 Array.prototype.unshift.call(args, this); 736 const res = jarallax.apply(global$1, args); 737 return 'object' !== typeof res ? res : this; 738 }; 739 740 $Plugin.constructor = jarallax.constructor; // no conflict 741 742 const old$Plugin = $.fn.jarallax; 743 $.fn.jarallax = $Plugin; 744 745 $.fn.jarallax.noConflict = function () { 746 $.fn.jarallax = old$Plugin; 747 return this; 748 }; 749 } // data-jarallax initialization 750 751 752 ready(() => { 753 jarallax(document.querySelectorAll('[data-jarallax]')); 754 }); 755 756 return jarallax; 757 758})); 759//# 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.