1(function () { 2 "use strict"; 3 4 const DEFAULT_FPS = 60; 5 const DT_FPS = 1000 / DEFAULT_FPS; 6 7 const easings = { 8 linear: (t) => t, 9 ease: (t) => 0.5 * (1 - Math.cos(Math.PI * t)), 10 easeInQuad: (t) => t * t, 11 easeOutQuad: (t) => t * (2 - t), 12 easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t), 13 easeInCubic: (t) => t * t * t, 14 easeOutCubic: (t) => --t * t * t + 1, 15 easeInOutCubic: (t) => 16 t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1, 17 easeInQuart: (t) => t * t * t * t, 18 easeOutQuart: (t) => 1 - --t * t * t * t, 19 easeInOutQuart: (t) => 20 t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t, 21 easeInQuint: (t) => t * t * t * t * t, 22 easeOutQuint: (t) => 1 + --t * t * t * t * t, 23 easeInOutQuint: (t) => 24 t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t, 25 easeOutBounce: (t) => { 26 const n1 = 7.5625; 27 const d1 = 2.75; 28 29 if (t < 1 / d1) { 30 return n1 * t * t; 31 } 32 if (t < 2 / d1) { 33 return n1 * (t -= 1.5 / d1) * t + 0.75; 34 } 35 if (t < 2.5 / d1) { 36 return n1 * (t -= 2.25 / d1) * t + 0.9375; 37 } 38 return n1 * (t -= 2.625 / d1) * t + 0.984375; 39 }, 40 easeInBounce: (t) => 1 - easings.easeOutBounce(1 - t), 41 easeOutBack: (t) => { 42 const c1 = 1.70158; 43 const c3 = c1 + 1; 44 45 return 1 + c3 * (t - 1) ** 3 + c1 * (t - 1) ** 2; 46 }, 47 easeInBack: (t) => { 48 const c1 = 1.70158; 49 const c3 = c1 + 1; 50 51 return c3 * t * t * t - c1 * t * t; 52 }, 53 easeInOut: (t) => 54 t < 0.5 55 ? easings.easeInBack(t * 2) / 2 56 : easings.easeOutBack(t * 2 - 1) / 2 + 0.5, 57 easeInElastic: (t) => { 58 const c4 = (2 * Math.PI) / 3; 59 return t === 0 60 ? 0 61 : t === 1 62 ? 1 63 : -(2 ** (10 * t - 10)) * Math.sin((t * 10 - 10.75) * c4); 64 }, 65 easeOutElastic: (t) => { 66 const c4 = (2 * Math.PI) / 3; 67 return t === 0 68 ? 0 69 : t === 1 70 ? 1 71 : 2 ** (-10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1; 72 }, 73 spring: (t) => 1 - Math.cos(t * 4.5 * Math.PI) * Math.exp(-t * 6), 74 decay: (t) => 1 - Math.exp(-t * 6), 75 bezier: (x1, y1, x2, y2) => (t) => { 76 const ax = 3 * x1 - 3 * x2 + 1; 77 const bx = 3 * x2 - 6 * x1; 78 const cx = 3 * x1; 79 const ay = 3 * y1 - 3 * y2 + 1; 80 const by = 3 * y2 - 6 * y1; 81 const cy = 3 * y1; 82 83 // eslint-disable-next-line @typescript-eslint/no-shadow 84 const sampleCurveX = (t) => ((ax * t + bx) * t + cx) * t; 85 86 const sampleCurveY = (t) => ((ay * t + by) * t + cy) * t; 87 88 const sampleCurveDerivativeX = (t) => (3 * ax * t + 2 * bx) * t + cx; 89 90 const solveCurveX = (x, epsilon) => { 91 let t0; 92 let t1; 93 let t2; 94 let x2; 95 let d2; 96 let i; 97 98 for (t2 = x, i = 0; i < 8; i++) { 99 x2 = sampleCurveX(t2) - x; 100 if (Math.abs(x2) < epsilon) { 101 return t2; 102 } 103 d2 = sampleCurveDerivativeX(t2); 104 if (Math.abs(d2) < 1e-6) { 105 break; 106 } 107 t2 -= x2 / d2; 108 } 109 110 t0 = 0; 111 t1 = 1; 112 t2 = x; 113 114 if (t2 < t0) { 115 return t0; 116 } 117 if (t2 > t1) { 118 return t1; 119 } 120 121 while (t0 < t1) { 122 x2 = sampleCurveX(t2); 123 if (Math.abs(x2 - x) < epsilon) { 124 return t2; 125 } 126 if (x > x2) { 127 t0 = t2; 128 } else { 129 t1 = t2; 130 } 131 t2 = (t1 - t0) * 0.5 + t0; 132 } 133 134 return t2; 135 }; 136 137 return sampleCurveY(solveCurveX(t, 1 / 200)); 138 }, 139 }; 140 141 function interpolate({ 142 inputRange, 143 outputRange, 144 value, 145 easing = (value) => value, 146 reverseEasing = false, 147 }) { 148 const sortedRanges = inputRange 149 .map((_, i) => ({input: inputRange[i], output: outputRange[i]})) 150 .sort((a, b) => a.input - b.input); 151 const sortedInputRange = sortedRanges.map(({input}) => input); 152 const sortedOutputRange = sortedRanges.map(({output}) => output); 153 154 if (value <= sortedInputRange[0]) { 155 return sortedOutputRange[0]; 156 } 157 158 if (value >= sortedInputRange[sortedInputRange.length - 1]) { 159 return sortedOutputRange[sortedOutputRange.length - 1]; 160 } 161 162 let i = 0; 163 for (const inputValue of sortedInputRange) { 164 if (inputValue < value) { 165 i++; 166 } 167 } 168
169 const j = i - 1; 170 171 let ratio = 172 (value - sortedInputRange[j]) / (sortedInputRange[i] - sortedInputRange[j]); 173 if (typeof easing === "function") { 174 if (reverseEasing) { 175 ratio = 1 - easing(1 - ratio); 176 } else { 177 ratio = easing(ratio); 178 } 179 } 180 return sortedOutputRange[j] * (1 - ratio) + sortedOutputRange[i] * ratio; 181 } 182 183 function getWindow(el) { 184 return el.nodeType === 9 && el.defaultView; 185 } 186 187 function offset(el) { 188 const doc = el?.ownerDocument; 189 const docElem = doc.documentElement; 190 const win = getWindow(doc); 191 let box = {top: 0, left: 0}; 192 193 if (!doc) { 194 return { 195 top: 0, 196 left: 0, 197 }; 198 } 199 200 if (typeof el.getBoundingClientRect !== typeof undefined) { 201 box = el.getBoundingClientRect(); 202 } 203 204 return { 205 top: box.top + win.scrollY - docElem.clientTop, 206 left: box.left + win.scrollX - docElem.clientLeft, 207 }; 208 } 209 210 function objectParse(value) { 211 const val = value.trim(); 212 if (/^{|\[/g.test(val)) { 213 try { 214 const fn = new Function(`return ${val}`); 215 const obj = fn(); 216 return JSON.parse(JSON.stringify(obj)); 217 } catch { 218 if (/^\[/g.test(val)) { 219 return []; 220 } 221 return {}; 222 } 223 } else { 224 return {}; 225 } 226 } 227 228 const lerp = (p1, p2, t) => { 229 return p1 + (p2 - p1) * t; 230 }; 231 232 function map(array, callback) { 233 const result = []; 234 for (let i = 0; i < array.length; i++) { 235 result.push(callback(array[i], i, array)); 236 } 237 return result; 238 } 239 240 function kebabToCamel(value) { 241 return value.replace(/([-]\w)/g, (g) => g[1].toUpperCase()); 242 } 243 244 function getValue(value, type) { 245 switch (type) { 246 case "string": 247 return value; 248 case "number": 249 return Number(value); 250 case "boolean": 251 return value === "true" || value === ""; 252 case "object": 253 return objectParse(value); 254 case "array": 255 return objectParse(value); 256 default: 257 return value; 258 } 259 } 260 261 function getAttrs( 262 el, 263 {prefix = "v-", pick, types, camelCase = true, propTransformer} = {} 264 ) { 265 let result = {}; 266 267 if (el == null) { 268 return result; 269 } 270 271 const attrs = Array.from(el.attributes); 272 if (pick == null) { 273 pick = map(attrs, (attr) => attr.name); 274 } 275 for (const attr of attrs) { 276 if (!attr.name.startsWith(prefix)) { 277 continue; 278 } 279 let name = camelCase 280 ? kebabToCamel(attr.name) 281 : attr.name.replace(prefix, ""); 282 const type = types?.[name]; 283 if (attr.value != null && pick?.includes(name)) { 284 if (typeof propTransformer === "function") { 285 name = propTransformer(name); 286 } 287 if (!!types && type != null) { 288 result = {...result, [name]: getValue(attr.value, type)}; 289 } else { 290 result = {...result, [name]: attr.value}; 291 } 292 } 293 } 294 return result; 295 } 296 297 class FrameManager { 298 frameId; 299 keepAliveFrameId; 300 lastTimestamp; 301 frames; 302 keepAliveFrames; 303 defaultTimestep; 304 305 constructor() { 306 this.frameId = null; 307 this.keepAliveFrameId = null; 308 this.lastTimestamp = null; 309 this.frames = []; 310 this.keepAliveFrames = []; 311 this.defaultTimestep = (1 / 60) * 1000; 312 } 313 314 handleFrameLoop = (timestamp) => { 315 if (this.lastTimestamp) { 316 const delta = timestamp - this.lastTimestamp; 317 this.keepAliveFrames.forEach((frame) => 318 frame.call(this, {delta, timestamp}) 319 ); 320 } 321 this.lastTimestamp = timestamp; 322 if (this.keepAliveFrameId) { 323 cancelAnimationFrame(this.keepAliveFrameId); 324 } 325 this.keepAliveFrameId = requestAnimationFrame(this.handleFrameLoop); 326 }; 327 328 handleFrame = (timestamp) => { 329 this.frames.forEach((frame) => 330 frame.call(this, {delta: this.defaultTimestep, timestamp}) 331 ); 332 }; 333 334 getFrames = () => { 335 return this.frames; 336 }; 337 338 add = (frame, keepAlive = false) => { 339 this.cancelFrame(); 340 if (!this.frames.includes(frame)) { 341 this.frames.push(frame); 342 } 343 if (keepAlive && !this.keepAliveFrames.includes(frame)) { 344 this.keepAliveFrames.push(frame); 345 } 346 this.start(); 347 348 return this; 349 }; 350 351 start = () => { 352 this.frameId = requestAnimationFrame(this.handleFrame); 353 this.keepAliveFrameId = requestAnimationFrame(this.handleFrameLoop); 354 }; 355 356 cancelFrame = () => { 357 if (this.frameId != null) { 358 cancelAnimationFrame(this.frameId); 359 this.frameId = null; 360 } 361 if (this.keepAliveFrameId != null) { 362 cancelAnimationFrame(this.keepAliveFrameId); 363 this.keepAliveFrameId = null; 364 } 365 }; 366 367 stopFrame = (frames, frame) => { 368 const taskIndex = frames.indexOf(frame); 369 if (taskIndex !== -1) { 370 frames.splice(taskIndex, 1); 371 } 372 if (frames.length === 0) { 373 this.cancelFrame(); 374 this.lastTimestamp = null; 375 } 376 }; 377 378 remove = (frame) => { 379 this.stopFrame(this.frames, frame); 380 this.stopFrame(this.keepAliveFrames, frame); 381 382 return this; 383 }; 384
385 clear = () => { 386 this.frames = []; 387 this.keepAliveFrames = []; 388 this.cancelFrame(); 389 this.lastTimestamp = null; 390 391 return this; 392 }; 393 } 394 395 const frameManager = new FrameManager(); 396 397 class ParallaxHover extends HTMLElement { 398 shadow = this.attachShadow({mode: "open"}); 399 400 providerEl = null; 401 402 targetValueX = 0; 403 404 targetValueY = 0; 405 406 currentValueX = 0; 407 408 currentValueY = 0; 409 410 static defaultOptions = { 411 vStrength: 100, 412 vResetPosition: true, 413 v3d: false, 414 }; 415 416 constructor() { 417 super(); 418 frameManager.add(this.handleFrameSyncUpdate, true); 419 } 420 421 get options() { 422 const options = getAttrs(this, { 423 pick: ["vStrength", "vResetPosition", "v3d"], 424 types: { 425 vStrength: "number", 426 vResetPosition: "boolean", 427 v3d: "boolean", 428 }, 429 }); 430 431 return { 432 ...ParallaxHover.defaultOptions, 433 ...options, 434 }; 435 } 436 437 handleParallax = (delta) => { 438 const {vStrength, v3d} = this.options; 439 let slowDown = delta / DT_FPS; 440 const slowDownRounded = Math.round(slowDown); 441 if (slowDownRounded >= 1) { 442 slowDown = slowDownRounded; 443 } 444 const lerpEase = Number( 445 this.providerEl?.getAttribute("v-lerp-ease") || "0.08" 446 ); 447 const valueX = lerp( 448 this.currentValueX, 449 this.targetValueX, 450 lerpEase * slowDown 451 ); 452 const valueY = lerp( 453 this.currentValueY, 454 this.targetValueY, 455 lerpEase * slowDown 456 ); 457 const innerEl = this.shadow.querySelector("v-parallax-hover-inner"); 458 if (v3d) { 459 const constant = 4; 460 innerEl.style.transformStyle = "preserve-3d"; 461 innerEl.style.transform = `perspective(2000px) rotateX(${ 462 (-valueY / constant) * vStrength 463 }deg) rotateY(${(valueX / constant) * vStrength}deg)`; 464 } else { 465 innerEl.style.transform = `translate3d(${valueX * vStrength}px, ${ 466 valueY * vStrength 467 }px, 0)`; 468 } 469 this.currentValueX = valueX; 470 this.currentValueY = valueY; 471 }; 472 473 handleFrameSyncUpdate = ({delta}) => { 474 const diffX = Math.abs(this.targetValueX - this.currentValueX); 475 const diffY = Math.abs(this.targetValueY - this.currentValueY); 476 if (diffX < 0.001 && diffY < 0.001) { 477 return; 478 } 479 480 this.handleParallax(delta); 481 }; 482 483 setTargetValue = (event, left, top, width, height) => { 484 const centerX = offset(this).left + this.offsetWidth / 2; 485 const centerY = offset(this).top + this.offsetHeight / 2; 486 this.targetValueX = interpolate({ 487 value: event.pageX - left, 488 inputRange: [0, centerX - left, width], 489 outputRange: [-1, 0, 1], 490 }); 491 this.targetValueY = interpolate({ 492 value: event.pageY - top, 493 inputRange: [0, centerY - top, height], 494 outputRange: [-1, 0, 1], 495 }); 496 }; 497 498 handleMouseMove = (event) => { 499 const providerLeft = offset(this.providerEl).left;
500 const providerTop = offset(this.providerEl).top; 501 const providerWidth = this.providerEl.offsetWidth; 502 const providerHeight = this.providerEl.offsetHeight; 503 this.setTargetValue( 504 event, 505 providerLeft, 506 providerTop, 507 providerWidth, 508 providerHeight 509 ); 510 }; 511 512 handleMouseLeave = () => { 513 const {vResetPosition} = this.options; 514 if (vResetPosition) { 515 this.targetValueX = 0; 516 this.targetValueY = 0; 517 } 518 }; 519 520 connectedCallback() { 521 this.providerEl = this.closest("v-parallax"); 522 if (!this.providerEl) { 523 throw new Error( 524 `The v-parallax-hover component must be a child of v-parallax` 525 ); 526 } 527 if (!this.shadow.innerHTML) { 528 this.shadow.innerHTML = `<v-parallax-hover-inner style="display: block"><slot></slot></v-parallax-hover-inner>`; 529 } 530 this.handleParallax((1 / 60) * 1000); 531 this.providerEl.addEventListener("mousemove", this.handleMouseMove); 532 this.providerEl.addEventListener("mouseleave", this.handleMouseLeave); 533 } 534 535 disconnectedCallback() { 536 frameManager.remove(this.handleFrameSyncUpdate); 537 this.providerEl?.removeEventListener("mousemove", this.handleMouseMove); 538 this.providerEl?.removeEventListener("mouseleave", this.handleMouseLeave); 539 } 540 } 541 542 customElements.define("v-parallax-hover", ParallaxHover); 543 544})();
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.