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