1// ===== index.js ===== 2function getDefaultExportFromCjs (x) { 3 return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; 4} 5 6var lib = {exports: {}}; 7 8/** 9 * Code refactored from Mozilla Developer Network: 10 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign 11 */ 12 13var es6ObjectAssign; 14var hasRequiredEs6ObjectAssign; 15 16function requireEs6ObjectAssign () { 17 if (hasRequiredEs6ObjectAssign) return es6ObjectAssign; 18 hasRequiredEs6ObjectAssign = 1; 19 20 function assign(target, firstSource) { 21 if (target === undefined || target === null) { 22 throw new TypeError('Cannot convert first argument to object'); 23 } 24 25 var to = Object(target); 26 for (var i = 1; i < arguments.length; i++) { 27 var nextSource = arguments[i]; 28 if (nextSource === undefined || nextSource === null) { 29 continue; 30 } 31 32 var keysArray = Object.keys(Object(nextSource)); 33 for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { 34 var nextKey = keysArray[nextIndex]; 35 var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); 36 if (desc !== undefined && desc.enumerable) { 37 to[nextKey] = nextSource[nextKey]; 38 } 39 } 40 } 41 return to; 42 } 43 44 function polyfill() { 45 if (!Object.assign) { 46 Object.defineProperty(Object, 'assign', { 47 enumerable: false, 48 configurable: true, 49 writable: true, 50 value: assign 51 }); 52 } 53 } 54 55 es6ObjectAssign = { 56 assign: assign, 57 polyfill: polyfill 58 }; 59 return es6ObjectAssign; 60} 61 62var util = {}; 63 64var hasRequiredUtil; 65 66function requireUtil () { 67 if (hasRequiredUtil) return util; 68 hasRequiredUtil = 1; 69 70 Object.defineProperty(util, "__esModule", { 71 value: true 72 }); 73 util.append = function append(element, string) { 74 var div = document.createElement('div'); 75 div.innerHTML = string; 76 while (div.children.length > 0) { 77 element.appendChild(div.children[0]); 78 } 79 }; 80 81 util.addClass = function addClass(element, className) { 82 if (element.classList) { 83 element.classList.add(className); 84 } else { 85 element.className += ' ' + className; 86 } 87 }; 88 89 util.removeClass = function removeClass(element, className) { 90 if (element.classList) { 91 element.classList.remove(className); 92 } else { 93 element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); 94 } 95 }; 96 97 var getScrollTop = util.getScrollTop = function getScrollTop() { 98 return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; 99 }; 100 101 var getScrollLeft = util.getScrollLeft = function getScrollLeft() { 102 return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; 103 }; 104 105 util.getOffset = function getOffset(el) { 106 var rect = el.getBoundingClientRect(); 107 return { 108 top: rect.top + getScrollTop(), 109 left: rect.left + getScrollLeft() 110 }; 111 }; 112 return util; 113} 114 115var hasRequiredLib; 116 117function requireLib () { 118 if (hasRequiredLib) return lib.exports; 119 hasRequiredLib = 1; 120 (function (module, exports$1) { 121 122 Object.defineProperty(exports$1, "__esModule", { 123 value: true 124 }); 125 126 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 127 128 var _es6ObjectAssign = requireEs6ObjectAssign(); 129 130 var _util = requireUtil(); 131 132 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 133 134 var defaults = { 135 suggestClass: 'is-active', 136 scrollableClass: 'is-scrollable', 137 scrollableRightClass: 'is-right-scrollable', 138 scrollableLeftClass: 'is-left-scrollable', 139 scrollHintClass: 'scroll-hint', 140 scrollHintShadowWrapClass: 'scroll-hint-shadow-wrap', 141 scrollHintIconClass: 'scroll-hint-icon', 142 scrollHintIconAppendClass: '', // 'scroll-hint-icon-white' 143 scrollHintIconWrapClass: 'scroll-hint-icon-wrap', 144 scrollHintText: 'scroll-hint-text', 145 scrollHintBorderWidth: 10, 146 remainingTime: -1, 147 enableOverflowScrolling: true, 148 applyToParents: false, 149 suggestiveShadow: false, 150 offset: 0, 151 i18n: { 152 scrollable: 'scrollable' 153 } 154 }; 155 156 var ScrollHint = function () { 157 function ScrollHint(ele, option) { 158 var _this = this; 159 160 _classCallCheck(this, ScrollHint); 161 162 this.opt = (0, _es6ObjectAssign.assign)({}, defaults, option); 163 this.items = []; 164 var elements = void 0; 165 if (ele instanceof HTMLElement) { 166 elements = [ele]; 167 } else if (typeof ele === 'string') { 168 elements = document.querySelectorAll(ele); 169 } else { 170 elements = ele; 171 } 172 var applyToParents = this.opt.applyToParents; 173 174 [].forEach.call(elements, function (element) {
175 if (applyToParents) { 176 element = element.parentElement; 177 } 178 element.style.position = 'relative'; 179 element.style.overflow = 'auto'; 180 if (_this.opt.enableOverflowScrolling) { 181 if ('overflowScrolling' in element.style) { 182 element.style.overflowScrolling = 'touch'; 183 } else if ('webkitOverflowScrolling' in element.style) { 184 element.style.webkitOverflowScrolling = 'touch'; 185 } 186 } 187 var item = { 188 element: element, 189 scrolledIn: false, 190 interacted: false 191 }; 192 document.addEventListener('scroll', function (e) { 193 if (e.target === element) { 194 item.interacted = true; 195 _this.updateItem(item); 196 } 197 }, true); 198 (0, _util.addClass)(element, _this.opt.scrollHintClass); 199 _this.items.push(item); 200 201 (0, _util.append)(element, '<div class="' + _this.opt.scrollHintIconWrapClass + '" data-target="scrollable-icon">\n <span class="' + _this.opt.scrollHintIconClass + (_this.opt.scrollHintIconAppendClass ? ' ' + _this.opt.scrollHintIconAppendClass : '') + '">\n <div class="' + _this.opt.scrollHintText + '">' + _this.opt.i18n.scrollable + '</div>\n </span>\n </div>'); 202 203 if (_this.opt.suggestiveShadow) { 204 var wrapper = document.createElement('div'); 205 wrapper.classList.add(_this.opt.scrollHintShadowWrapClass); 206 element.parentNode.insertBefore(wrapper, element); 207 wrapper.appendChild(element); 208 } 209 }); 210 window.addEventListener('scroll', function () { 211 _this.updateItems(); 212 }); 213 window.addEventListener('resize', function () { 214 _this.updateItems(); 215 }); 216 this.updateItems(); 217 } 218 219 _createClass(ScrollHint, [{ 220 key: 'isScrollable', 221 value: function isScrollable(item) { 222 var offset = this.opt.offset; 223 var element = item.element; 224 var offsetWidth = element.offsetWidth; 225 226 return offsetWidth + offset < element.scrollWidth; 227 } 228 }, { 229 key: 'checkScrollableDir', 230 value: function checkScrollableDir(item) { 231 var _opt = this.opt, 232 scrollHintBorderWidth = _opt.scrollHintBorderWidth, 233 scrollableRightClass = _opt.scrollableRightClass, 234 scrollableLeftClass = _opt.scrollableLeftClass; 235 var element = item.element; 236 237 var child = element.children[0]; 238 var width = child.scrollWidth; 239 var parentWidth = element.offsetWidth; 240 var scrollLeft = element.scrollLeft; 241 if (parentWidth + scrollLeft < width - scrollHintBorderWidth) { 242 (0, _util.addClass)(element, scrollableRightClass); 243 } else { 244 (0, _util.removeClass)(element, scrollableRightClass); 245 } 246 if (parentWidth < width && scrollLeft > scrollHintBorderWidth) { 247 (0, _util.addClass)(element, scrollableLeftClass); 248 } else { 249 (0, _util.removeClass)(element, scrollableLeftClass); 250 } 251 } 252 }, { 253 key: 'needSuggest', 254 value: function needSuggest(item) { 255 var scrolledIn = item.scrolledIn, 256 interacted = item.interacted; 257 258 return !interacted && scrolledIn && this.isScrollable(item); 259 } 260 }, { 261 key: 'updateItems', 262 value: function updateItems() { 263 var _this2 = this; 264 265 [].forEach.call(this.items, function (item) { 266 _this2.updateItem(item); 267 }); 268 } 269 }, { 270 key: 'updateStatus', 271 value: function updateStatus(item) { 272 var _this3 = this; 273 274 var element = item.element, 275 scrolledIn = item.scrolledIn; 276 277 if (scrolledIn) { 278 return; 279 } 280 var target = element.querySelector('[data-target="scrollable-icon"] > span'); 281 if ((0, _util.getOffset)(target).top < (0, _util.getScrollTop)() + window.innerHeight) { 282 item.scrolledIn = true; 283 if (this.opt.remainingTime !== -1) { 284 setTimeout(function () { 285 item.interacted = true; 286 _this3.updateItem(item); 287 }, this.opt.remainingTime); 288 } 289 } 290 } 291 }, {
292 key: 'updateItem', 293 value: function updateItem(item) { 294 var opt = this.opt; 295 var element = item.element; 296 297 var target = element.querySelector('[data-target="scrollable-icon"]'); 298 this.updateStatus(item); 299 if (this.isScrollable(item)) { 300 (0, _util.addClass)(element, opt.scrollableClass); 301 } else { 302 (0, _util.removeClass)(element, opt.scrollableClass); 303 } 304 if (this.needSuggest(item)) { 305 (0, _util.addClass)(target, opt.suggestClass); 306 } else { 307 (0, _util.removeClass)(target, opt.suggestClass); 308 } 309 if (opt.suggestiveShadow) { 310 this.checkScrollableDir(item); 311 } 312 } 313 }]); 314 315 return ScrollHint; 316 }(); 317 318 exports$1.default = ScrollHint; 319 module.exports = exports$1['default']; 320 } (lib, lib.exports)); 321 return lib.exports; 322} 323 324var libExports = requireLib(); 325const ScrollHint = /*@__PURE__*/getDefaultExportFromCjs(libExports); 326 327 328 329// ===== ir-highlights.js ===== 330const highlightsYears = [ 331 "2021å¹´3ææ", 332 "2022å¹´3ææ", 333 "2023å¹´3ææ", 334 "2024å¹´3ææ", 335 "2025å¹´3ææ" 336]; 337const highlightColumnIndex = 4; 338const performanceRows = [ 339 { 340 label: "売ä¸é«", 341 values: ["94,410", "94,410", "94,410", "94,410", "94,410"] 342 }, 343 { 344 label: "売ä¸ç·å©ç", 345 subLabel: "売ä¸é«æ¯ç", 346 values: ["62,667", "62,667", "62,667", "62,667", "62,667"], 347 subValues: ["66.4%", "66.4%", "66.4%", "66.4%", "66.4%"] 348 }, 349 { 350 label: "å¶æ¥å©ç", 351 subLabel: "売ä¸é«æ¯ç", 352 values: ["â³ 1,683", "â³ 1,683", "â³ 1,683", "â³ 1,683", "â³ 1,683"], 353 subValues: ["â³ 1.8%", "â³ 1.8%", "â³ 1.8%", "â³ 1.8%", "â³ 1.8%"] 354 }, 355 { 356 label: "çµå¸¸å©ç", 357 subLabel: "売ä¸é«æ¯ç", 358 values: ["33", "33", "33", "33", "33"], 359 subValues: ["0.0%", "0.0%", "0.0%", "0.0%", "0.0%"] 360 }, 361 { 362 label: "彿ç´å©ç", 363 subLabel: "売ä¸é«æ¯ç", 364 values: ["â³ 2,376", "â³ 2,376", "â³ 2,376", "â³ 2,376", "â³ 2,376"], 365 subValues: ["â³ 2.5%", "â³ 2.5%", "â³ 2.5%", "â³ 2.5%", "â³ 2.5%"] 366 }, 367 { 368 label: "FLæ¯ç â»1", 369 values: ["66.9%", "66.9%", "66.9%", "66.9%", "66.9%"], 370 rowClass: "editor-table__row--total" 371 }, 372 { 373 label: "ç·è³ç£", 374 values: ["104,155", "104,155", "104,155", "104,155", "104,155"] 375 }, 376 { 377 label: "ç´è³ç£", 378 values: ["45,615", "45,615", "45,615", "45,615", "45,615"] 379 }, 380 { 381 label: "èªå·±è³æ¬æ¯ç", 382 values: ["43.8%", "43.8%", "43.8%", "43.8%", "43.8%"] 383 }, 384 { 385 label: "1æ ªå½ãç´è³ç£ (å)", 386 values: ["2,392.81", "2,392.81", "2,392.81", "2,392.81", "2,392.81"] 387 }, 388 { 389 label: "1æ ªå½ã彿å©ç (å)", 390 values: ["114.67", "114.67", "114.67", "114.67", "114.67"] 391 } 392]; 393const dividendRows = [ 394 { 395 label: "第2ååæ", 396 values: ["12å", "12å", "12å", "12å", "12å"] 397 }, 398 { 399 label: "ææ«", 400 values: ["12å", "12å", "12å", "12å", "12å"] 401 }, 402 { 403 label: "å¹´éåè¨", 404 values: ["24å", "24å", "24å", "24å", "24å"], 405 rowClass: "editor-table__row--total" 406 } 407]; 408const cashflowRows = [ 409 { 410 label: "å¶æ¥ãã£ãã·ã¥ããã¼", 411 values: ["8,323", "8,323", "8,323", "8,323", "8,323"] 412 }, 413 { 414 label: "æè³ãã£ãã·ã¥ããã¼", 415 values: ["â³ 17,740", "â³ 17,740", "â³ 17,740", "â³ 17,740", "â³ 17,740"] 416 }, 417 { 418 label: "財åãã£ãã·ã¥ããã¼", 419 values: ["7,144", "7,144", "7,144", "7,144", "7,144"], 420 rowClass: "editor-table__row--total" 421 } 422]; 423 424 425 426// ===== photoswipe.esm.js =====
vendor: 16,617 bytes, lines 427-1109
427/*! 428 * PhotoSwipe 5.4.4 - https://photoswipe.com 429 * (c) 2024 Dmytro Semenov 430 */ 431/** @typedef {import('../photoswipe.js').Point} Point */ 432 433/** 434 * @template {keyof HTMLElementTagNameMap} T 435 * @param {string} className 436 * @param {T} tagName 437 * @param {Node} [appendToEl] 438 * @returns {HTMLElementTagNameMap[T]} 439 */ 440function createElement(className, tagName, appendToEl) { 441 const el = document.createElement(tagName); 442 443 if (className) { 444 el.className = className; 445 } 446 447 if (appendToEl) { 448 appendToEl.appendChild(el); 449 } 450 451 return el; 452} 453/** 454 * @param {Point} p1 455 * @param {Point} p2 456 * @returns {Point} 457 */ 458 459function equalizePoints(p1, p2) { 460 p1.x = p2.x; 461 p1.y = p2.y; 462 463 if (p2.id !== undefined) { 464 p1.id = p2.id; 465 } 466 467 return p1; 468} 469/** 470 * @param {Point} p 471 */ 472 473function roundPoint(p) { 474 p.x = Math.round(p.x); 475 p.y = Math.round(p.y); 476} 477/** 478 * Returns distance between two points. 479 * 480 * @param {Point} p1 481 * @param {Point} p2 482 * @returns {number} 483 */ 484 485function getDistanceBetween(p1, p2) { 486 const x = Math.abs(p1.x - p2.x); 487 const y = Math.abs(p1.y - p2.y); 488 return Math.sqrt(x * x + y * y); 489} 490/** 491 * Whether X and Y positions of points are equal 492 * 493 * @param {Point} p1 494 * @param {Point} p2 495 * @returns {boolean} 496 */ 497 498function pointsEqual(p1, p2) { 499 return p1.x === p2.x && p1.y === p2.y; 500} 501/** 502 * The float result between the min and max values. 503 * 504 * @param {number} val 505 * @param {number} min 506 * @param {number} max 507 * @returns {number} 508 */ 509 510function clamp(val, min, max) { 511 return Math.min(Math.max(val, min), max); 512} 513/** 514 * Get transform string 515 * 516 * @param {number} x 517 * @param {number} [y] 518 * @param {number} [scale] 519 * @returns {string} 520 */ 521 522function toTransformString(x, y, scale) { 523 let propValue = `translate3d(${x}px,${y || 0}px,0)`; 524 525 if (scale !== undefined) { 526 propValue += ` scale3d(${scale},${scale},1)`; 527 } 528 529 return propValue; 530} 531/** 532 * Apply transform:translate(x, y) scale(scale) to element 533 * 534 * @param {HTMLElement} el 535 * @param {number} x 536 * @param {number} [y] 537 * @param {number} [scale] 538 */ 539 540function setTransform(el, x, y, scale) { 541 el.style.transform = toTransformString(x, y, scale); 542} 543const defaultCSSEasing = 'cubic-bezier(.4,0,.22,1)'; 544/** 545 * Apply CSS transition to element 546 * 547 * @param {HTMLElement} el 548 * @param {string} [prop] CSS property to animate 549 * @param {number} [duration] in ms 550 * @param {string} [ease] CSS easing function 551 */ 552 553function setTransitionStyle(el, prop, duration, ease) { 554 // inOut: 'cubic-bezier(.4, 0, .22, 1)', // for "toggle state" transitions 555 // out: 'cubic-bezier(0, 0, .22, 1)', // for "show" transitions 556 // in: 'cubic-bezier(.4, 0, 1, 1)'// for "hide" transitions 557 el.style.transition = prop ? `${prop} ${duration}ms ${ease || defaultCSSEasing}` : 'none'; 558} 559/** 560 * Apply width and height CSS properties to element 561 * 562 * @param {HTMLElement} el 563 * @param {string | number} w 564 * @param {string | number} h 565 */ 566 567function setWidthHeight(el, w, h) { 568 el.style.width = typeof w === 'number' ? `${w}px` : w; 569 el.style.height = typeof h === 'number' ? `${h}px` : h; 570} 571/** 572 * @param {HTMLElement} el 573 */ 574 575function removeTransitionStyle(el) { 576 setTransitionStyle(el); 577} 578/** 579 * @param {HTMLImageElement} img 580 * @returns {Promise<HTMLImageElement | void>} 581 */ 582 583function decodeImage(img) { 584 if ('decode' in img) { 585 return img.decode().catch(() => {}); 586 } 587 588 if (img.complete) { 589 return Promise.resolve(img); 590 } 591 592 return new Promise((resolve, reject) => { 593 img.onload = () => resolve(img); 594 595 img.onerror = reject; 596 }); 597} 598/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */ 599 600/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */ 601 602const LOAD_STATE = { 603 IDLE: 'idle', 604 LOADING: 'loading', 605 LOADED: 'loaded', 606 ERROR: 'error' 607}; 608/** 609 * Check if click or keydown event was dispatched 610 * with a special key or via mouse wheel. 611 * 612 * @param {MouseEvent | KeyboardEvent} e 613 * @returns {boolean} 614 */ 615 616function specialKeyUsed(e) { 617 return 'button' in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey; 618} 619/** 620 * Parse `gallery` or `children` options. 621 * 622 * @param {import('../photoswipe.js').ElementProvider} [option] 623 * @param {string} [legacySelector] 624 * @param {HTMLElement | Document} [parent] 625 * @returns HTMLElement[] 626 */ 627 628function getElementsFromOption(option, legacySelector, parent = document) { 629 /** @type {HTMLElement[]} */ 630 let elements = []; 631 632 if (option instanceof Element) { 633 elements = [option]; 634 } else if (option instanceof NodeList || Array.isArray(option)) { 635 elements = Array.from(option); 636 } else { 637 const selector = typeof option === 'string' ? option : legacySelector; 638 639 if (selector) { 640 elements = Array.from(parent.querySelectorAll(selector)); 641 } 642 } 643 644 return elements; 645} 646/** 647 * Check if browser is Safari 648 * 649 * @returns {boolean} 650 */ 651 652function isSafari() { 653 return !!(navigator.vendor && navigator.vendor.match(/apple/i)); 654} 655 656// Detect passive event listener support 657let supportsPassive = false; 658/* eslint-disable */ 659 660try { 661 /* @ts-ignore */ 662 window.addEventListener('test', null, Object.defineProperty({}, 'passive', { 663 get: () => { 664 supportsPassive = true; 665 } 666 })); 667} catch (e) {} 668/* eslint-enable */ 669 670/** 671 * @typedef {Object} PoolItem 672 * @prop {HTMLElement | Window | Document | undefined | null} target 673 * @prop {string} type 674 * @prop {EventListenerOrEventListenerObject} listener 675 * @prop {boolean} [passive] 676 */ 677 678 679class DOMEvents { 680 constructor() { 681 /** 682 * @type {PoolItem[]} 683 * @private 684 */ 685 this._pool = []; 686 } 687 /** 688 * Adds event listeners 689 * 690 * @param {PoolItem['target']} target 691 * @param {PoolItem['type']} type Can be multiple, separated by space. 692 * @param {PoolItem['listener']} listener 693 * @param {PoolItem['passive']} [passive] 694 */ 695 696 697 add(target, type, listener, passive) { 698 this._toggleListener(target, type, listener, passive); 699 } 700 /** 701 * Removes event listeners 702 * 703 * @param {PoolItem['target']} target 704 * @param {PoolItem['type']} type 705 * @param {PoolItem['listener']} listener 706 * @param {PoolItem['passive']} [passive] 707 */ 708 709 710 remove(target, type, listener, passive) { 711 this._toggleListener(target, type, listener, passive, true); 712 } 713 /** 714 * Removes all bound events 715 */ 716 717 718 removeAll() { 719 this._pool.forEach(poolItem => { 720 this._toggleListener(poolItem.target, poolItem.type, poolItem.listener, poolItem.passive, true, true); 721 }); 722 723 this._pool = []; 724 } 725 /** 726 * Adds or removes event 727 * 728 * @private 729 * @param {PoolItem['target']} target 730 * @param {PoolItem['type']} type 731 * @param {PoolItem['listener']} listener 732 * @param {PoolItem['passive']} [passive] 733 * @param {boolean} [unbind] Whether the event should be added or removed 734 * @param {boolean} [skipPool] Whether events pool should be skipped 735 */ 736 737 738 _toggleListener(target, type, listener, passive, unbind, skipPool) { 739 if (!target) { 740 return; 741 } 742 743 const methodName = unbind ? 'removeEventListener' : 'addEventListener'; 744 const types = type.split(' '); 745 types.forEach(eType => { 746 if (eType) { 747 // Events pool is used to easily unbind all events when PhotoSwipe is closed, 748 // so developer doesn't need to do this manually 749 if (!skipPool) { 750 if (unbind) { 751 // Remove from the events pool 752 this._pool = this._pool.filter(poolItem => { 753 return poolItem.type !== eType || poolItem.listener !== listener || poolItem.target !== target; 754 }); 755 } else { 756 // Add to the events pool 757 this._pool.push({ 758 target, 759 type: eType, 760 listener, 761 passive 762 }); 763 } 764 } // most PhotoSwipe events call preventDefault, 765 // and we do not need browser to scroll the page 766 767 768 const eventOptions = supportsPassive ? { 769 passive: passive || false 770 } : false; 771 target[methodName](eType, listener, eventOptions); 772 } 773 }); 774 } 775 776} 777 778/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 779 780/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ 781 782/** @typedef {import('../photoswipe.js').Point} Point */ 783 784/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 785 786/** 787 * @param {PhotoSwipeOptions} options 788 * @param {PhotoSwipeBase} pswp 789 * @returns {Point} 790 */ 791function getViewportSize(options, pswp) { 792 if (options.getViewportSizeFn) { 793 const newViewportSize = options.getViewportSizeFn(options, pswp); 794 795 if (newViewportSize) { 796 return newViewportSize; 797 } 798 } 799 800 return { 801 x: document.documentElement.clientWidth, 802 // TODO: height on mobile is very incosistent due to toolbar 803 // find a way to improve this 804 // 805 // document.documentElement.clientHeight - doesn't seem to work well 806 y: window.innerHeight 807 }; 808} 809/** 810 * Parses padding option. 811 * Supported formats: 812 * 813 * // Object 814 * padding: { 815 * top: 0, 816 * bottom: 0, 817 * left: 0, 818 * right: 0 819 * } 820 * 821 * // A function that returns the object 822 * paddingFn: (viewportSize, itemData, index) => { 823 * return { 824 * top: 0, 825 * bottom: 0, 826 * left: 0, 827 * right: 0 828 * }; 829 * } 830 * 831 * // Legacy variant 832 * paddingLeft: 0, 833 * paddingRight: 0, 834 * paddingTop: 0, 835 * paddingBottom: 0, 836 * 837 * @param {'left' | 'top' | 'bottom' | 'right'} prop 838 * @param {PhotoSwipeOptions} options PhotoSwipe options 839 * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 } 840 * @param {SlideData} itemData Data about the slide 841 * @param {number} index Slide index 842 * @returns {number} 843 */ 844 845function parsePaddingOption(prop, options, viewportSize, itemData, index) { 846 let paddingValue = 0; 847 848 if (options.paddingFn) { 849 paddingValue = options.paddingFn(viewportSize, itemData, index)[prop]; 850 } else if (options.padding) { 851 paddingValue = options.padding[prop]; 852 } else { 853 const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1); // @ts-expect-error 854 855 if (options[legacyPropName]) { 856 // @ts-expect-error 857 paddingValue = options[legacyPropName]; 858 } 859 } 860 861 return Number(paddingValue) || 0; 862} 863/** 864 * @param {PhotoSwipeOptions} options 865 * @param {Point} viewportSize 866 * @param {SlideData} itemData 867 * @param {number} index 868 * @returns {Point} 869 */ 870 871function getPanAreaSize(options, viewportSize, itemData, index) { 872 return { 873 x: viewportSize.x - parsePaddingOption('left', options, viewportSize, itemData, index) - parsePaddingOption('right', options, viewportSize, itemData, index), 874 y: viewportSize.y - parsePaddingOption('top', options, viewportSize, itemData, index) - parsePaddingOption('bottom', options, viewportSize, itemData, index) 875 }; 876} 877 878/** @typedef {import('./slide.js').default} Slide */ 879 880/** @typedef {Record<Axis, number>} Point */ 881 882/** @typedef {'x' | 'y'} Axis */ 883 884/** 885 * Calculates minimum, maximum and initial (center) bounds of a slide 886 */ 887 888class PanBounds { 889 /** 890 * @param {Slide} slide 891 */ 892 constructor(slide) { 893 this.slide = slide; 894 this.currZoomLevel = 1; 895 this.center = 896 /** @type {Point} */ 897 { 898 x: 0, 899 y: 0 900 }; 901 this.max = 902 /** @type {Point} */ 903 { 904 x: 0, 905 y: 0 906 }; 907 this.min = 908 /** @type {Point} */ 909 { 910 x: 0, 911 y: 0 912 }; 913 } 914 /** 915 * _getItemBounds 916 * 917 * @param {number} currZoomLevel 918 */ 919 920 921 update(currZoomLevel) { 922 this.currZoomLevel = currZoomLevel; 923 924 if (!this.slide.width) { 925 this.reset(); 926 } else { 927 this._updateAxis('x'); 928 929 this._updateAxis('y'); 930 931 this.slide.pswp.dispatch('calcBounds', { 932 slide: this.slide 933 }); 934 } 935 } 936 /** 937 * _calculateItemBoundsForAxis 938 * 939 * @param {Axis} axis 940 */ 941 942 943 _updateAxis(axis) { 944 const { 945 pswp 946 } = this.slide; 947 const elSize = this.slide[axis === 'x' ? 'width' : 'height'] * this.currZoomLevel; 948 const paddingProp = axis === 'x' ? 'left' : 'top'; 949 const padding = parsePaddingOption(paddingProp, pswp.options, pswp.viewportSize, this.slide.data, this.slide.index); 950 const panAreaSize = this.slide.panAreaSize[axis]; // Default position of element. 951 // By default, it is center of viewport: 952 953 this.center[axis] = Math.round((panAreaSize - elSize) / 2) + padding; // maximum pan position 954 955 this.max[axis] = elSize > panAreaSize ? Math.round(panAreaSize - elSize) + padding : this.center[axis]; // minimum pan position 956 957 this.min[axis] = elSize > panAreaSize ? padding : this.center[axis]; 958 } // _getZeroBounds 959 960 961 reset() { 962 this.center.x = 0; 963 this.center.y = 0; 964 this.max.x = 0; 965 this.max.y = 0; 966 this.min.x = 0; 967 this.min.y = 0; 968 } 969 /** 970 * Correct pan position if it's beyond the bounds 971 * 972 * @param {Axis} axis x or y 973 * @param {number} panOffset 974 * @returns {number} 975 */ 976 977 978 correctPan(axis, panOffset) { 979 // checkPanBounds 980 return clamp(panOffset, this.max[axis], this.min[axis]); 981 } 982 983} 984 985const MAX_IMAGE_WIDTH = 4000; 986/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 987 988/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 989 990/** @typedef {import('../photoswipe.js').Point} Point */ 991 992/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 993 994/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */ 995 996/** 997 * Calculates zoom levels for specific slide. 998 * Depends on viewport size and image size. 999 */ 1000 1001class ZoomLevel { 1002 /** 1003 * @param {PhotoSwipeOptions} options PhotoSwipe options 1004 * @param {SlideData} itemData Slide data 1005 * @param {number} index Slide index 1006 * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet 1007 */ 1008 constructor(options, itemData, index, pswp) { 1009 this.pswp = pswp; 1010 this.options = options; 1011 this.itemData = itemData; 1012 this.index = index; 1013 /** @type { Point | null } */ 1014 1015 this.panAreaSize = null; 1016 /** @type { Point | null } */ 1017 1018 this.elementSize = null; 1019 this.fit = 1; 1020 this.fill = 1; 1021 this.vFill = 1; 1022 this.initial = 1; 1023 this.secondary = 1; 1024 this.max = 1; 1025 this.min = 1; 1026 } 1027 /** 1028 * Calculate initial, secondary and maximum zoom level for the specified slide. 1029 * 1030 * It should be called when either image or viewport size changes. 1031 * 1032 * @param {number} maxWidth 1033 * @param {number} maxHeight 1034 * @param {Point} panAreaSize 1035 */ 1036 1037 1038 update(maxWidth, maxHeight, panAreaSize) { 1039 /** @type {Point} */ 1040 const elementSize = { 1041 x: maxWidth, 1042 y: maxHeight 1043 }; 1044 this.elementSize = elementSize; 1045 this.panAreaSize = panAreaSize; 1046 const hRatio = panAreaSize.x / elementSize.x; 1047 const vRatio = panAreaSize.y / elementSize.y; 1048 this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio); 1049 this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio); // zoom.vFill defines zoom level of the image 1050 // when it has 100% of viewport vertical space (height) 1051 1052 this.vFill = Math.min(1, vRatio); 1053 this.initial = this._getInitial(); 1054 this.secondary = this._getSecondary(); 1055 this.max = Math.max(this.initial, this.secondary, this._getMax()); 1056 this.min = Math.min(this.fit, this.initial, this.secondary); 1057 1058 if (this.pswp) { 1059 this.pswp.dispatch('zoomLevelsUpdate', { 1060 zoomLevels: this, 1061 slideData: this.itemData 1062 }); 1063 } 1064 } 1065 /** 1066 * Parses user-defined zoom option. 1067 * 1068 * @private 1069 * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max) 1070 * @returns { number | undefined } 1071 */ 1072 1073 1074 _parseZoomLevelOption(optionPrefix) { 1075 const optionName = 1076 /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */ 1077 optionPrefix + 'ZoomLevel'; 1078 const optionValue = this.options[optionName]; 1079 1080 if (!optionValue) { 1081 return; 1082 } 1083 1084 if (typeof optionValue === 'function') { 1085 return optionValue(this); 1086 } 1087 1088 if (optionValue === 'fill') { 1089 return this.fill; 1090 } 1091 1092 if (optionValue === 'fit') { 1093 return this.fit; 1094 } 1095 1096 return Number(optionValue); 1097 } 1098 /** 1099 * Get zoom level to which image will be zoomed after double-tap gesture, 1100 * or when user clicks on zoom icon, 1101 * or mouse-click on image itself. 1102 * If you return 1 image will be zoomed to its original size. 1103 * 1104 * @private 1105 * @return {number} 1106 */ 1107 1108 1109 _getSecondary() {
vendor: 4,103 bytes, lines 1110-1296
1110 let currZoomLevel = this._parseZoomLevelOption('secondary'); 1111 1112 if (currZoomLevel) { 1113 return currZoomLevel; 1114 } // 3x of "fit" state, but not larger than original 1115 1116 1117 currZoomLevel = Math.min(1, this.fit * 3); 1118 1119 if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) { 1120 currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x; 1121 } 1122 1123 return currZoomLevel; 1124 } 1125 /** 1126 * Get initial image zoom level. 1127 * 1128 * @private 1129 * @return {number} 1130 */ 1131 1132 1133 _getInitial() { 1134 return this._parseZoomLevelOption('initial') || this.fit; 1135 } 1136 /** 1137 * Maximum zoom level when user zooms 1138 * via zoom/pinch gesture, 1139 * via cmd/ctrl-wheel or via trackpad. 1140 * 1141 * @private 1142 * @return {number} 1143 */ 1144 1145 1146 _getMax() { 1147 // max zoom level is x4 from "fit state", 1148 // used for zoom gesture and ctrl/trackpad zoom 1149 return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4); 1150 } 1151 1152} 1153 1154/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 1155/** 1156 * Renders and allows to control a single slide 1157 */ 1158 1159class Slide { 1160 /** 1161 * @param {SlideData} data 1162 * @param {number} index 1163 * @param {PhotoSwipe} pswp 1164 */ 1165 constructor(data, index, pswp) { 1166 this.data = data; 1167 this.index = index; 1168 this.pswp = pswp; 1169 this.isActive = index === pswp.currIndex; 1170 this.currentResolution = 0; 1171 /** @type {Point} */ 1172 1173 this.panAreaSize = { 1174 x: 0, 1175 y: 0 1176 }; 1177 /** @type {Point} */ 1178 1179 this.pan = { 1180 x: 0, 1181 y: 0 1182 }; 1183 this.isFirstSlide = this.isActive && !pswp.opener.isOpen; 1184 this.zoomLevels = new ZoomLevel(pswp.options, data, index, pswp); 1185 this.pswp.dispatch('gettingData', { 1186 slide: this, 1187 data: this.data, 1188 index 1189 }); 1190 this.content = this.pswp.contentLoader.getContentBySlide(this); 1191 this.container = createElement('pswp__zoom-wrap', 'div'); 1192 /** @type {HTMLElement | null} */ 1193 1194 this.holderElement = null; 1195 this.currZoomLevel = 1; 1196 /** @type {number} */ 1197 1198 this.width = this.content.width; 1199 /** @type {number} */ 1200 1201 this.height = this.content.height; 1202 this.heavyAppended = false; 1203 this.bounds = new PanBounds(this); 1204 this.prevDisplayedWidth = -1; 1205 this.prevDisplayedHeight = -1; 1206 this.pswp.dispatch('slideInit', { 1207 slide: this 1208 }); 1209 } 1210 /** 1211 * If this slide is active/current/visible 1212 * 1213 * @param {boolean} isActive 1214 */ 1215 1216 1217 setIsActive(isActive) { 1218 if (isActive && !this.isActive) { 1219 // slide just became active 1220 this.activate(); 1221 } else if (!isActive && this.isActive) { 1222 // slide just became non-active 1223 this.deactivate(); 1224 } 1225 } 1226 /** 1227 * Appends slide content to DOM 1228 * 1229 * @param {HTMLElement} holderElement 1230 */ 1231 1232 1233 append(holderElement) { 1234 this.holderElement = holderElement; 1235 this.container.style.transformOrigin = '0 0'; // Slide appended to DOM 1236 1237 if (!this.data) { 1238 return; 1239 } 1240 1241 this.calculateSize(); 1242 this.load(); 1243 this.updateContentSize(); 1244 this.appendHeavy(); 1245 this.holderElement.appendChild(this.container); 1246 this.zoomAndPanToInitial(); 1247 this.pswp.dispatch('firstZoomPan', { 1248 slide: this 1249 }); 1250 this.applyCurrentZoomPan(); 1251 this.pswp.dispatch('afterSetContent', { 1252 slide: this 1253 }); 1254 1255 if (this.isActive) { 1256 this.activate(); 1257 } 1258 } 1259 1260 load() { 1261 this.content.load(false); 1262 this.pswp.dispatch('slideLoad', { 1263 slide: this 1264 }); 1265 } 1266 /** 1267 * Append "heavy" DOM elements 1268 * 1269 * This may depend on a type of slide, 1270 * but generally these are large images. 1271 */ 1272 1273 1274 appendHeavy() { 1275 const { 1276 pswp 1277 } = this; 1278 // Avoid appending heavy elements during animations 1279 1280 if (this.heavyAppended || !pswp.opener.isOpen || pswp.mainScroll.isShifted() || !this.isActive && false) { 1281 return; 1282 } 1283 1284 if (this.pswp.dispatch('appendHeavy', { 1285 slide: this 1286 }).defaultPrevented) { 1287 return; 1288 } 1289 1290 this.heavyAppended = true; 1291 this.content.append(); 1292 this.pswp.dispatch('appendHeavyContent', { 1293 slide: this 1294 }); 1295 } 1296 /**
vendor: 107,300 bytes, lines 1297-5251
1297 * Triggered when this slide is active (selected). 1298 * 1299 * If it's part of opening/closing transition - 1300 * activate() will trigger after the transition is ended. 1301 */ 1302 1303 1304 activate() { 1305 this.isActive = true; 1306 this.appendHeavy(); 1307 this.content.activate(); 1308 this.pswp.dispatch('slideActivate', { 1309 slide: this 1310 }); 1311 } 1312 /** 1313 * Triggered when this slide becomes inactive. 1314 * 1315 * Slide can become inactive only after it was active. 1316 */ 1317 1318 1319 deactivate() { 1320 this.isActive = false; 1321 this.content.deactivate(); 1322 1323 if (this.currZoomLevel !== this.zoomLevels.initial) { 1324 // allow filtering 1325 this.calculateSize(); 1326 } // reset zoom level 1327 1328 1329 this.currentResolution = 0; 1330 this.zoomAndPanToInitial(); 1331 this.applyCurrentZoomPan(); 1332 this.updateContentSize(); 1333 this.pswp.dispatch('slideDeactivate', { 1334 slide: this 1335 }); 1336 } 1337 /** 1338 * The slide should destroy itself, it will never be used again. 1339 * (unbind all events and destroy internal components) 1340 */ 1341 1342 1343 destroy() { 1344 this.content.hasSlide = false; 1345 this.content.remove(); 1346 this.container.remove(); 1347 this.pswp.dispatch('slideDestroy', { 1348 slide: this 1349 }); 1350 } 1351 1352 resize() { 1353 if (this.currZoomLevel === this.zoomLevels.initial || !this.isActive) { 1354 // Keep initial zoom level if it was before the resize, 1355 // as well as when this slide is not active 1356 // Reset position and scale to original state 1357 this.calculateSize(); 1358 this.currentResolution = 0; 1359 this.zoomAndPanToInitial(); 1360 this.applyCurrentZoomPan(); 1361 this.updateContentSize(); 1362 } else { 1363 // readjust pan position if it's beyond the bounds 1364 this.calculateSize(); 1365 this.bounds.update(this.currZoomLevel); 1366 this.panTo(this.pan.x, this.pan.y); 1367 } 1368 } 1369 /** 1370 * Apply size to current slide content, 1371 * based on the current resolution and scale. 1372 * 1373 * @param {boolean} [force] if size should be updated even if dimensions weren't changed 1374 */ 1375 1376 1377 updateContentSize(force) { 1378 // Use initial zoom level 1379 // if resolution is not defined (user didn't zoom yet) 1380 const scaleMultiplier = this.currentResolution || this.zoomLevels.initial; 1381 1382 if (!scaleMultiplier) { 1383 return; 1384 } 1385 1386 const width = Math.round(this.width * scaleMultiplier) || this.pswp.viewportSize.x; 1387 const height = Math.round(this.height * scaleMultiplier) || this.pswp.viewportSize.y; 1388 1389 if (!this.sizeChanged(width, height) && !force) { 1390 return; 1391 } 1392 1393 this.content.setDisplayedSize(width, height); 1394 } 1395 /** 1396 * @param {number} width 1397 * @param {number} height 1398 */ 1399 1400 1401 sizeChanged(width, height) { 1402 if (width !== this.prevDisplayedWidth || height !== this.prevDisplayedHeight) { 1403 this.prevDisplayedWidth = width; 1404 this.prevDisplayedHeight = height; 1405 return true; 1406 } 1407 1408 return false; 1409 } 1410 /** @returns {HTMLImageElement | HTMLDivElement | null | undefined} */ 1411 1412 1413 getPlaceholderElement() { 1414 var _this$content$placeho; 1415 1416 return (_this$content$placeho = this.content.placeholder) === null || _this$content$placeho === void 0 ? void 0 : _this$content$placeho.element; 1417 } 1418 /** 1419 * Zoom current slide image to... 1420 * 1421 * @param {number} destZoomLevel Destination zoom level. 1422 * @param {Point} [centerPoint] 1423 * Transform origin center point, or false if viewport center should be used. 1424 * @param {number | false} [transitionDuration] Transition duration, may be set to 0. 1425 * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored. 1426 */ 1427 1428 1429 zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) { 1430 const { 1431 pswp 1432 } = this; 1433 1434 if (!this.isZoomable() || pswp.mainScroll.isShifted()) { 1435 return; 1436 } 1437 1438 pswp.dispatch('beforeZoomTo', { 1439 destZoomLevel, 1440 centerPoint, 1441 transitionDuration 1442 }); // stop all pan and zoom transitions 1443 1444 pswp.animations.stopAllPan(); // if (!centerPoint) { 1445 // centerPoint = pswp.getViewportCenterPoint(); 1446 // } 1447 1448 const prevZoomLevel = this.currZoomLevel; 1449 1450 if (!ignoreBounds) { 1451 destZoomLevel = clamp(destZoomLevel, this.zoomLevels.min, this.zoomLevels.max); 1452 } // if (transitionDuration === undefined) { 1453 // transitionDuration = this.pswp.options.zoomAnimationDuration; 1454 // } 1455 1456 1457 this.setZoomLevel(destZoomLevel); 1458 this.pan.x = this.calculateZoomToPanOffset('x', centerPoint, prevZoomLevel); 1459 this.pan.y = this.calculateZoomToPanOffset('y', centerPoint, prevZoomLevel); 1460 roundPoint(this.pan); 1461 1462 const finishTransition = () => { 1463 this._setResolution(destZoomLevel); 1464 1465 this.applyCurrentZoomPan(); 1466 }; 1467 1468 if (!transitionDuration) { 1469 finishTransition(); 1470 } else { 1471 pswp.animations.startTransition({ 1472 isPan: true, 1473 name: 'zoomTo', 1474 target: this.container, 1475 transform: this.getCurrentTransform(), 1476 onComplete: finishTransition, 1477 duration: transitionDuration, 1478 easing: pswp.options.easing 1479 }); 1480 } 1481 } 1482 /** 1483 * @param {Point} [centerPoint] 1484 */ 1485 1486 1487 toggleZoom(centerPoint) { 1488 this.zoomTo(this.currZoomLevel === this.zoomLevels.initial ? this.zoomLevels.secondary : this.zoomLevels.initial, centerPoint, this.pswp.options.zoomAnimationDuration); 1489 } 1490 /** 1491 * Updates zoom level property and recalculates new pan bounds, 1492 * unlike zoomTo it does not apply transform (use applyCurrentZoomPan) 1493 * 1494 * @param {number} currZoomLevel 1495 */ 1496 1497 1498 setZoomLevel(currZoomLevel) { 1499 this.currZoomLevel = currZoomLevel; 1500 this.bounds.update(this.currZoomLevel); 1501 } 1502 /** 1503 * Get pan position after zoom at a given `point`. 1504 * 1505 * Always call setZoomLevel(newZoomLevel) beforehand to recalculate 1506 * pan bounds according to the new zoom level. 1507 * 1508 * @param {'x' | 'y'} axis 1509 * @param {Point} [point] 1510 * point based on which zoom is performed, usually refers to the current mouse position, 1511 * if false - viewport center will be used. 1512 * @param {number} [prevZoomLevel] Zoom level before new zoom was applied. 1513 * @returns {number} 1514 */ 1515 1516 1517 calculateZoomToPanOffset(axis, point, prevZoomLevel) { 1518 const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis]; 1519 1520 if (totalPanDistance === 0) { 1521 return this.bounds.center[axis]; 1522 } 1523 1524 if (!point) { 1525 point = this.pswp.getViewportCenterPoint(); 1526 } 1527 1528 if (!prevZoomLevel) { 1529 prevZoomLevel = this.zoomLevels.initial; 1530 } 1531 1532 const zoomFactor = this.currZoomLevel / prevZoomLevel; 1533 return this.bounds.correctPan(axis, (this.pan[axis] - point[axis]) * zoomFactor + point[axis]); 1534 } 1535 /** 1536 * Apply pan and keep it within bounds. 1537 * 1538 * @param {number} panX 1539 * @param {number} panY 1540 */ 1541 1542 1543 panTo(panX, panY) { 1544 this.pan.x = this.bounds.correctPan('x', panX); 1545 this.pan.y = this.bounds.correctPan('y', panY); 1546 this.applyCurrentZoomPan(); 1547 } 1548 /** 1549 * If the slide in the current state can be panned by the user 1550 * @returns {boolean} 1551 */ 1552 1553 1554 isPannable() { 1555 return Boolean(this.width) && this.currZoomLevel > this.zoomLevels.fit; 1556 } 1557 /** 1558 * If the slide can be zoomed 1559 * @returns {boolean} 1560 */ 1561 1562 1563 isZoomable() { 1564 return Boolean(this.width) && this.content.isZoomable(); 1565 } 1566 /** 1567 * Apply transform and scale based on 1568 * the current pan position (this.pan) and zoom level (this.currZoomLevel) 1569 */ 1570 1571 1572 applyCurrentZoomPan() { 1573 this._applyZoomTransform(this.pan.x, this.pan.y, this.currZoomLevel); 1574 1575 if (this === this.pswp.currSlide) { 1576 this.pswp.dispatch('zoomPanUpdate', { 1577 slide: this 1578 }); 1579 } 1580 } 1581 1582 zoomAndPanToInitial() { 1583 this.currZoomLevel = this.zoomLevels.initial; // pan according to the zoom level 1584 1585 this.bounds.update(this.currZoomLevel); 1586 equalizePoints(this.pan, this.bounds.center); 1587 this.pswp.dispatch('initialZoomPan', { 1588 slide: this 1589 }); 1590 } 1591 /** 1592 * Set translate and scale based on current resolution 1593 * 1594 * @param {number} x 1595 * @param {number} y 1596 * @param {number} zoom 1597 * @private 1598 */ 1599 1600 1601 _applyZoomTransform(x, y, zoom) { 1602 zoom /= this.currentResolution || this.zoomLevels.initial; 1603 setTransform(this.container, x, y, zoom); 1604 } 1605 1606 calculateSize() { 1607 const { 1608 pswp 1609 } = this; 1610 equalizePoints(this.panAreaSize, getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index)); 1611 this.zoomLevels.update(this.width, this.height, this.panAreaSize); 1612 pswp.dispatch('calcSlideSize', { 1613 slide: this 1614 }); 1615 } 1616 /** @returns {string} */ 1617 1618 1619 getCurrentTransform() { 1620 const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial); 1621 return toTransformString(this.pan.x, this.pan.y, scale); 1622 } 1623 /** 1624 * Set resolution and re-render the image. 1625 * 1626 * For example, if the real image size is 2000x1500, 1627 * and resolution is 0.5 - it will be rendered as 1000x750. 1628 * 1629 * Image with zoom level 2 and resolution 0.5 is 1630 * the same as image with zoom level 1 and resolution 1. 1631 * 1632 * Used to optimize animations and make 1633 * sure that browser renders image in the highest quality. 1634 * Also used by responsive images to load the correct one. 1635 * 1636 * @param {number} newResolution 1637 */ 1638 1639 1640 _setResolution(newResolution) { 1641 if (newResolution === this.currentResolution) { 1642 return; 1643 } 1644 1645 this.currentResolution = newResolution; 1646 this.updateContentSize(); 1647 this.pswp.dispatch('resolutionChanged'); 1648 } 1649 1650} 1651 1652/** @typedef {import('../photoswipe.js').Point} Point */ 1653 1654/** @typedef {import('./gestures.js').default} Gestures */ 1655 1656const PAN_END_FRICTION = 0.35; 1657const VERTICAL_DRAG_FRICTION = 0.6; // 1 corresponds to the third of viewport height 1658 1659const MIN_RATIO_TO_CLOSE = 0.4; // Minimum speed required to navigate 1660// to next or previous slide 1661 1662const MIN_NEXT_SLIDE_SPEED = 0.5; 1663/** 1664 * @param {number} initialVelocity 1665 * @param {number} decelerationRate 1666 * @returns {number} 1667 */ 1668 1669function project(initialVelocity, decelerationRate) { 1670 return initialVelocity * decelerationRate / (1 - decelerationRate); 1671} 1672/** 1673 * Handles single pointer dragging 1674 */ 1675 1676 1677class DragHandler { 1678 /** 1679 * @param {Gestures} gestures 1680 */ 1681 constructor(gestures) { 1682 this.gestures = gestures; 1683 this.pswp = gestures.pswp; 1684 /** @type {Point} */ 1685 1686 this.startPan = { 1687 x: 0, 1688 y: 0 1689 }; 1690 } 1691 1692 start() { 1693 if (this.pswp.currSlide) { 1694 equalizePoints(this.startPan, this.pswp.currSlide.pan); 1695 } 1696 1697 this.pswp.animations.stopAll(); 1698 } 1699 1700 change() { 1701 const { 1702 p1, 1703 prevP1, 1704 dragAxis 1705 } = this.gestures; 1706 const { 1707 currSlide 1708 } = this.pswp; 1709 1710 if (dragAxis === 'y' && this.pswp.options.closeOnVerticalDrag && currSlide && currSlide.currZoomLevel <= currSlide.zoomLevels.fit && !this.gestures.isMultitouch) { 1711 // Handle vertical drag to close 1712 const panY = currSlide.pan.y + (p1.y - prevP1.y); 1713 1714 if (!this.pswp.dispatch('verticalDrag', { 1715 panY 1716 }).defaultPrevented) { 1717 this._setPanWithFriction('y', panY, VERTICAL_DRAG_FRICTION); 1718 1719 const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y)); 1720 this.pswp.applyBgOpacity(bgOpacity); 1721 currSlide.applyCurrentZoomPan(); 1722 } 1723 } else { 1724 const mainScrollChanged = this._panOrMoveMainScroll('x'); 1725 1726 if (!mainScrollChanged) { 1727 this._panOrMoveMainScroll('y'); 1728 1729 if (currSlide) { 1730 roundPoint(currSlide.pan); 1731 currSlide.applyCurrentZoomPan(); 1732 } 1733 } 1734 } 1735 } 1736 1737 end() { 1738 const { 1739 velocity 1740 } = this.gestures; 1741 const { 1742 mainScroll, 1743 currSlide 1744 } = this.pswp; 1745 let indexDiff = 0; 1746 this.pswp.animations.stopAll(); // Handle main scroll if it's shifted 1747 1748 if (mainScroll.isShifted()) { 1749 // Position of the main scroll relative to the viewport 1750 const mainScrollShiftDiff = mainScroll.x - mainScroll.getCurrSlideX(); // Ratio between 0 and 1: 1751 // 0 - slide is not visible at all, 1752 // 0.5 - half of the slide is visible 1753 // 1 - slide is fully visible 1754 1755 const currentSlideVisibilityRatio = mainScrollShiftDiff / this.pswp.viewportSize.x; // Go next slide. 1756 // 1757 // - if velocity and its direction is matched, 1758 // and we see at least tiny part of the next slide 1759 // 1760 // - or if we see less than 50% of the current slide 1761 // and velocity is close to 0 1762 // 1763 1764 if (velocity.x < -MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio < 0 || velocity.x < 0.1 && currentSlideVisibilityRatio < -0.5) { 1765 // Go to next slide 1766 indexDiff = 1; 1767 velocity.x = Math.min(velocity.x, 0); 1768 } else if (velocity.x > MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio > 0 || velocity.x > -0.1 && currentSlideVisibilityRatio > 0.5) { 1769 // Go to prev slide 1770 indexDiff = -1; 1771 velocity.x = Math.max(velocity.x, 0); 1772 } 1773 1774 mainScroll.moveIndexBy(indexDiff, true, velocity.x); 1775 } // Restore zoom level 1776 1777 1778 if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.max || this.gestures.isMultitouch) { 1779 this.gestures.zoomLevels.correctZoomPan(true); 1780 } else { 1781 // we run two animations instead of one, 1782 // as each axis has own pan boundaries and thus different spring function 1783 // (correctZoomPan does not have this functionality, 1784 // it animates all properties with single timing function) 1785 this._finishPanGestureForAxis('x'); 1786 1787 this._finishPanGestureForAxis('y'); 1788 } 1789 } 1790 /** 1791 * @private 1792 * @param {'x' | 'y'} axis 1793 */ 1794 1795 1796 _finishPanGestureForAxis(axis) { 1797 const { 1798 velocity 1799 } = this.gestures; 1800 const { 1801 currSlide 1802 } = this.pswp; 1803 1804 if (!currSlide) { 1805 return; 1806 } 1807 1808 const { 1809 pan, 1810 bounds 1811 } = currSlide; 1812 const panPos = pan[axis]; 1813 const restoreBgOpacity = this.pswp.bgOpacity < 1 && axis === 'y'; // 0.995 means - scroll view loses 0.5% of its velocity per millisecond 1814 // Increasing this number will reduce travel distance 1815 1816 const decelerationRate = 0.995; // 0.99 1817 // Pan position if there is no bounds 1818 1819 const projectedPosition = panPos + project(velocity[axis], decelerationRate); 1820 1821 if (restoreBgOpacity) { 1822 const vDragRatio = this._getVerticalDragRatio(panPos); 1823 1824 const projectedVDragRatio = this._getVerticalDragRatio(projectedPosition); // If we are above and moving upwards, 1825 // or if we are below and moving downwards 1826 1827 1828 if (vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE || vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE) { 1829 this.pswp.close(); 1830 return; 1831 } 1832 } // Pan position with corrected bounds 1833 1834 1835 const correctedPanPosition = bounds.correctPan(axis, projectedPosition); // Exit if pan position should not be changed 1836 // or if speed it too low 1837 1838 if (panPos === correctedPanPosition) { 1839 return; 1840 } // Overshoot if the final position is out of pan bounds 1841 1842 1843 const dampingRatio = correctedPanPosition === projectedPosition ? 1 : 0.82; 1844 const initialBgOpacity = this.pswp.bgOpacity; 1845 const totalPanDist = correctedPanPosition - panPos; 1846 this.pswp.animations.startSpring({ 1847 name: 'panGesture' + axis, 1848 isPan: true, 1849 start: panPos, 1850 end: correctedPanPosition, 1851 velocity: velocity[axis], 1852 dampingRatio, 1853 onUpdate: pos => { 1854 // Animate opacity of background relative to Y pan position of an image 1855 if (restoreBgOpacity && this.pswp.bgOpacity < 1) { 1856 // 0 - start of animation, 1 - end of animation 1857 const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist; // We clamp opacity to keep it between 0 and 1. 1858 // As progress ratio can be larger than 1 due to overshoot, 1859 // and we do not want to bounce opacity. 1860 1861 this.pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio, 0, 1)); 1862 } 1863 1864 pan[axis] = Math.floor(pos); 1865 currSlide.applyCurrentZoomPan(); 1866 } 1867 }); 1868 } 1869 /** 1870 * Update position of the main scroll, 1871 * or/and update pan position of the current slide. 1872 * 1873 * Should return true if it changes (or can change) main scroll. 1874 * 1875 * @private 1876 * @param {'x' | 'y'} axis 1877 * @returns {boolean} 1878 */ 1879 1880 1881 _panOrMoveMainScroll(axis) { 1882 const { 1883 p1, 1884 dragAxis, 1885 prevP1, 1886 isMultitouch 1887 } = this.gestures; 1888 const { 1889 currSlide, 1890 mainScroll 1891 } = this.pswp; 1892 const delta = p1[axis] - prevP1[axis]; 1893 const newMainScrollX = mainScroll.x + delta; 1894 1895 if (!delta || !currSlide) { 1896 return false; 1897 } // Always move main scroll if image can not be panned 1898 1899 1900 if (axis === 'x' && !currSlide.isPannable() && !isMultitouch) { 1901 mainScroll.moveTo(newMainScrollX, true); 1902 return true; // changed main scroll 1903 } 1904 1905 const { 1906 bounds 1907 } = currSlide; 1908 const newPan = currSlide.pan[axis] + delta; 1909 1910 if (this.pswp.options.allowPanToNext && dragAxis === 'x' && axis === 'x' && !isMultitouch) { 1911 const currSlideMainScrollX = mainScroll.getCurrSlideX(); // Position of the main scroll relative to the viewport 1912 1913 const mainScrollShiftDiff = mainScroll.x - currSlideMainScrollX; 1914 const isLeftToRight = delta > 0; 1915 const isRightToLeft = !isLeftToRight; 1916 1917 if (newPan > bounds.min[axis] && isLeftToRight) { 1918 // Panning from left to right, beyond the left edge 1919 // Wether the image was at minimum pan position (or less) 1920 // when this drag gesture started. 1921 // Minimum pan position refers to the left edge of the image. 1922 const wasAtMinPanPosition = bounds.min[axis] <= this.startPan[axis]; 1923 1924 if (wasAtMinPanPosition) { 1925 mainScroll.moveTo(newMainScrollX, true); 1926 return true; 1927 } else { 1928 this._setPanWithFriction(axis, newPan); //currSlide.pan[axis] = newPan; 1929 1930 } 1931 } else if (newPan < bounds.max[axis] && isRightToLeft) { 1932 // Paning from right to left, beyond the right edge 1933 // Maximum pan position refers to the right edge of the image. 1934 const wasAtMaxPanPosition = this.startPan[axis] <= bounds.max[axis]; 1935 1936 if (wasAtMaxPanPosition) { 1937 mainScroll.moveTo(newMainScrollX, true); 1938 return true; 1939 } else { 1940 this._setPanWithFriction(axis, newPan); //currSlide.pan[axis] = newPan; 1941 1942 } 1943 } else { 1944 // If main scroll is shifted 1945 if (mainScrollShiftDiff !== 0) { 1946 // If main scroll is shifted right 1947 if (mainScrollShiftDiff > 0 1948 /*&& isRightToLeft*/ 1949 ) { 1950 mainScroll.moveTo(Math.max(newMainScrollX, currSlideMainScrollX), true); 1951 return true; 1952 } else if (mainScrollShiftDiff < 0 1953 /*&& isLeftToRight*/ 1954 ) { 1955 // Main scroll is shifted left (Position is less than 0 comparing to the viewport 0) 1956 mainScroll.moveTo(Math.min(newMainScrollX, currSlideMainScrollX), true); 1957 return true; 1958 } 1959 } else { 1960 // We are within pan bounds, so just pan 1961 this._setPanWithFriction(axis, newPan); 1962 } 1963 } 1964 } else { 1965 if (axis === 'y') { 1966 // Do not pan vertically if main scroll is shifted o 1967 if (!mainScroll.isShifted() && bounds.min.y !== bounds.max.y) { 1968 this._setPanWithFriction(axis, newPan); 1969 } 1970 } else { 1971 this._setPanWithFriction(axis, newPan); 1972 } 1973 } 1974 1975 return false; 1976 } // If we move above - the ratio is negative 1977 // If we move below the ratio is positive 1978 1979 /** 1980 * Relation between pan Y position and third of viewport height. 1981 * 1982 * When we are at initial position (center bounds) - the ratio is 0, 1983 * if position is shifted upwards - the ratio is negative, 1984 * if position is shifted downwards - the ratio is positive. 1985 * 1986 * @private 1987 * @param {number} panY The current pan Y position. 1988 * @returns {number} 1989 */ 1990 1991 1992 _getVerticalDragRatio(panY) { 1993 var _this$pswp$currSlide$, _this$pswp$currSlide; 1994 1995 return (panY - ((_this$pswp$currSlide$ = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.bounds.center.y) !== null && _this$pswp$currSlide$ !== void 0 ? _this$pswp$currSlide$ : 0)) / (this.pswp.viewportSize.y / 3); 1996 } 1997 /** 1998 * Set pan position of the current slide. 1999 * Apply friction if the position is beyond the pan bounds, 2000 * or if custom friction is defined. 2001 * 2002 * @private 2003 * @param {'x' | 'y'} axis 2004 * @param {number} potentialPan 2005 * @param {number} [customFriction] (0.1 - 1) 2006 */ 2007 2008 2009 _setPanWithFriction(axis, potentialPan, customFriction) { 2010 const { 2011 currSlide 2012 } = this.pswp; 2013 2014 if (!currSlide) { 2015 return; 2016 } 2017 2018 const { 2019 pan, 2020 bounds 2021 } = currSlide; 2022 const correctedPan = bounds.correctPan(axis, potentialPan); // If we are out of pan bounds 2023 2024 if (correctedPan !== potentialPan || customFriction) { 2025 const delta = Math.round(potentialPan - pan[axis]); 2026 pan[axis] += delta * (customFriction || PAN_END_FRICTION); 2027 } else { 2028 pan[axis] = potentialPan; 2029 } 2030 } 2031 2032} 2033 2034/** @typedef {import('../photoswipe.js').Point} Point */ 2035 2036/** @typedef {import('./gestures.js').default} Gestures */ 2037 2038const UPPER_ZOOM_FRICTION = 0.05; 2039const LOWER_ZOOM_FRICTION = 0.15; 2040/** 2041 * Get center point between two points 2042 * 2043 * @param {Point} p 2044 * @param {Point} p1 2045 * @param {Point} p2 2046 * @returns {Point} 2047 */ 2048 2049function getZoomPointsCenter(p, p1, p2) { 2050 p.x = (p1.x + p2.x) / 2; 2051 p.y = (p1.y + p2.y) / 2; 2052 return p; 2053} 2054 2055class ZoomHandler { 2056 /** 2057 * @param {Gestures} gestures 2058 */ 2059 constructor(gestures) { 2060 this.gestures = gestures; 2061 /** 2062 * @private 2063 * @type {Point} 2064 */ 2065 2066 this._startPan = { 2067 x: 0, 2068 y: 0 2069 }; 2070 /** 2071 * @private 2072 * @type {Point} 2073 */ 2074 2075 this._startZoomPoint = { 2076 x: 0, 2077 y: 0 2078 }; 2079 /** 2080 * @private 2081 * @type {Point} 2082 */ 2083 2084 this._zoomPoint = { 2085 x: 0, 2086 y: 0 2087 }; 2088 /** @private */ 2089 2090 this._wasOverFitZoomLevel = false; 2091 /** @private */ 2092 2093 this._startZoomLevel = 1; 2094 } 2095 2096 start() { 2097 const { 2098 currSlide 2099 } = this.gestures.pswp; 2100 2101 if (currSlide) { 2102 this._startZoomLevel = currSlide.currZoomLevel; 2103 equalizePoints(this._startPan, currSlide.pan); 2104 } 2105 2106 this.gestures.pswp.animations.stopAllPan(); 2107 this._wasOverFitZoomLevel = false; 2108 } 2109 2110 change() { 2111 const { 2112 p1, 2113 startP1, 2114 p2, 2115 startP2, 2116 pswp 2117 } = this.gestures; 2118 const { 2119 currSlide 2120 } = pswp; 2121 2122 if (!currSlide) { 2123 return; 2124 } 2125 2126 const minZoomLevel = currSlide.zoomLevels.min; 2127 const maxZoomLevel = currSlide.zoomLevels.max; 2128 2129 if (!currSlide.isZoomable() || pswp.mainScroll.isShifted()) { 2130 return; 2131 } 2132 2133 getZoomPointsCenter(this._startZoomPoint, startP1, startP2); 2134 getZoomPointsCenter(this._zoomPoint, p1, p2); 2135 2136 let currZoomLevel = 1 / getDistanceBetween(startP1, startP2) * getDistanceBetween(p1, p2) * this._startZoomLevel; // slightly over the zoom.fit 2137 2138 2139 if (currZoomLevel > currSlide.zoomLevels.initial + currSlide.zoomLevels.initial / 15) { 2140 this._wasOverFitZoomLevel = true; 2141 } 2142 2143 if (currZoomLevel < minZoomLevel) { 2144 if (pswp.options.pinchToClose && !this._wasOverFitZoomLevel && this._startZoomLevel <= currSlide.zoomLevels.initial) { 2145 // fade out background if zooming out 2146 const bgOpacity = 1 - (minZoomLevel - currZoomLevel) / (minZoomLevel / 1.2); 2147 2148 if (!pswp.dispatch('pinchClose', { 2149 bgOpacity 2150 }).defaultPrevented) { 2151 pswp.applyBgOpacity(bgOpacity); 2152 } 2153 } else { 2154 // Apply the friction if zoom level is below the min 2155 currZoomLevel = minZoomLevel - (minZoomLevel - currZoomLevel) * LOWER_ZOOM_FRICTION; 2156 } 2157 } else if (currZoomLevel > maxZoomLevel) { 2158 // Apply the friction if zoom level is above the max 2159 currZoomLevel = maxZoomLevel + (currZoomLevel - maxZoomLevel) * UPPER_ZOOM_FRICTION; 2160 } 2161 2162 currSlide.pan.x = this._calculatePanForZoomLevel('x', currZoomLevel); 2163 currSlide.pan.y = this._calculatePanForZoomLevel('y', currZoomLevel); 2164 currSlide.setZoomLevel(currZoomLevel); 2165 currSlide.applyCurrentZoomPan(); 2166 } 2167 2168 end() { 2169 const { 2170 pswp 2171 } = this.gestures; 2172 const { 2173 currSlide 2174 } = pswp; 2175 2176 if ((!currSlide || currSlide.currZoomLevel < currSlide.zoomLevels.initial) && !this._wasOverFitZoomLevel && pswp.options.pinchToClose) { 2177 pswp.close(); 2178 } else { 2179 this.correctZoomPan(); 2180 } 2181 } 2182 /** 2183 * @private 2184 * @param {'x' | 'y'} axis 2185 * @param {number} currZoomLevel 2186 * @returns {number} 2187 */ 2188 2189 2190 _calculatePanForZoomLevel(axis, currZoomLevel) { 2191 const zoomFactor = currZoomLevel / this._startZoomLevel; 2192 return this._zoomPoint[axis] - (this._startZoomPoint[axis] - this._startPan[axis]) * zoomFactor; 2193 } 2194 /** 2195 * Correct currZoomLevel and pan if they are 2196 * beyond minimum or maximum values. 2197 * With animation. 2198 * 2199 * @param {boolean} [ignoreGesture] 2200 * Wether gesture coordinates should be ignored when calculating destination pan position. 2201 */ 2202 2203 2204 correctZoomPan(ignoreGesture) { 2205 const { 2206 pswp 2207 } = this.gestures; 2208 const { 2209 currSlide 2210 } = pswp; 2211 2212 if (!(currSlide !== null && currSlide !== void 0 && currSlide.isZoomable())) { 2213 return; 2214 } 2215 2216 if (this._zoomPoint.x === 0) { 2217 ignoreGesture = true; 2218 } 2219 2220 const prevZoomLevel = currSlide.currZoomLevel; 2221 /** @type {number} */ 2222 2223 let destinationZoomLevel; 2224 let currZoomLevelNeedsChange = true; 2225 2226 if (prevZoomLevel < currSlide.zoomLevels.initial) { 2227 destinationZoomLevel = currSlide.zoomLevels.initial; // zoom to min 2228 } else if (prevZoomLevel > currSlide.zoomLevels.max) { 2229 destinationZoomLevel = currSlide.zoomLevels.max; // zoom to max 2230 } else { 2231 currZoomLevelNeedsChange = false; 2232 destinationZoomLevel = prevZoomLevel; 2233 } 2234 2235 const initialBgOpacity = pswp.bgOpacity; 2236 const restoreBgOpacity = pswp.bgOpacity < 1; 2237 const initialPan = equalizePoints({ 2238 x: 0, 2239 y: 0 2240 }, currSlide.pan); 2241 let destinationPan = equalizePoints({ 2242 x: 0, 2243 y: 0 2244 }, initialPan); 2245 2246 if (ignoreGesture) { 2247 this._zoomPoint.x = 0; 2248 this._zoomPoint.y = 0; 2249 this._startZoomPoint.x = 0; 2250 this._startZoomPoint.y = 0; 2251 this._startZoomLevel = prevZoomLevel; 2252 equalizePoints(this._startPan, initialPan); 2253 } 2254 2255 if (currZoomLevelNeedsChange) { 2256 destinationPan = { 2257 x: this._calculatePanForZoomLevel('x', destinationZoomLevel), 2258 y: this._calculatePanForZoomLevel('y', destinationZoomLevel) 2259 }; 2260 } // set zoom level, so pan bounds are updated according to it 2261 2262 2263 currSlide.setZoomLevel(destinationZoomLevel); 2264 destinationPan = { 2265 x: currSlide.bounds.correctPan('x', destinationPan.x), 2266 y: currSlide.bounds.correctPan('y', destinationPan.y) 2267 }; // return zoom level and its bounds to initial 2268 2269 currSlide.setZoomLevel(prevZoomLevel); 2270 const panNeedsChange = !pointsEqual(destinationPan, initialPan); 2271 2272 if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) { 2273 // update resolution after gesture 2274 currSlide._setResolution(destinationZoomLevel); 2275 2276 currSlide.applyCurrentZoomPan(); // nothing to animate 2277 2278 return; 2279 } 2280 2281 pswp.animations.stopAllPan(); 2282 pswp.animations.startSpring({ 2283 isPan: true, 2284 start: 0, 2285 end: 1000, 2286 velocity: 0, 2287 dampingRatio: 1, 2288 naturalFrequency: 40, 2289 onUpdate: now => { 2290 now /= 1000; // 0 - start, 1 - end 2291 2292 if (panNeedsChange || currZoomLevelNeedsChange) { 2293 if (panNeedsChange) { 2294 currSlide.pan.x = initialPan.x + (destinationPan.x - initialPan.x) * now; 2295 currSlide.pan.y = initialPan.y + (destinationPan.y - initialPan.y) * now; 2296 } 2297 2298 if (currZoomLevelNeedsChange) { 2299 const newZoomLevel = prevZoomLevel + (destinationZoomLevel - prevZoomLevel) * now; 2300 currSlide.setZoomLevel(newZoomLevel); 2301 } 2302 2303 currSlide.applyCurrentZoomPan(); 2304 } // Restore background opacity 2305 2306 2307 if (restoreBgOpacity && pswp.bgOpacity < 1) { 2308 // We clamp opacity to keep it between 0 and 1. 2309 // As progress ratio can be larger than 1 due to overshoot, 2310 // and we do not want to bounce opacity. 2311 pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * now, 0, 1)); 2312 } 2313 }, 2314 onComplete: () => { 2315 // update resolution after transition ends 2316 currSlide._setResolution(destinationZoomLevel); 2317 2318 currSlide.applyCurrentZoomPan(); 2319 } 2320 }); 2321 } 2322 2323} 2324 2325/** 2326 * @template {string} T 2327 * @template {string} P 2328 * @typedef {import('../types.js').AddPostfix<T, P>} AddPostfix<T, P> 2329 */ 2330 2331/** @typedef {import('./gestures.js').default} Gestures */ 2332 2333/** @typedef {import('../photoswipe.js').Point} Point */ 2334 2335/** @typedef {'imageClick' | 'bgClick' | 'tap' | 'doubleTap'} Actions */ 2336 2337/** 2338 * Whether the tap was performed on the main slide 2339 * (rather than controls or caption). 2340 * 2341 * @param {PointerEvent} event 2342 * @returns {boolean} 2343 */ 2344function didTapOnMainContent(event) { 2345 return !! 2346 /** @type {HTMLElement} */ 2347 event.target.closest('.pswp__container'); 2348} 2349/** 2350 * Tap, double-tap handler. 2351 */ 2352 2353 2354class TapHandler { 2355 /** 2356 * @param {Gestures} gestures 2357 */ 2358 constructor(gestures) { 2359 this.gestures = gestures; 2360 } 2361 /** 2362 * @param {Point} point 2363 * @param {PointerEvent} originalEvent 2364 */ 2365 2366 2367 click(point, originalEvent) { 2368 const targetClassList = 2369 /** @type {HTMLElement} */ 2370 originalEvent.target.classList; 2371 const isImageClick = targetClassList.contains('pswp__img'); 2372 const isBackgroundClick = targetClassList.contains('pswp__item') || targetClassList.contains('pswp__zoom-wrap'); 2373 2374 if (isImageClick) { 2375 this._doClickOrTapAction('imageClick', point, originalEvent); 2376 } else if (isBackgroundClick) { 2377 this._doClickOrTapAction('bgClick', point, originalEvent); 2378 } 2379 } 2380 /** 2381 * @param {Point} point 2382 * @param {PointerEvent} originalEvent 2383 */ 2384 2385 2386 tap(point, originalEvent) { 2387 if (didTapOnMainContent(originalEvent)) { 2388 this._doClickOrTapAction('tap', point, originalEvent); 2389 } 2390 } 2391 /** 2392 * @param {Point} point 2393 * @param {PointerEvent} originalEvent 2394 */ 2395 2396 2397 doubleTap(point, originalEvent) { 2398 if (didTapOnMainContent(originalEvent)) { 2399 this._doClickOrTapAction('doubleTap', point, originalEvent); 2400 } 2401 } 2402 /** 2403 * @private 2404 * @param {Actions} actionName 2405 * @param {Point} point 2406 * @param {PointerEvent} originalEvent 2407 */ 2408 2409 2410 _doClickOrTapAction(actionName, point, originalEvent) { 2411 var _this$gestures$pswp$e; 2412 2413 const { 2414 pswp 2415 } = this.gestures; 2416 const { 2417 currSlide 2418 } = pswp; 2419 const actionFullName = 2420 /** @type {AddPostfix<Actions, 'Action'>} */ 2421 actionName + 'Action'; 2422 const optionValue = pswp.options[actionFullName]; 2423 2424 if (pswp.dispatch(actionFullName, { 2425 point, 2426 originalEvent 2427 }).defaultPrevented) { 2428 return; 2429 } 2430 2431 if (typeof optionValue === 'function') { 2432 optionValue.call(pswp, point, originalEvent); 2433 return; 2434 } 2435 2436 switch (optionValue) { 2437 case 'close': 2438 case 'next': 2439 pswp[optionValue](); 2440 break; 2441 2442 case 'zoom': 2443 currSlide === null || currSlide === void 0 || currSlide.toggleZoom(point); 2444 break; 2445 2446 case 'zoom-or-close': 2447 // by default click zooms current image, 2448 // if it can not be zoomed - gallery will be closed 2449 if (currSlide !== null && currSlide !== void 0 && currSlide.isZoomable() && currSlide.zoomLevels.secondary !== currSlide.zoomLevels.initial) { 2450 currSlide.toggleZoom(point); 2451 } else if (pswp.options.clickToCloseNonZoomable) { 2452 pswp.close(); 2453 } 2454 2455 break; 2456 2457 case 'toggle-controls': 2458 (_this$gestures$pswp$e = this.gestures.pswp.element) === null || _this$gestures$pswp$e === void 0 || _this$gestures$pswp$e.classList.toggle('pswp--ui-visible'); // if (_controlsVisible) { 2459 // _ui.hideControls(); 2460 // } else { 2461 // _ui.showControls(); 2462 // } 2463 2464 break; 2465 } 2466 } 2467 2468} 2469 2470/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 2471 2472/** @typedef {import('../photoswipe.js').Point} Point */ 2473// How far should user should drag 2474// until we can determine that the gesture is swipe and its direction 2475 2476const AXIS_SWIPE_HYSTERISIS = 10; //const PAN_END_FRICTION = 0.35; 2477 2478const DOUBLE_TAP_DELAY = 300; // ms 2479 2480const MIN_TAP_DISTANCE = 25; // px 2481 2482/** 2483 * Gestures class bind touch, pointer or mouse events 2484 * and emits drag to drag-handler and zoom events zoom-handler. 2485 * 2486 * Drag and zoom events are emited in requestAnimationFrame, 2487 * and only when one of pointers was actually changed. 2488 */ 2489 2490class Gestures { 2491 /** 2492 * @param {PhotoSwipe} pswp 2493 */ 2494 constructor(pswp) { 2495 this.pswp = pswp; 2496 /** @type {'x' | 'y' | null} */ 2497 2498 this.dragAxis = null; // point objects are defined once and reused 2499 // PhotoSwipe keeps track only of two pointers, others are ignored 2500 2501 /** @type {Point} */ 2502 2503 this.p1 = { 2504 x: 0, 2505 y: 0 2506 }; // the first pressed pointer 2507 2508 /** @type {Point} */ 2509 2510 this.p2 = { 2511 x: 0, 2512 y: 0 2513 }; // the second pressed pointer 2514 2515 /** @type {Point} */ 2516 2517 this.prevP1 = { 2518 x: 0, 2519 y: 0 2520 }; 2521 /** @type {Point} */ 2522 2523 this.prevP2 = { 2524 x: 0, 2525 y: 0 2526 }; 2527 /** @type {Point} */ 2528 2529 this.startP1 = { 2530 x: 0, 2531 y: 0 2532 }; 2533 /** @type {Point} */ 2534 2535 this.startP2 = { 2536 x: 0, 2537 y: 0 2538 }; 2539 /** @type {Point} */ 2540 2541 this.velocity = { 2542 x: 0, 2543 y: 0 2544 }; 2545 /** @type {Point} 2546 * @private 2547 */ 2548 2549 this._lastStartP1 = { 2550 x: 0, 2551 y: 0 2552 }; 2553 /** @type {Point} 2554 * @private 2555 */ 2556 2557 this._intervalP1 = { 2558 x: 0, 2559 y: 0 2560 }; 2561 /** @private */ 2562 2563 this._numActivePoints = 0; 2564 /** @type {Point[]} 2565 * @private 2566 */ 2567 2568 this._ongoingPointers = []; 2569 /** @private */ 2570 2571 this._touchEventEnabled = 'ontouchstart' in window; 2572 /** @private */ 2573 2574 this._pointerEventEnabled = !!window.PointerEvent; 2575 this.supportsTouch = this._touchEventEnabled || this._pointerEventEnabled && navigator.maxTouchPoints > 1; 2576 /** @private */ 2577 2578 this._numActivePoints = 0; 2579 /** @private */ 2580 2581 this._intervalTime = 0; 2582 /** @private */ 2583 2584 this._velocityCalculated = false; 2585 this.isMultitouch = false; 2586 this.isDragging = false; 2587 this.isZooming = false; 2588 /** @type {number | null} */ 2589 2590 this.raf = null; 2591 /** @type {NodeJS.Timeout | null} 2592 * @private 2593 */ 2594 2595 this._tapTimer = null; 2596 2597 if (!this.supportsTouch) { 2598 // disable pan to next slide for non-touch devices 2599 pswp.options.allowPanToNext = false; 2600 } 2601 2602 this.drag = new DragHandler(this); 2603 this.zoomLevels = new ZoomHandler(this); 2604 this.tapHandler = new TapHandler(this); 2605 pswp.on('bindEvents', () => { 2606 pswp.events.add(pswp.scrollWrap, 'click', 2607 /** @type EventListener */ 2608 this._onClick.bind(this)); 2609 2610 if (this._pointerEventEnabled) { 2611 this._bindEvents('pointer', 'down', 'up', 'cancel'); 2612 } else if (this._touchEventEnabled) { 2613 this._bindEvents('touch', 'start', 'end', 'cancel'); // In previous versions we also bound mouse event here, 2614 // in case device supports both touch and mouse events, 2615 // but newer versions of browsers now support PointerEvent. 2616 // on iOS10 if you bind touchmove/end after touchstart, 2617 // and you don't preventDefault touchstart (which PhotoSwipe does), 2618 // preventDefault will have no effect on touchmove and touchend. 2619 // Unless you bind it previously. 2620 2621 2622 if (pswp.scrollWrap) { 2623 pswp.scrollWrap.ontouchmove = () => {}; 2624 2625 pswp.scrollWrap.ontouchend = () => {}; 2626 } 2627 } else { 2628 this._bindEvents('mouse', 'down', 'up'); 2629 } 2630 }); 2631 } 2632 /** 2633 * @private 2634 * @param {'mouse' | 'touch' | 'pointer'} pref 2635 * @param {'down' | 'start'} down 2636 * @param {'up' | 'end'} up 2637 * @param {'cancel'} [cancel] 2638 */ 2639 2640 2641 _bindEvents(pref, down, up, cancel) { 2642 const { 2643 pswp 2644 } = this; 2645 const { 2646 events 2647 } = pswp; 2648 const cancelEvent = cancel ? pref + cancel : ''; 2649 events.add(pswp.scrollWrap, pref + down, 2650 /** @type EventListener */ 2651 this.onPointerDown.bind(this)); 2652 events.add(window, pref + 'move', 2653 /** @type EventListener */ 2654 this.onPointerMove.bind(this)); 2655 events.add(window, pref + up, 2656 /** @type EventListener */ 2657 this.onPointerUp.bind(this)); 2658 2659 if (cancelEvent) { 2660 events.add(pswp.scrollWrap, cancelEvent, 2661 /** @type EventListener */ 2662 this.onPointerUp.bind(this)); 2663 } 2664 } 2665 /** 2666 * @param {PointerEvent} e 2667 */ 2668 2669 2670 onPointerDown(e) { 2671 // We do not call preventDefault for touch events 2672 // to allow browser to show native dialog on longpress 2673 // (the one that allows to save image or open it in new tab). 2674 // 2675 // Desktop Safari allows to drag images when preventDefault isn't called on mousedown, 2676 // even though preventDefault IS called on mousemove. That's why we preventDefault mousedown. 2677 const isMousePointer = e.type === 'mousedown' || e.pointerType === 'mouse'; // Allow dragging only via left mouse button. 2678 // http://www.quirksmode.org/js/events_properties.html 2679 // https://developer.mozilla.org/en-US/docs/Web/API/event.button 2680 2681 if (isMousePointer && e.button > 0) { 2682 return; 2683 } 2684 2685 const { 2686 pswp 2687 } = this; // if PhotoSwipe is opening or closing 2688 2689 if (!pswp.opener.isOpen) { 2690 e.preventDefault(); 2691 return; 2692 } 2693 2694 if (pswp.dispatch('pointerDown', { 2695 originalEvent: e 2696 }).defaultPrevented) { 2697 return; 2698 } 2699 2700 if (isMousePointer) { 2701 pswp.mouseDetected(); // preventDefault mouse event to prevent 2702 // browser image drag feature 2703 2704 this._preventPointerEventBehaviour(e, 'down'); 2705 } 2706 2707 pswp.animations.stopAll(); 2708 2709 this._updatePoints(e, 'down'); 2710 2711 if (this._numActivePoints === 1) { 2712 this.dragAxis = null; // we need to store initial point to determine the main axis, 2713 // drag is activated only after the axis is determined 2714 2715 equalizePoints(this.startP1, this.p1); 2716 } 2717 2718 if (this._numActivePoints > 1) { 2719 // Tap or double tap should not trigger if more than one pointer 2720 this._clearTapTimer(); 2721 2722 this.isMultitouch = true; 2723 } else { 2724 this.isMultitouch = false; 2725 } 2726 } 2727 /** 2728 * @param {PointerEvent} e 2729 */ 2730 2731 2732 onPointerMove(e) { 2733 this._preventPointerEventBehaviour(e, 'move'); 2734 2735 if (!this._numActivePoints) { 2736 return; 2737 } 2738 2739 this._updatePoints(e, 'move'); 2740 2741 if (this.pswp.dispatch('pointerMove', { 2742 originalEvent: e 2743 }).defaultPrevented) { 2744 return; 2745 } 2746 2747 if (this._numActivePoints === 1 && !this.isDragging) { 2748 if (!this.dragAxis) { 2749 this._calculateDragDirection(); 2750 } // Drag axis was detected, emit drag.start 2751 2752 2753 if (this.dragAxis && !this.isDragging) { 2754 if (this.isZooming) { 2755 this.isZooming = false; 2756 this.zoomLevels.end(); 2757 } 2758 2759 this.isDragging = true; 2760 2761 this._clearTapTimer(); // Tap can not trigger after drag 2762 // Adjust starting point 2763 2764 2765 this._updateStartPoints(); 2766 2767 this._intervalTime = Date.now(); //this._startTime = this._intervalTime; 2768 2769 this._velocityCalculated = false; 2770 equalizePoints(this._intervalP1, this.p1); 2771 this.velocity.x = 0; 2772 this.velocity.y = 0; 2773 this.drag.start(); 2774 2775 this._rafStopLoop(); 2776 2777 this._rafRenderLoop(); 2778 } 2779 } else if (this._numActivePoints > 1 && !this.isZooming) { 2780 this._finishDrag(); 2781 2782 this.isZooming = true; // Adjust starting points 2783 2784 this._updateStartPoints(); 2785 2786 this.zoomLevels.start(); 2787 2788 this._rafStopLoop(); 2789 2790 this._rafRenderLoop(); 2791 } 2792 } 2793 /** 2794 * @private 2795 */ 2796 2797 2798 _finishDrag() { 2799 if (this.isDragging) { 2800 this.isDragging = false; // Try to calculate velocity, 2801 // if it wasn't calculated yet in drag.change 2802 2803 if (!this._velocityCalculated) { 2804 this._updateVelocity(true); 2805 } 2806 2807 this.drag.end(); 2808 this.dragAxis = null; 2809 } 2810 } 2811 /** 2812 * @param {PointerEvent} e 2813 */ 2814 2815 2816 onPointerUp(e) { 2817 if (!this._numActivePoints) { 2818 return; 2819 } 2820 2821 this._updatePoints(e, 'up'); 2822 2823 if (this.pswp.dispatch('pointerUp', { 2824 originalEvent: e 2825 }).defaultPrevented) { 2826 return; 2827 } 2828 2829 if (this._numActivePoints === 0) { 2830 this._rafStopLoop(); 2831 2832 if (this.isDragging) { 2833 this._finishDrag(); 2834 } else if (!this.isZooming && !this.isMultitouch) { 2835 //this.zoomLevels.correctZoomPan(); 2836 this._finishTap(e); 2837 } 2838 } 2839 2840 if (this._numActivePoints < 2 && this.isZooming) { 2841 this.isZooming = false; 2842 this.zoomLevels.end(); 2843 2844 if (this._numActivePoints === 1) { 2845 // Since we have 1 point left, we need to reinitiate drag 2846 this.dragAxis = null; 2847 2848 this._updateStartPoints(); 2849 } 2850 } 2851 } 2852 /** 2853 * @private 2854 */ 2855 2856 2857 _rafRenderLoop() { 2858 if (this.isDragging || this.isZooming) { 2859 this._updateVelocity(); 2860 2861 if (this.isDragging) { 2862 // make sure that pointer moved since the last update 2863 if (!pointsEqual(this.p1, this.prevP1)) { 2864 this.drag.change(); 2865 } 2866 } else 2867 /* if (this.isZooming) */ 2868 { 2869 if (!pointsEqual(this.p1, this.prevP1) || !pointsEqual(this.p2, this.prevP2)) { 2870 this.zoomLevels.change(); 2871 } 2872 } 2873 2874 this._updatePrevPoints(); 2875 2876 this.raf = requestAnimationFrame(this._rafRenderLoop.bind(this)); 2877 } 2878 } 2879 /** 2880 * Update velocity at 50ms interval 2881 * 2882 * @private 2883 * @param {boolean} [force] 2884 */ 2885 2886 2887 _updateVelocity(force) { 2888 const time = Date.now(); 2889 const duration = time - this._intervalTime; 2890 2891 if (duration < 50 && !force) { 2892 return; 2893 } 2894 2895 this.velocity.x = this._getVelocity('x', duration); 2896 this.velocity.y = this._getVelocity('y', duration); 2897 this._intervalTime = time; 2898 equalizePoints(this._intervalP1, this.p1); 2899 this._velocityCalculated = true; 2900 } 2901 /** 2902 * @private 2903 * @param {PointerEvent} e 2904 */ 2905 2906 2907 _finishTap(e) { 2908 const { 2909 mainScroll 2910 } = this.pswp; // Do not trigger tap events if main scroll is shifted 2911 2912 if (mainScroll.isShifted()) { 2913 // restore main scroll position 2914 // (usually happens if stopped in the middle of animation) 2915 mainScroll.moveIndexBy(0, true); 2916 return; 2917 } // Do not trigger tap for touchcancel or pointercancel 2918 2919 2920 if (e.type.indexOf('cancel') > 0) { 2921 return; 2922 } // Trigger click instead of tap for mouse events 2923 2924 2925 if (e.type === 'mouseup' || e.pointerType === 'mouse') { 2926 this.tapHandler.click(this.startP1, e); 2927 return; 2928 } // Disable delay if there is no doubleTapAction 2929 2930 2931 const tapDelay = this.pswp.options.doubleTapAction ? DOUBLE_TAP_DELAY : 0; // If tapTimer is defined - we tapped recently, 2932 // check if the current tap is close to the previous one, 2933 // if yes - trigger double tap 2934 2935 if (this._tapTimer) { 2936 this._clearTapTimer(); // Check if two taps were more or less on the same place 2937 2938 2939 if (getDistanceBetween(this._lastStartP1, this.startP1) < MIN_TAP_DISTANCE) { 2940 this.tapHandler.doubleTap(this.startP1, e); 2941 } 2942 } else { 2943 equalizePoints(this._lastStartP1, this.startP1); 2944 this._tapTimer = setTimeout(() => { 2945 this.tapHandler.tap(this.startP1, e); 2946 2947 this._clearTapTimer(); 2948 }, tapDelay); 2949 } 2950 } 2951 /** 2952 * @private 2953 */ 2954 2955 2956 _clearTapTimer() { 2957 if (this._tapTimer) { 2958 clearTimeout(this._tapTimer); 2959 this._tapTimer = null; 2960 } 2961 } 2962 /** 2963 * Get velocity for axis 2964 * 2965 * @private 2966 * @param {'x' | 'y'} axis 2967 * @param {number} duration 2968 * @returns {number} 2969 */ 2970 2971 2972 _getVelocity(axis, duration) { 2973 // displacement is like distance, but can be negative. 2974 const displacement = this.p1[axis] - this._intervalP1[axis]; 2975 2976 if (Math.abs(displacement) > 1 && duration > 5) { 2977 return displacement / duration; 2978 } 2979 2980 return 0; 2981 } 2982 /** 2983 * @private 2984 */ 2985 2986 2987 _rafStopLoop() { 2988 if (this.raf) { 2989 cancelAnimationFrame(this.raf); 2990 this.raf = null; 2991 } 2992 } 2993 /** 2994 * @private 2995 * @param {PointerEvent} e 2996 * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type 2997 */ 2998 2999 3000 _preventPointerEventBehaviour(e, pointerType) { 3001 const preventPointerEvent = this.pswp.applyFilters('preventPointerEvent', true, e, pointerType); 3002 3003 if (preventPointerEvent) { 3004 e.preventDefault(); 3005 } 3006 } 3007 /** 3008 * Parses and normalizes points from the touch, mouse or pointer event. 3009 * Updates p1 and p2. 3010 * 3011 * @private 3012 * @param {PointerEvent | TouchEvent} e 3013 * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type 3014 */ 3015 3016 3017 _updatePoints(e, pointerType) { 3018 if (this._pointerEventEnabled) { 3019 const pointerEvent = 3020 /** @type {PointerEvent} */ 3021 e; // Try to find the current pointer in ongoing pointers by its ID 3022 3023 const pointerIndex = this._ongoingPointers.findIndex(ongoingPointer => { 3024 return ongoingPointer.id === pointerEvent.pointerId; 3025 }); 3026 3027 if (pointerType === 'up' && pointerIndex > -1) { 3028 // release the pointer - remove it from ongoing 3029 this._ongoingPointers.splice(pointerIndex, 1); 3030 } else if (pointerType === 'down' && pointerIndex === -1) { 3031 // add new pointer 3032 this._ongoingPointers.push(this._convertEventPosToPoint(pointerEvent, { 3033 x: 0, 3034 y: 0 3035 })); 3036 } else if (pointerIndex > -1) { 3037 // update existing pointer 3038 this._convertEventPosToPoint(pointerEvent, this._ongoingPointers[pointerIndex]); 3039 } 3040 3041 this._numActivePoints = this._ongoingPointers.length; // update points that PhotoSwipe uses 3042 // to calculate position and scale 3043 3044 if (this._numActivePoints > 0) { 3045 equalizePoints(this.p1, this._ongoingPointers[0]); 3046 } 3047 3048 if (this._numActivePoints > 1) { 3049 equalizePoints(this.p2, this._ongoingPointers[1]); 3050 } 3051 } else { 3052 const touchEvent = 3053 /** @type {TouchEvent} */ 3054 e; 3055 this._numActivePoints = 0; 3056 3057 if (touchEvent.type.indexOf('touch') > -1) { 3058 // Touch Event 3059 // https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent 3060 if (touchEvent.touches && touchEvent.touches.length > 0) { 3061 this._convertEventPosToPoint(touchEvent.touches[0], this.p1); 3062 3063 this._numActivePoints++; 3064 3065 if (touchEvent.touches.length > 1) { 3066 this._convertEventPosToPoint(touchEvent.touches[1], this.p2); 3067 3068 this._numActivePoints++; 3069 } 3070 } 3071 } else { 3072 // Mouse Event 3073 this._convertEventPosToPoint( 3074 /** @type {PointerEvent} */ 3075 e, this.p1); 3076 3077 if (pointerType === 'up') { 3078 // clear all points on mouseup 3079 this._numActivePoints = 0; 3080 } else { 3081 this._numActivePoints++; 3082 } 3083 } 3084 } 3085 } 3086 /** update points that were used during previous rAF tick 3087 * @private 3088 */ 3089 3090 3091 _updatePrevPoints() { 3092 equalizePoints(this.prevP1, this.p1); 3093 equalizePoints(this.prevP2, this.p2); 3094 } 3095 /** update points at the start of gesture 3096 * @private 3097 */ 3098 3099 3100 _updateStartPoints() { 3101 equalizePoints(this.startP1, this.p1); 3102 equalizePoints(this.startP2, this.p2); 3103 3104 this._updatePrevPoints(); 3105 } 3106 /** @private */ 3107 3108 3109 _calculateDragDirection() { 3110 if (this.pswp.mainScroll.isShifted()) { 3111 // if main scroll position is shifted â direction is always horizontal 3112 this.dragAxis = 'x'; 3113 } else { 3114 // calculate delta of the last touchmove tick 3115 const diff = Math.abs(this.p1.x - this.startP1.x) - Math.abs(this.p1.y - this.startP1.y); 3116 3117 if (diff !== 0) { 3118 // check if pointer was shifted horizontally or vertically 3119 const axisToCheck = diff > 0 ? 'x' : 'y'; 3120 3121 if (Math.abs(this.p1[axisToCheck] - this.startP1[axisToCheck]) >= AXIS_SWIPE_HYSTERISIS) { 3122 this.dragAxis = axisToCheck; 3123 } 3124 } 3125 } 3126 } 3127 /** 3128 * Converts touch, pointer or mouse event 3129 * to PhotoSwipe point. 3130 * 3131 * @private 3132 * @param {Touch | PointerEvent} e 3133 * @param {Point} p 3134 * @returns {Point} 3135 */ 3136 3137 3138 _convertEventPosToPoint(e, p) { 3139 p.x = e.pageX - this.pswp.offset.x; 3140 p.y = e.pageY - this.pswp.offset.y; 3141 3142 if ('pointerId' in e) { 3143 p.id = e.pointerId; 3144 } else if (e.identifier !== undefined) { 3145 p.id = e.identifier; 3146 } 3147 3148 return p; 3149 } 3150 /** 3151 * @private 3152 * @param {PointerEvent} e 3153 */ 3154 3155 3156 _onClick(e) { 3157 // Do not allow click event to pass through after drag 3158 if (this.pswp.mainScroll.isShifted()) { 3159 e.preventDefault(); 3160 e.stopPropagation(); 3161 } 3162 } 3163 3164} 3165 3166/** @typedef {import('./photoswipe.js').default} PhotoSwipe */ 3167 3168/** @typedef {import('./slide/slide.js').default} Slide */ 3169 3170/** @typedef {{ el: HTMLDivElement; slide?: Slide }} ItemHolder */ 3171 3172const MAIN_SCROLL_END_FRICTION = 0.35; // const MIN_SWIPE_TRANSITION_DURATION = 250; 3173// const MAX_SWIPE_TRABSITION_DURATION = 500; 3174// const DEFAULT_SWIPE_TRANSITION_DURATION = 333; 3175 3176/** 3177 * Handles movement of the main scrolling container 3178 * (for example, it repositions when user swipes left or right). 3179 * 3180 * Also stores its state. 3181 */ 3182 3183class MainScroll { 3184 /** 3185 * @param {PhotoSwipe} pswp 3186 */ 3187 constructor(pswp) { 3188 this.pswp = pswp; 3189 this.x = 0; 3190 this.slideWidth = 0; 3191 /** @private */ 3192 3193 this._currPositionIndex = 0; 3194 /** @private */ 3195 3196 this._prevPositionIndex = 0; 3197 /** @private */ 3198 3199 this._containerShiftIndex = -1; 3200 /** @type {ItemHolder[]} */ 3201 3202 this.itemHolders = []; 3203 } 3204 /** 3205 * Position the scroller and slide containers 3206 * according to viewport size. 3207 * 3208 * @param {boolean} [resizeSlides] Whether slides content should resized 3209 */ 3210 3211 3212 resize(resizeSlides) { 3213 const { 3214 pswp 3215 } = this; 3216 const newSlideWidth = Math.round(pswp.viewportSize.x + pswp.viewportSize.x * pswp.options.spacing); // Mobile browsers might trigger a resize event during a gesture. 3217 // (due to toolbar appearing or hiding). 3218 // Avoid re-adjusting main scroll position if width wasn't changed 3219 3220 const slideWidthChanged = newSlideWidth !== this.slideWidth; 3221 3222 if (slideWidthChanged) { 3223 this.slideWidth = newSlideWidth; 3224 this.moveTo(this.getCurrSlideX()); 3225 } 3226 3227 this.itemHolders.forEach((itemHolder, index) => { 3228 if (slideWidthChanged) { 3229 setTransform(itemHolder.el, (index + this._containerShiftIndex) * this.slideWidth); 3230 } 3231 3232 if (resizeSlides && itemHolder.slide) { 3233 itemHolder.slide.resize(); 3234 } 3235 }); 3236 } 3237 /** 3238 * Reset X position of the main scroller to zero 3239 */ 3240 3241 3242 resetPosition() { 3243 // Position on the main scroller (offset) 3244 // it is independent from slide index 3245 this._currPositionIndex = 0; 3246 this._prevPositionIndex = 0; // This will force recalculation of size on next resize() 3247 3248 this.slideWidth = 0; // _containerShiftIndex*viewportSize will give you amount of transform of the current slide 3249 3250 this._containerShiftIndex = -1; 3251 } 3252 /** 3253 * Create and append array of three items 3254 * that hold data about slides in DOM 3255 */ 3256 3257 3258 appendHolders() { 3259 this.itemHolders = []; // append our three slide holders - 3260 // previous, current, and next 3261 3262 for (let i = 0; i < 3; i++) { 3263 const el = createElement('pswp__item', 'div', this.pswp.container); 3264 el.setAttribute('role', 'group'); 3265 el.setAttribute('aria-roledescription', 'slide'); 3266 el.setAttribute('aria-hidden', 'true'); // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints) 3267 3268 el.style.display = i === 1 ? 'block' : 'none'; 3269 this.itemHolders.push({ 3270 el //index: -1 3271 3272 }); 3273 } 3274 } 3275 /** 3276 * Whether the main scroll can be horizontally swiped to the next or previous slide. 3277 * @returns {boolean} 3278 */ 3279 3280 3281 canBeSwiped() { 3282 return this.pswp.getNumItems() > 1; 3283 } 3284 /** 3285 * Move main scroll by X amount of slides. 3286 * For example: 3287 * `-1` will move to the previous slide, 3288 * `0` will reset the scroll position of the current slide, 3289 * `3` will move three slides forward 3290 * 3291 * If loop option is enabled - index will be automatically looped too, 3292 * (for example `-1` will move to the last slide of the gallery). 3293 * 3294 * @param {number} diff 3295 * @param {boolean} [animate] 3296 * @param {number} [velocityX] 3297 * @returns {boolean} whether index was changed or not 3298 */ 3299 3300 3301 moveIndexBy(diff, animate, velocityX) { 3302 const { 3303 pswp 3304 } = this; 3305 let newIndex = pswp.potentialIndex + diff; 3306 const numSlides = pswp.getNumItems(); 3307 3308 if (pswp.canLoop()) { 3309 newIndex = pswp.getLoopedIndex(newIndex); 3310 const distance = (diff + numSlides) % numSlides; 3311 3312 if (distance <= numSlides / 2) { 3313 // go forward 3314 diff = distance; 3315 } else { 3316 // go backwards 3317 diff = distance - numSlides; 3318 } 3319 } else { 3320 if (newIndex < 0) { 3321 newIndex = 0; 3322 } else if (newIndex >= numSlides) { 3323 newIndex = numSlides - 1; 3324 } 3325 3326 diff = newIndex - pswp.potentialIndex; 3327 } 3328 3329 pswp.potentialIndex = newIndex; 3330 this._currPositionIndex -= diff; 3331 pswp.animations.stopMainScroll(); 3332 const destinationX = this.getCurrSlideX(); 3333 3334 if (!animate) { 3335 this.moveTo(destinationX); 3336 this.updateCurrItem(); 3337 } else { 3338 pswp.animations.startSpring({ 3339 isMainScroll: true, 3340 start: this.x, 3341 end: destinationX, 3342 velocity: velocityX || 0, 3343 naturalFrequency: 30, 3344 dampingRatio: 1, 3345 //0.7, 3346 onUpdate: x => { 3347 this.moveTo(x); 3348 }, 3349 onComplete: () => { 3350 this.updateCurrItem(); 3351 pswp.appendHeavy(); 3352 } 3353 }); 3354 let currDiff = pswp.potentialIndex - pswp.currIndex; 3355 3356 if (pswp.canLoop()) { 3357 const currDistance = (currDiff + numSlides) % numSlides; 3358 3359 if (currDistance <= numSlides / 2) { 3360 // go forward 3361 currDiff = currDistance; 3362 } else { 3363 // go backwards 3364 currDiff = currDistance - numSlides; 3365 } 3366 } // Force-append new slides during transition 3367 // if difference between slides is more than 1 3368 3369 3370 if (Math.abs(currDiff) > 1) { 3371 this.updateCurrItem(); 3372 } 3373 } 3374 3375 return Boolean(diff); 3376 } 3377 /** 3378 * X position of the main scroll for the current slide 3379 * (ignores position during dragging) 3380 * @returns {number} 3381 */ 3382 3383 3384 getCurrSlideX() { 3385 return this.slideWidth * this._currPositionIndex; 3386 } 3387 /** 3388 * Whether scroll position is shifted. 3389 * For example, it will return true if the scroll is being dragged or animated. 3390 * @returns {boolean} 3391 */ 3392 3393 3394 isShifted() { 3395 return this.x !== this.getCurrSlideX(); 3396 } 3397 /** 3398 * Update slides X positions and set their content 3399 */ 3400 3401 3402 updateCurrItem() { 3403 var _this$itemHolders$; 3404 3405 const { 3406 pswp 3407 } = this; 3408 const positionDifference = this._prevPositionIndex - this._currPositionIndex; 3409 3410 if (!positionDifference) { 3411 return; 3412 } 3413 3414 this._prevPositionIndex = this._currPositionIndex; 3415 pswp.currIndex = pswp.potentialIndex; 3416 let diffAbs = Math.abs(positionDifference); 3417 /** @type {ItemHolder | undefined} */ 3418 3419 let tempHolder; 3420 3421 if (diffAbs >= 3) { 3422 this._containerShiftIndex += positionDifference + (positionDifference > 0 ? -3 : 3); 3423 diffAbs = 3; // If slides are changed by 3 screens or more - clean up previous slides 3424 3425 this.itemHolders.forEach(itemHolder => { 3426 var _itemHolder$slide; 3427 3428 (_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.destroy(); 3429 itemHolder.slide = undefined; 3430 }); 3431 } 3432 3433 for (let i = 0; i < diffAbs; i++) { 3434 if (positionDifference > 0) { 3435 tempHolder = this.itemHolders.shift(); 3436 3437 if (tempHolder) { 3438 this.itemHolders[2] = tempHolder; // move first to last 3439 3440 this._containerShiftIndex++; 3441 setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth); 3442 pswp.setContent(tempHolder, pswp.currIndex - diffAbs + i + 2); 3443 } 3444 } else { 3445 tempHolder = this.itemHolders.pop(); 3446 3447 if (tempHolder) { 3448 this.itemHolders.unshift(tempHolder); // move last to first 3449 3450 this._containerShiftIndex--; 3451 setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth); 3452 pswp.setContent(tempHolder, pswp.currIndex + diffAbs - i - 2); 3453 } 3454 } 3455 } // Reset transfrom every 50ish navigations in one direction. 3456 // 3457 // Otherwise transform will keep growing indefinitely, 3458 // which might cause issues as browsers have a maximum transform limit. 3459 // I wasn't able to reach it, but just to be safe. 3460 // This should not cause noticable lag. 3461 3462 3463 if (Math.abs(this._containerShiftIndex) > 50 && !this.isShifted()) { 3464 this.resetPosition(); 3465 this.resize(); 3466 } // Pan transition might be running (and consntantly updating pan position) 3467 3468 3469 pswp.animations.stopAllPan(); 3470 this.itemHolders.forEach((itemHolder, i) => { 3471 if (itemHolder.slide) { 3472 // Slide in the 2nd holder is always active 3473 itemHolder.slide.setIsActive(i === 1); 3474 } 3475 }); 3476 pswp.currSlide = (_this$itemHolders$ = this.itemHolders[1]) === null || _this$itemHolders$ === void 0 ? void 0 : _this$itemHolders$.slide; 3477 pswp.contentLoader.updateLazy(positionDifference); 3478 3479 if (pswp.currSlide) { 3480 pswp.currSlide.applyCurrentZoomPan(); 3481 } 3482 3483 pswp.dispatch('change'); 3484 } 3485 /** 3486 * Move the X position of the main scroll container 3487 * 3488 * @param {number} x 3489 * @param {boolean} [dragging] 3490 */ 3491 3492 3493 moveTo(x, dragging) { 3494 if (!this.pswp.canLoop() && dragging) { 3495 // Apply friction 3496 let newSlideIndexOffset = (this.slideWidth * this._currPositionIndex - x) / this.slideWidth; 3497 newSlideIndexOffset += this.pswp.currIndex; 3498 const delta = Math.round(x - this.x); 3499 3500 if (newSlideIndexOffset < 0 && delta > 0 || newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0) { 3501 x = this.x + delta * MAIN_SCROLL_END_FRICTION; 3502 } 3503 } 3504 3505 this.x = x; 3506 3507 if (this.pswp.container) { 3508 setTransform(this.pswp.container, x); 3509 } 3510 3511 this.pswp.dispatch('moveMainScroll', { 3512 x, 3513 dragging: dragging !== null && dragging !== void 0 ? dragging : false 3514 }); 3515 } 3516 3517} 3518 3519/** @typedef {import('./photoswipe.js').default} PhotoSwipe */ 3520 3521/** 3522 * @template T 3523 * @typedef {import('./types.js').Methods<T>} Methods<T> 3524 */ 3525 3526const KeyboardKeyCodesMap = { 3527 Escape: 27, 3528 z: 90, 3529 ArrowLeft: 37, 3530 ArrowUp: 38, 3531 ArrowRight: 39, 3532 ArrowDown: 40, 3533 Tab: 9 3534}; 3535/** 3536 * @template {keyof KeyboardKeyCodesMap} T 3537 * @param {T} key 3538 * @param {boolean} isKeySupported 3539 * @returns {T | number | undefined} 3540 */ 3541 3542const getKeyboardEventKey = (key, isKeySupported) => { 3543 return isKeySupported ? key : KeyboardKeyCodesMap[key]; 3544}; 3545/** 3546 * - Manages keyboard shortcuts. 3547 * - Helps trap focus within photoswipe. 3548 */ 3549 3550 3551class Keyboard { 3552 /** 3553 * @param {PhotoSwipe} pswp 3554 */ 3555 constructor(pswp) { 3556 this.pswp = pswp; 3557 /** @private */ 3558 3559 this._wasFocused = false; 3560 pswp.on('bindEvents', () => { 3561 if (pswp.options.trapFocus) { 3562 // Dialog was likely opened by keyboard if initial point is not defined 3563 if (!pswp.options.initialPointerPos) { 3564 // focus causes layout, 3565 // which causes lag during the animation, 3566 // that's why we delay it until the opener transition ends 3567 this._focusRoot(); 3568 } 3569 3570 pswp.events.add(document, 'focusin', 3571 /** @type EventListener */ 3572 this._onFocusIn.bind(this)); 3573 } 3574 3575 pswp.events.add(document, 'keydown', 3576 /** @type EventListener */ 3577 this._onKeyDown.bind(this)); 3578 }); 3579 const lastActiveElement = 3580 /** @type {HTMLElement} */ 3581 document.activeElement; 3582 pswp.on('destroy', () => { 3583 if (pswp.options.returnFocus && lastActiveElement && this._wasFocused) { 3584 lastActiveElement.focus(); 3585 } 3586 }); 3587 } 3588 /** @private */ 3589 3590 3591 _focusRoot() { 3592 if (!this._wasFocused && this.pswp.element) { 3593 this.pswp.element.focus(); 3594 this._wasFocused = true; 3595 } 3596 } 3597 /** 3598 * @private 3599 * @param {KeyboardEvent} e 3600 */ 3601 3602 3603 _onKeyDown(e) { 3604 const { 3605 pswp 3606 } = this; 3607 3608 if (pswp.dispatch('keydown', { 3609 originalEvent: e 3610 }).defaultPrevented) { 3611 return; 3612 } 3613 3614 if (specialKeyUsed(e)) { 3615 // don't do anything if special key pressed 3616 // to prevent from overriding default browser actions 3617 // for example, in Chrome on Mac cmd+arrow-left returns to previous page 3618 return; 3619 } 3620 /** @type {Methods<PhotoSwipe> | undefined} */ 3621 3622 3623 let keydownAction; 3624 /** @type {'x' | 'y' | undefined} */ 3625 3626 let axis; 3627 let isForward = false; 3628 const isKeySupported = ('key' in e); 3629 3630 switch (isKeySupported ? e.key : e.keyCode) { 3631 case getKeyboardEventKey('Escape', isKeySupported): 3632 if (pswp.options.escKey) { 3633 keydownAction = 'close'; 3634 } 3635 3636 break; 3637 3638 case getKeyboardEventKey('z', isKeySupported): 3639 keydownAction = 'toggleZoom'; 3640 break; 3641 3642 case getKeyboardEventKey('ArrowLeft', isKeySupported): 3643 axis = 'x'; 3644 break; 3645 3646 case getKeyboardEventKey('ArrowUp', isKeySupported): 3647 axis = 'y'; 3648 break; 3649 3650 case getKeyboardEventKey('ArrowRight', isKeySupported): 3651 axis = 'x'; 3652 isForward = true; 3653 break; 3654 3655 case getKeyboardEventKey('ArrowDown', isKeySupported): 3656 isForward = true; 3657 axis = 'y'; 3658 break; 3659 3660 case getKeyboardEventKey('Tab', isKeySupported): 3661 this._focusRoot(); 3662 3663 break; 3664 } // if left/right/top/bottom key 3665 3666 3667 if (axis) { 3668 // prevent page scroll 3669 e.preventDefault(); 3670 const { 3671 currSlide 3672 } = pswp; 3673 3674 if (pswp.options.arrowKeys && axis === 'x' && pswp.getNumItems() > 1) { 3675 keydownAction = isForward ? 'next' : 'prev'; 3676 } else if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.fit) { 3677 // up/down arrow keys pan the image vertically 3678 // left/right arrow keys pan horizontally. 3679 // Unless there is only one image, 3680 // or arrowKeys option is disabled 3681 currSlide.pan[axis] += isForward ? -80 : 80; 3682 currSlide.panTo(currSlide.pan.x, currSlide.pan.y); 3683 } 3684 } 3685 3686 if (keydownAction) { 3687 e.preventDefault(); // @ts-ignore 3688 3689 pswp[keydownAction](); 3690 } 3691 } 3692 /** 3693 * Trap focus inside photoswipe 3694 * 3695 * @private 3696 * @param {FocusEvent} e 3697 */ 3698 3699 3700 _onFocusIn(e) { 3701 const { 3702 template 3703 } = this.pswp; 3704 3705 if (template && document !== e.target && template !== e.target && !template.contains( 3706 /** @type {Node} */ 3707 e.target)) { 3708 // focus root element 3709 template.focus(); 3710 } 3711 } 3712 3713} 3714 3715const DEFAULT_EASING = 'cubic-bezier(.4,0,.22,1)'; 3716/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */ 3717 3718/** @typedef {Object} DefaultCssAnimationProps 3719 * 3720 * @prop {HTMLElement} target 3721 * @prop {number} [duration] 3722 * @prop {string} [easing] 3723 * @prop {string} [transform] 3724 * @prop {string} [opacity] 3725 * */ 3726 3727/** @typedef {SharedAnimationProps & DefaultCssAnimationProps} CssAnimationProps */ 3728 3729/** 3730 * Runs CSS transition. 3731 */ 3732 3733class CSSAnimation { 3734 /** 3735 * onComplete can be unpredictable, be careful about current state 3736 * 3737 * @param {CssAnimationProps} props 3738 */ 3739 constructor(props) { 3740 var _props$prop; 3741 3742 this.props = props; 3743 const { 3744 target, 3745 onComplete, 3746 transform, 3747 onFinish = () => {}, 3748 duration = 333, 3749 easing = DEFAULT_EASING 3750 } = props; 3751 this.onFinish = onFinish; // support only transform and opacity 3752 3753 const prop = transform ? 'transform' : 'opacity'; 3754 const propValue = (_props$prop = props[prop]) !== null && _props$prop !== void 0 ? _props$prop : ''; 3755 /** @private */ 3756 3757 this._target = target; 3758 /** @private */ 3759 3760 this._onComplete = onComplete; 3761 /** @private */ 3762 3763 this._finished = false; 3764 /** @private */ 3765 3766 this._onTransitionEnd = this._onTransitionEnd.bind(this); // Using timeout hack to make sure that animation 3767 // starts even if the animated property was changed recently, 3768 // otherwise transitionend might not fire or transition won't start. 3769 // https://drafts.csswg.org/css-transitions/#starting 3770 // 3771 // ¯\_(ã)_/¯ 3772 3773 /** @private */ 3774 3775 this._helperTimeout = setTimeout(() => { 3776 setTransitionStyle(target, prop, duration, easing); 3777 this._helperTimeout = setTimeout(() => { 3778 target.addEventListener('transitionend', this._onTransitionEnd, false); 3779 target.addEventListener('transitioncancel', this._onTransitionEnd, false); // Safari occasionally does not emit transitionend event 3780 // if element property was modified during the transition, 3781 // which may be caused by resize or third party component, 3782 // using timeout as a safety fallback 3783 3784 this._helperTimeout = setTimeout(() => { 3785 this._finalizeAnimation(); 3786 }, duration + 500); 3787 target.style[prop] = propValue; 3788 }, 30); // Do not reduce this number 3789 }, 0); 3790 } 3791 /** 3792 * @private 3793 * @param {TransitionEvent} e 3794 */ 3795 3796 3797 _onTransitionEnd(e) { 3798 if (e.target === this._target) { 3799 this._finalizeAnimation(); 3800 } 3801 } 3802 /** 3803 * @private 3804 */ 3805 3806 3807 _finalizeAnimation() { 3808 if (!this._finished) { 3809 this._finished = true; 3810 this.onFinish(); 3811 3812 if (this._onComplete) { 3813 this._onComplete(); 3814 } 3815 } 3816 } // Destroy is called automatically onFinish 3817 3818 3819 destroy() { 3820 if (this._helperTimeout) { 3821 clearTimeout(this._helperTimeout); 3822 } 3823 3824 removeTransitionStyle(this._target); 3825 3826 this._target.removeEventListener('transitionend', this._onTransitionEnd, false); 3827 3828 this._target.removeEventListener('transitioncancel', this._onTransitionEnd, false); 3829 3830 if (!this._finished) { 3831 this._finalizeAnimation(); 3832 } 3833 } 3834 3835} 3836 3837const DEFAULT_NATURAL_FREQUENCY = 12; 3838const DEFAULT_DAMPING_RATIO = 0.75; 3839/** 3840 * Spring easing helper 3841 */ 3842 3843class SpringEaser { 3844 /** 3845 * @param {number} initialVelocity Initial velocity, px per ms. 3846 * 3847 * @param {number} [dampingRatio] 3848 * Determines how bouncy animation will be. 3849 * From 0 to 1, 0 - always overshoot, 1 - do not overshoot. 3850 * "overshoot" refers to part of animation that 3851 * goes beyond the final value. 3852 * 3853 * @param {number} [naturalFrequency] 3854 * Determines how fast animation will slow down. 3855 * The higher value - the stiffer the transition will be, 3856 * and the faster it will slow down. 3857 * Recommended value from 10 to 50 3858 */ 3859 constructor(initialVelocity, dampingRatio, naturalFrequency) { 3860 this.velocity = initialVelocity * 1000; // convert to "pixels per second" 3861 // https://en.wikipedia.org/wiki/Damping_ratio 3862 3863 this._dampingRatio = dampingRatio || DEFAULT_DAMPING_RATIO; // https://en.wikipedia.org/wiki/Natural_frequency 3864 3865 this._naturalFrequency = naturalFrequency || DEFAULT_NATURAL_FREQUENCY; 3866 this._dampedFrequency = this._naturalFrequency; 3867 3868 if (this._dampingRatio < 1) { 3869 this._dampedFrequency *= Math.sqrt(1 - this._dampingRatio * this._dampingRatio); 3870 } 3871 } 3872 /** 3873 * @param {number} deltaPosition Difference between current and end position of the animation 3874 * @param {number} deltaTime Frame duration in milliseconds 3875 * 3876 * @returns {number} Displacement, relative to the end position. 3877 */ 3878 3879 3880 easeFrame(deltaPosition, deltaTime) { 3881 // Inspired by Apple Webkit and Android spring function implementation 3882 // https://en.wikipedia.org/wiki/Oscillation 3883 // https://en.wikipedia.org/wiki/Damping_ratio 3884 // we ignore mass (assume that it's 1kg) 3885 let displacement = 0; 3886 let coeff; 3887 deltaTime /= 1000; 3888 const naturalDumpingPow = Math.E ** (-this._dampingRatio * this._naturalFrequency * deltaTime); 3889 3890 if (this._dampingRatio === 1) { 3891 coeff = this.velocity + this._naturalFrequency * deltaPosition; 3892 displacement = (deltaPosition + coeff * deltaTime) * naturalDumpingPow; 3893 this.velocity = displacement * -this._naturalFrequency + coeff * naturalDumpingPow; 3894 } else if (this._dampingRatio < 1) { 3895 coeff = 1 / this._dampedFrequency * (this._dampingRatio * this._naturalFrequency * deltaPosition + this.velocity); 3896 const dumpedFCos = Math.cos(this._dampedFrequency * deltaTime); 3897 const dumpedFSin = Math.sin(this._dampedFrequency * deltaTime); 3898 displacement = naturalDumpingPow * (deltaPosition * dumpedFCos + coeff * dumpedFSin); 3899 this.velocity = displacement * -this._naturalFrequency * this._dampingRatio + naturalDumpingPow * (-this._dampedFrequency * deltaPosition * dumpedFSin + this._dampedFrequency * coeff * dumpedFCos); 3900 } // Overdamped (>1) damping ratio is not supported 3901 3902 3903 return displacement; 3904 } 3905 3906} 3907 3908/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */ 3909 3910/** 3911 * @typedef {Object} DefaultSpringAnimationProps 3912 * 3913 * @prop {number} start 3914 * @prop {number} end 3915 * @prop {number} velocity 3916 * @prop {number} [dampingRatio] 3917 * @prop {number} [naturalFrequency] 3918 * @prop {(end: number) => void} onUpdate 3919 */ 3920 3921/** @typedef {SharedAnimationProps & DefaultSpringAnimationProps} SpringAnimationProps */ 3922 3923class SpringAnimation { 3924 /** 3925 * @param {SpringAnimationProps} props 3926 */ 3927 constructor(props) { 3928 this.props = props; 3929 this._raf = 0; 3930 const { 3931 start, 3932 end, 3933 velocity, 3934 onUpdate, 3935 onComplete, 3936 onFinish = () => {}, 3937 dampingRatio, 3938 naturalFrequency 3939 } = props; 3940 this.onFinish = onFinish; 3941 const easer = new SpringEaser(velocity, dampingRatio, naturalFrequency); 3942 let prevTime = Date.now(); 3943 let deltaPosition = start - end; 3944 3945 const animationLoop = () => { 3946 if (this._raf) { 3947 deltaPosition = easer.easeFrame(deltaPosition, Date.now() - prevTime); // Stop the animation if velocity is low and position is close to end 3948 3949 if (Math.abs(deltaPosition) < 1 && Math.abs(easer.velocity) < 50) { 3950 // Finalize the animation 3951 onUpdate(end); 3952 3953 if (onComplete) { 3954 onComplete(); 3955 } 3956 3957 this.onFinish(); 3958 } else { 3959 prevTime = Date.now(); 3960 onUpdate(deltaPosition + end); 3961 this._raf = requestAnimationFrame(animationLoop); 3962 } 3963 } 3964 }; 3965 3966 this._raf = requestAnimationFrame(animationLoop); 3967 } // Destroy is called automatically onFinish 3968 3969 3970 destroy() { 3971 if (this._raf >= 0) { 3972 cancelAnimationFrame(this._raf); 3973 } 3974 3975 this._raf = 0; 3976 } 3977 3978} 3979 3980/** @typedef {import('./css-animation.js').CssAnimationProps} CssAnimationProps */ 3981 3982/** @typedef {import('./spring-animation.js').SpringAnimationProps} SpringAnimationProps */ 3983 3984/** @typedef {Object} SharedAnimationProps 3985 * @prop {string} [name] 3986 * @prop {boolean} [isPan] 3987 * @prop {boolean} [isMainScroll] 3988 * @prop {VoidFunction} [onComplete] 3989 * @prop {VoidFunction} [onFinish] 3990 */ 3991 3992/** @typedef {SpringAnimation | CSSAnimation} Animation */ 3993 3994/** @typedef {SpringAnimationProps | CssAnimationProps} AnimationProps */ 3995 3996/** 3997 * Manages animations 3998 */ 3999 4000class Animations { 4001 constructor() { 4002 /** @type {Animation[]} */ 4003 this.activeAnimations = []; 4004 } 4005 /** 4006 * @param {SpringAnimationProps} props 4007 */ 4008 4009 4010 startSpring(props) { 4011 this._start(props, true); 4012 } 4013 /** 4014 * @param {CssAnimationProps} props 4015 */ 4016 4017 4018 startTransition(props) { 4019 this._start(props); 4020 } 4021 /** 4022 * @private 4023 * @param {AnimationProps} props 4024 * @param {boolean} [isSpring] 4025 * @returns {Animation} 4026 */ 4027 4028 4029 _start(props, isSpring) { 4030 const animation = isSpring ? new SpringAnimation( 4031 /** @type SpringAnimationProps */ 4032 props) : new CSSAnimation( 4033 /** @type CssAnimationProps */ 4034 props); 4035 this.activeAnimations.push(animation); 4036 4037 animation.onFinish = () => this.stop(animation); 4038 4039 return animation; 4040 } 4041 /** 4042 * @param {Animation} animation 4043 */ 4044 4045 4046 stop(animation) { 4047 animation.destroy(); 4048 const index = this.activeAnimations.indexOf(animation); 4049 4050 if (index > -1) { 4051 this.activeAnimations.splice(index, 1); 4052 } 4053 } 4054 4055 stopAll() { 4056 // _stopAllAnimations 4057 this.activeAnimations.forEach(animation => { 4058 animation.destroy(); 4059 }); 4060 this.activeAnimations = []; 4061 } 4062 /** 4063 * Stop all pan or zoom transitions 4064 */ 4065 4066 4067 stopAllPan() { 4068 this.activeAnimations = this.activeAnimations.filter(animation => { 4069 if (animation.props.isPan) { 4070 animation.destroy(); 4071 return false; 4072 } 4073 4074 return true; 4075 }); 4076 } 4077 4078 stopMainScroll() { 4079 this.activeAnimations = this.activeAnimations.filter(animation => { 4080 if (animation.props.isMainScroll) { 4081 animation.destroy(); 4082 return false; 4083 } 4084 4085 return true; 4086 }); 4087 } 4088 /** 4089 * Returns true if main scroll transition is running 4090 */ 4091 // isMainScrollRunning() { 4092 // return this.activeAnimations.some((animation) => { 4093 // return animation.props.isMainScroll; 4094 // }); 4095 // } 4096 4097 /** 4098 * Returns true if any pan or zoom transition is running 4099 */ 4100 4101 4102 isPanRunning() { 4103 return this.activeAnimations.some(animation => { 4104 return animation.props.isPan; 4105 }); 4106 } 4107 4108} 4109 4110/** @typedef {import('./photoswipe.js').default} PhotoSwipe */ 4111 4112/** 4113 * Handles scroll wheel. 4114 * Can pan and zoom current slide image. 4115 */ 4116class ScrollWheel { 4117 /** 4118 * @param {PhotoSwipe} pswp 4119 */ 4120 constructor(pswp) { 4121 this.pswp = pswp; 4122 pswp.events.add(pswp.element, 'wheel', 4123 /** @type EventListener */ 4124 this._onWheel.bind(this)); 4125 } 4126 /** 4127 * @private 4128 * @param {WheelEvent} e 4129 */ 4130 4131 4132 _onWheel(e) { 4133 e.preventDefault(); 4134 const { 4135 currSlide 4136 } = this.pswp; 4137 let { 4138 deltaX, 4139 deltaY 4140 } = e; 4141 4142 if (!currSlide) { 4143 return; 4144 } 4145 4146 if (this.pswp.dispatch('wheel', { 4147 originalEvent: e 4148 }).defaultPrevented) { 4149 return; 4150 } 4151 4152 if (e.ctrlKey || this.pswp.options.wheelToZoom) { 4153 // zoom 4154 if (currSlide.isZoomable()) { 4155 let zoomFactor = -deltaY; 4156 4157 if (e.deltaMode === 1 4158 /* DOM_DELTA_LINE */ 4159 ) { 4160 zoomFactor *= 0.05; 4161 } else { 4162 zoomFactor *= e.deltaMode ? 1 : 0.002; 4163 } 4164 4165 zoomFactor = 2 ** zoomFactor; 4166 const destZoomLevel = currSlide.currZoomLevel * zoomFactor; 4167 currSlide.zoomTo(destZoomLevel, { 4168 x: e.clientX, 4169 y: e.clientY 4170 }); 4171 } 4172 } else { 4173 // pan 4174 if (currSlide.isPannable()) { 4175 if (e.deltaMode === 1 4176 /* DOM_DELTA_LINE */ 4177 ) { 4178 // 18 - average line height 4179 deltaX *= 18; 4180 deltaY *= 18; 4181 } 4182 4183 currSlide.panTo(currSlide.pan.x - deltaX, currSlide.pan.y - deltaY); 4184 } 4185 } 4186 } 4187 4188} 4189 4190/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 4191 4192/** 4193 * @template T 4194 * @typedef {import('../types.js').Methods<T>} Methods<T> 4195 */ 4196 4197/** 4198 * @typedef {Object} UIElementMarkupProps 4199 * @prop {boolean} [isCustomSVG] 4200 * @prop {string} inner 4201 * @prop {string} [outlineID] 4202 * @prop {number | string} [size] 4203 */ 4204 4205/** 4206 * @typedef {Object} UIElementData 4207 * @prop {DefaultUIElements | string} [name] 4208 * @prop {string} [className] 4209 * @prop {UIElementMarkup} [html] 4210 * @prop {boolean} [isButton] 4211 * @prop {keyof HTMLElementTagNameMap} [tagName] 4212 * @prop {string} [title] 4213 * @prop {string} [ariaLabel] 4214 * @prop {(element: HTMLElement, pswp: PhotoSwipe) => void} [onInit] 4215 * @prop {Methods<PhotoSwipe> | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void)} [onClick] 4216 * @prop {'bar' | 'wrapper' | 'root'} [appendTo] 4217 * @prop {number} [order] 4218 */ 4219 4220/** @typedef {'arrowPrev' | 'arrowNext' | 'close' | 'zoom' | 'counter'} DefaultUIElements */ 4221 4222/** @typedef {string | UIElementMarkupProps} UIElementMarkup */ 4223 4224/** 4225 * @param {UIElementMarkup} [htmlData] 4226 * @returns {string} 4227 */ 4228 4229function addElementHTML(htmlData) { 4230 if (typeof htmlData === 'string') { 4231 // Allow developers to provide full svg, 4232 // For example: 4233 // <svg viewBox="0 0 32 32" width="32" height="32" aria-hidden="true" class="pswp__icn"> 4234 // <path d="..." /> 4235 // <circle ... /> 4236 // </svg> 4237 // Can also be any HTML string. 4238 return htmlData; 4239 } 4240 4241 if (!htmlData || !htmlData.isCustomSVG) { 4242 return ''; 4243 } 4244 4245 const svgData = htmlData; 4246 let out = '<svg aria-hidden="true" class="pswp__icn" viewBox="0 0 %d %d" width="%d" height="%d">'; // replace all %d with size 4247 4248 out = out.split('%d').join( 4249 /** @type {string} */ 4250 svgData.size || 32); // Icons may contain outline/shadow, 4251 // to make it we "clone" base icon shape and add border to it. 4252 // Icon itself and border are styled via CSS. 4253 // 4254 // Property shadowID defines ID of element that should be cloned. 4255 4256 if (svgData.outlineID) { 4257 out += '<use class="pswp__icn-shadow" xlink:href="#' + svgData.outlineID + '"/>'; 4258 } 4259 4260 out += svgData.inner; 4261 out += '</svg>'; 4262 return out; 4263} 4264 4265class UIElement { 4266 /** 4267 * @param {PhotoSwipe} pswp 4268 * @param {UIElementData} data 4269 */ 4270 constructor(pswp, data) { 4271 var _container; 4272 4273 const name = data.name || data.className; 4274 let elementHTML = data.html; // @ts-expect-error lookup only by `data.name` maybe? 4275 4276 if (pswp.options[name] === false) { 4277 // exit if element is disabled from options 4278 return; 4279 } // Allow to override SVG icons from options 4280 // @ts-expect-error lookup only by `data.name` maybe? 4281 4282 4283 if (typeof pswp.options[name + 'SVG'] === 'string') { 4284 // arrowPrevSVG 4285 // arrowNextSVG 4286 // closeSVG 4287 // zoomSVG 4288 // @ts-expect-error lookup only by `data.name` maybe? 4289 elementHTML = pswp.options[name + 'SVG']; 4290 } 4291 4292 pswp.dispatch('uiElementCreate', { 4293 data 4294 }); 4295 let className = ''; 4296 4297 if (data.isButton) { 4298 className += 'pswp__button '; 4299 className += data.className || `pswp__button--${data.name}`; 4300 } else { 4301 className += data.className || `pswp__${data.name}`; 4302 } 4303 4304 let tagName = data.isButton ? data.tagName || 'button' : data.tagName || 'div'; 4305 tagName = 4306 /** @type {keyof HTMLElementTagNameMap} */ 4307 tagName.toLowerCase(); 4308 /** @type {HTMLElement} */ 4309 4310 const element = createElement(className, tagName); 4311 4312 if (data.isButton) { 4313 if (tagName === 'button') { 4314 /** @type {HTMLButtonElement} */ 4315 element.type = 'button'; 4316 } 4317 4318 let { 4319 title 4320 } = data; 4321 const { 4322 ariaLabel 4323 } = data; // @ts-expect-error lookup only by `data.name` maybe? 4324 4325 if (typeof pswp.options[name + 'Title'] === 'string') { 4326 // @ts-expect-error lookup only by `data.name` maybe? 4327 title = pswp.options[name + 'Title']; 4328 } 4329 4330 if (title) { 4331 element.title = title; 4332 } 4333 4334 const ariaText = ariaLabel || title; 4335 4336 if (ariaText) { 4337 element.setAttribute('aria-label', ariaText); 4338 } 4339 } 4340 4341 element.innerHTML = addElementHTML(elementHTML); 4342 4343 if (data.onInit) { 4344 data.onInit(element, pswp); 4345 } 4346 4347 if (data.onClick) { 4348 element.onclick = e => { 4349 if (typeof data.onClick === 'string') { 4350 // @ts-ignore 4351 pswp[data.onClick](); 4352 } else if (typeof data.onClick === 'function') { 4353 data.onClick(e, element, pswp); 4354 } 4355 }; 4356 } // Top bar is default position 4357 4358 4359 const appendTo = data.appendTo || 'bar'; 4360 /** @type {HTMLElement | undefined} root element by default */ 4361 4362 let container = pswp.element; 4363 4364 if (appendTo === 'bar') { 4365 if (!pswp.topBar) { 4366 pswp.topBar = createElement('pswp__top-bar pswp__hide-on-close', 'div', pswp.scrollWrap); 4367 } 4368 4369 container = pswp.topBar; 4370 } else { 4371 // element outside of top bar gets a secondary class 4372 // that makes element fade out on close 4373 element.classList.add('pswp__hide-on-close'); 4374 4375 if (appendTo === 'wrapper') { 4376 container = pswp.scrollWrap; 4377 } 4378 } 4379 4380 (_container = container) === null || _container === void 0 || _container.appendChild(pswp.applyFilters('uiElement', element, data)); 4381 } 4382 4383} 4384 4385/* 4386 Backward and forward arrow buttons 4387 */ 4388 4389/** @typedef {import('./ui-element.js').UIElementData} UIElementData */ 4390 4391/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 4392 4393/** 4394 * 4395 * @param {HTMLElement} element 4396 * @param {PhotoSwipe} pswp 4397 * @param {boolean} [isNextButton] 4398 */ 4399function initArrowButton(element, pswp, isNextButton) { 4400 element.classList.add('pswp__button--arrow'); // TODO: this should point to a unique id for this instance 4401 4402 element.setAttribute('aria-controls', 'pswp__items'); 4403 pswp.on('change', () => { 4404 if (!pswp.options.loop) { 4405 if (isNextButton) { 4406 /** @type {HTMLButtonElement} */ 4407 element.disabled = !(pswp.currIndex < pswp.getNumItems() - 1); 4408 } else { 4409 /** @type {HTMLButtonElement} */ 4410 element.disabled = !(pswp.currIndex > 0); 4411 } 4412 } 4413 }); 4414} 4415/** @type {UIElementData} */ 4416 4417 4418const arrowPrev = { 4419 name: 'arrowPrev', 4420 className: 'pswp__button--arrow--prev', 4421 title: 'Previous', 4422 order: 10, 4423 isButton: true, 4424 appendTo: 'wrapper', 4425 html: { 4426 isCustomSVG: true, 4427 size: 60, 4428 inner: '<path d="M29 43l-3 3-16-16 16-16 3 3-13 13 13 13z" id="pswp__icn-arrow"/>', 4429 outlineID: 'pswp__icn-arrow' 4430 }, 4431 onClick: 'prev', 4432 onInit: initArrowButton 4433}; 4434/** @type {UIElementData} */ 4435 4436const arrowNext = { 4437 name: 'arrowNext', 4438 className: 'pswp__button--arrow--next', 4439 title: 'Next', 4440 order: 11, 4441 isButton: true, 4442 appendTo: 'wrapper', 4443 html: { 4444 isCustomSVG: true, 4445 size: 60, 4446 inner: '<use xlink:href="#pswp__icn-arrow"/>', 4447 outlineID: 'pswp__icn-arrow' 4448 }, 4449 onClick: 'next', 4450 onInit: (el, pswp) => { 4451 initArrowButton(el, pswp, true); 4452 } 4453}; 4454 4455/** @type {import('./ui-element.js').UIElementData} UIElementData */ 4456const closeButton = { 4457 name: 'close', 4458 title: 'Close', 4459 order: 20, 4460 isButton: true, 4461 html: { 4462 isCustomSVG: true, 4463 inner: '<path d="M24 10l-2-2-6 6-6-6-2 2 6 6-6 6 2 2 6-6 6 6 2-2-6-6z" id="pswp__icn-close"/>', 4464 outlineID: 'pswp__icn-close' 4465 }, 4466 onClick: 'close' 4467}; 4468 4469/** @type {import('./ui-element.js').UIElementData} UIElementData */ 4470const zoomButton = { 4471 name: 'zoom', 4472 title: 'Zoom', 4473 order: 10, 4474 isButton: true, 4475 html: { 4476 isCustomSVG: true, 4477 // eslint-disable-next-line max-len 4478 inner: '<path d="M17.426 19.926a6 6 0 1 1 1.5-1.5L23 22.5 21.5 24l-4.074-4.074z" id="pswp__icn-zoom"/>' + '<path fill="currentColor" class="pswp__zoom-icn-bar-h" d="M11 16v-2h6v2z"/>' + '<path fill="currentColor" class="pswp__zoom-icn-bar-v" d="M13 12h2v6h-2z"/>', 4479 outlineID: 'pswp__icn-zoom' 4480 }, 4481 onClick: 'toggleZoom' 4482}; 4483 4484/** @type {import('./ui-element.js').UIElementData} UIElementData */ 4485const loadingIndicator = { 4486 name: 'preloader', 4487 appendTo: 'bar', 4488 order: 7, 4489 html: { 4490 isCustomSVG: true, 4491 // eslint-disable-next-line max-len 4492 inner: '<path fill-rule="evenodd" clip-rule="evenodd" d="M21.2 16a5.2 5.2 0 1 1-5.2-5.2V8a8 8 0 1 0 8 8h-2.8Z" id="pswp__icn-loading"/>', 4493 outlineID: 'pswp__icn-loading' 4494 }, 4495 onInit: (indicatorElement, pswp) => { 4496 /** @type {boolean | undefined} */ 4497 let isVisible; 4498 /** @type {NodeJS.Timeout | null} */ 4499 4500 let delayTimeout = null; 4501 /** 4502 * @param {string} className 4503 * @param {boolean} add 4504 */ 4505 4506 const toggleIndicatorClass = (className, add) => { 4507 indicatorElement.classList.toggle('pswp__preloader--' + className, add); 4508 }; 4509 /** 4510 * @param {boolean} visible 4511 */ 4512 4513 4514 const setIndicatorVisibility = visible => { 4515 if (isVisible !== visible) { 4516 isVisible = visible; 4517 toggleIndicatorClass('active', visible); 4518 } 4519 }; 4520 4521 const updatePreloaderVisibility = () => { 4522 var _pswp$currSlide; 4523 4524 if (!((_pswp$currSlide = pswp.currSlide) !== null && _pswp$currSlide !== void 0 && _pswp$currSlide.content.isLoading())) { 4525 setIndicatorVisibility(false); 4526 4527 if (delayTimeout) { 4528 clearTimeout(delayTimeout); 4529 delayTimeout = null; 4530 } 4531 4532 return; 4533 } 4534 4535 if (!delayTimeout) { 4536 // display loading indicator with delay 4537 delayTimeout = setTimeout(() => { 4538 var _pswp$currSlide2; 4539 4540 setIndicatorVisibility(Boolean((_pswp$currSlide2 = pswp.currSlide) === null || _pswp$currSlide2 === void 0 ? void 0 : _pswp$currSlide2.content.isLoading())); 4541 delayTimeout = null; 4542 }, pswp.options.preloaderDelay); 4543 } 4544 }; 4545 4546 pswp.on('change', updatePreloaderVisibility); 4547 pswp.on('loadComplete', e => { 4548 if (pswp.currSlide === e.slide) { 4549 updatePreloaderVisibility(); 4550 } 4551 }); // expose the method 4552 4553 if (pswp.ui) { 4554 pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility; 4555 } 4556 } 4557}; 4558 4559/** @type {import('./ui-element.js').UIElementData} UIElementData */ 4560const counterIndicator = { 4561 name: 'counter', 4562 order: 5, 4563 onInit: (counterElement, pswp) => { 4564 pswp.on('change', () => { 4565 counterElement.innerText = pswp.currIndex + 1 + pswp.options.indexIndicatorSep + pswp.getNumItems(); 4566 }); 4567 } 4568}; 4569 4570/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 4571 4572/** @typedef {import('./ui-element.js').UIElementData} UIElementData */ 4573 4574/** 4575 * Set special class on element when image is zoomed. 4576 * 4577 * By default, it is used to adjust 4578 * zoom icon and zoom cursor via CSS. 4579 * 4580 * @param {HTMLElement} el 4581 * @param {boolean} isZoomedIn 4582 */ 4583 4584function setZoomedIn(el, isZoomedIn) { 4585 el.classList.toggle('pswp--zoomed-in', isZoomedIn); 4586} 4587 4588class UI { 4589 /** 4590 * @param {PhotoSwipe} pswp 4591 */ 4592 constructor(pswp) { 4593 this.pswp = pswp; 4594 this.isRegistered = false; 4595 /** @type {UIElementData[]} */ 4596 4597 this.uiElementsData = []; 4598 /** @type {(UIElement | UIElementData)[]} */ 4599 4600 this.items = []; 4601 /** @type {() => void} */ 4602 4603 this.updatePreloaderVisibility = () => {}; 4604 /** 4605 * @private 4606 * @type {number | undefined} 4607 */ 4608 4609 4610 this._lastUpdatedZoomLevel = undefined; 4611 } 4612 4613 init() { 4614 const { 4615 pswp 4616 } = this; 4617 this.isRegistered = false; 4618 this.uiElementsData = [closeButton, arrowPrev, arrowNext, zoomButton, loadingIndicator, counterIndicator]; 4619 pswp.dispatch('uiRegister'); // sort by order 4620 4621 this.uiElementsData.sort((a, b) => { 4622 // default order is 0 4623 return (a.order || 0) - (b.order || 0); 4624 }); 4625 this.items = []; 4626 this.isRegistered = true; 4627 this.uiElementsData.forEach(uiElementData => { 4628 this.registerElement(uiElementData); 4629 }); 4630 pswp.on('change', () => { 4631 var _pswp$element; 4632 4633 (_pswp$element = pswp.element) === null || _pswp$element === void 0 || _pswp$element.classList.toggle('pswp--one-slide', pswp.getNumItems() === 1); 4634 }); 4635 pswp.on('zoomPanUpdate', () => this._onZoomPanUpdate()); 4636 } 4637 /** 4638 * @param {UIElementData} elementData 4639 */ 4640 4641 4642 registerElement(elementData) { 4643 if (this.isRegistered) { 4644 this.items.push(new UIElement(this.pswp, elementData)); 4645 } else { 4646 this.uiElementsData.push(elementData); 4647 } 4648 } 4649 /** 4650 * Fired each time zoom or pan position is changed. 4651 * Update classes that control visibility of zoom button and cursor icon. 4652 * 4653 * @private 4654 */ 4655 4656 4657 _onZoomPanUpdate() { 4658 const { 4659 template, 4660 currSlide, 4661 options 4662 } = this.pswp; 4663 4664 if (this.pswp.opener.isClosing || !template || !currSlide) { 4665 return; 4666 } 4667 4668 let { 4669 currZoomLevel 4670 } = currSlide; // if not open yet - check against initial zoom level 4671 4672 if (!this.pswp.opener.isOpen) { 4673 currZoomLevel = currSlide.zoomLevels.initial; 4674 } 4675 4676 if (currZoomLevel === this._lastUpdatedZoomLevel) { 4677 return; 4678 } 4679 4680 this._lastUpdatedZoomLevel = currZoomLevel; 4681 const currZoomLevelDiff = currSlide.zoomLevels.initial - currSlide.zoomLevels.secondary; // Initial and secondary zoom levels are almost equal 4682 4683 if (Math.abs(currZoomLevelDiff) < 0.01 || !currSlide.isZoomable()) { 4684 // disable zoom 4685 setZoomedIn(template, false); 4686 template.classList.remove('pswp--zoom-allowed'); 4687 return; 4688 } 4689 4690 template.classList.add('pswp--zoom-allowed'); 4691 const potentialZoomLevel = currZoomLevel === currSlide.zoomLevels.initial ? currSlide.zoomLevels.secondary : currSlide.zoomLevels.initial; 4692 setZoomedIn(template, potentialZoomLevel <= currZoomLevel); 4693 4694 if (options.imageClickAction === 'zoom' || options.imageClickAction === 'zoom-or-close') { 4695 template.classList.add('pswp--click-to-zoom'); 4696 } 4697 } 4698 4699} 4700 4701/** @typedef {import('./slide.js').SlideData} SlideData */ 4702 4703/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 4704 4705/** @typedef {{ x: number; y: number; w: number; innerRect?: { w: number; h: number; x: number; y: number } }} Bounds */ 4706 4707/** 4708 * @param {HTMLElement} el 4709 * @returns Bounds 4710 */ 4711function getBoundsByElement(el) { 4712 const thumbAreaRect = el.getBoundingClientRect(); 4713 return { 4714 x: thumbAreaRect.left, 4715 y: thumbAreaRect.top, 4716 w: thumbAreaRect.width 4717 }; 4718} 4719/** 4720 * @param {HTMLElement} el 4721 * @param {number} imageWidth 4722 * @param {number} imageHeight 4723 * @returns Bounds 4724 */ 4725 4726 4727function getCroppedBoundsByElement(el, imageWidth, imageHeight) { 4728 const thumbAreaRect = el.getBoundingClientRect(); // fill image into the area 4729 // (do they same as object-fit:cover does to retrieve coordinates) 4730 4731 const hRatio = thumbAreaRect.width / imageWidth; 4732 const vRatio = thumbAreaRect.height / imageHeight; 4733 const fillZoomLevel = hRatio > vRatio ? hRatio : vRatio; 4734 const offsetX = (thumbAreaRect.width - imageWidth * fillZoomLevel) / 2; 4735 const offsetY = (thumbAreaRect.height - imageHeight * fillZoomLevel) / 2; 4736 /** 4737 * Coordinates of the image, 4738 * as if it was not cropped, 4739 * height is calculated automatically 4740 * 4741 * @type {Bounds} 4742 */ 4743 4744 const bounds = { 4745 x: thumbAreaRect.left + offsetX, 4746 y: thumbAreaRect.top + offsetY, 4747 w: imageWidth * fillZoomLevel 4748 }; // Coordinates of inner crop area 4749 // relative to the image 4750 4751 bounds.innerRect = { 4752 w: thumbAreaRect.width, 4753 h: thumbAreaRect.height, 4754 x: offsetX, 4755 y: offsetY 4756 }; 4757 return bounds; 4758} 4759/** 4760 * Get dimensions of thumbnail image 4761 * (click on which opens photoswipe or closes photoswipe to) 4762 * 4763 * @param {number} index 4764 * @param {SlideData} itemData 4765 * @param {PhotoSwipe} instance PhotoSwipe instance 4766 * @returns {Bounds | undefined} 4767 */ 4768 4769 4770function getThumbBounds(index, itemData, instance) { 4771 // legacy event, before filters were introduced 4772 const event = instance.dispatch('thumbBounds', { 4773 index, 4774 itemData, 4775 instance 4776 }); // @ts-expect-error 4777 4778 if (event.thumbBounds) { 4779 // @ts-expect-error 4780 return event.thumbBounds; 4781 } 4782 4783 const { 4784 element 4785 } = itemData; 4786 /** @type {Bounds | undefined} */ 4787 4788 let thumbBounds; 4789 /** @type {HTMLElement | null | undefined} */ 4790 4791 let thumbnail; 4792 4793 if (element && instance.options.thumbSelector !== false) { 4794 const thumbSelector = instance.options.thumbSelector || 'img'; 4795 thumbnail = element.matches(thumbSelector) ? element : 4796 /** @type {HTMLElement | null} */ 4797 element.querySelector(thumbSelector); 4798 } 4799 4800 thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index); 4801 4802 if (thumbnail) { 4803 if (!itemData.thumbCropped) { 4804 thumbBounds = getBoundsByElement(thumbnail); 4805 } else { 4806 thumbBounds = getCroppedBoundsByElement(thumbnail, itemData.width || itemData.w || 0, itemData.height || itemData.h || 0); 4807 } 4808 } 4809 4810 return instance.applyFilters('thumbBounds', thumbBounds, itemData, index); 4811} 4812 4813/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */ 4814 4815/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 4816 4817/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 4818 4819/** @typedef {import('../photoswipe.js').DataSource} DataSource */ 4820 4821/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */ 4822 4823/** @typedef {import('../slide/content.js').default} ContentDefault */ 4824 4825/** @typedef {import('../slide/slide.js').default} Slide */ 4826 4827/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 4828 4829/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */ 4830 4831/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */ 4832 4833/** 4834 * Allow adding an arbitrary props to the Content 4835 * https://photoswipe.com/custom-content/#using-webp-image-format 4836 * @typedef {ContentDefault & Record<string, any>} Content 4837 */ 4838 4839/** @typedef {{ x?: number; y?: number }} Point */ 4840 4841/** 4842 * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/ 4843 * 4844 * 4845 * https://photoswipe.com/adding-ui-elements/ 4846 * 4847 * @prop {undefined} uiRegister 4848 * @prop {{ data: UIElementData }} uiElementCreate 4849 * 4850 * 4851 * https://photoswipe.com/events/#initialization-events 4852 * 4853 * @prop {undefined} beforeOpen 4854 * @prop {undefined} firstUpdate 4855 * @prop {undefined} initialLayout 4856 * @prop {undefined} change 4857 * @prop {undefined} afterInit 4858 * @prop {undefined} bindEvents 4859 * 4860 * 4861 * https://photoswipe.com/events/#opening-or-closing-transition-events 4862 * 4863 * @prop {undefined} openingAnimationStart 4864 * @prop {undefined} openingAnimationEnd 4865 * @prop {undefined} closingAnimationStart 4866 * @prop {undefined} closingAnimationEnd 4867 * 4868 * 4869 * https://photoswipe.com/events/#closing-events 4870 * 4871 * @prop {undefined} close 4872 * @prop {undefined} destroy 4873 * 4874 * 4875 * https://photoswipe.com/events/#pointer-and-gesture-events 4876 * 4877 * @prop {{ originalEvent: PointerEvent }} pointerDown 4878 * @prop {{ originalEvent: PointerEvent }} pointerMove 4879 * @prop {{ originalEvent: PointerEvent }} pointerUp 4880 * @prop {{ bgOpacity: number }} pinchClose can be default prevented 4881 * @prop {{ panY: number }} verticalDrag can be default prevented 4882 * 4883 * 4884 * https://photoswipe.com/events/#slide-content-events 4885 * 4886 * @prop {{ content: Content }} contentInit 4887 * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented 4888 * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented 4889 * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete 4890 * @prop {{ content: Content; slide: Slide }} loadError 4891 * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented 4892 * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange 4893 * @prop {{ content: Content }} contentLazyLoad can be default prevented 4894 * @prop {{ content: Content }} contentAppend can be default prevented 4895 * @prop {{ content: Content }} contentActivate can be default prevented 4896 * @prop {{ content: Content }} contentDeactivate can be default prevented 4897 * @prop {{ content: Content }} contentRemove can be default prevented 4898 * @prop {{ content: Content }} contentDestroy can be default prevented 4899 * 4900 * 4901 * undocumented 4902 * 4903 * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented 4904 * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented 4905 * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented 4906 * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented 4907 * 4908 * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented 4909 * @prop {{ x: number; dragging: boolean }} moveMainScroll 4910 * @prop {{ slide: Slide }} firstZoomPan 4911 * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData 4912 * @prop {undefined} beforeResize 4913 * @prop {undefined} resize 4914 * @prop {undefined} viewportSize 4915 * @prop {undefined} updateScrollOffset 4916 * @prop {{ slide: Slide }} slideInit 4917 * @prop {{ slide: Slide }} afterSetContent 4918 * @prop {{ slide: Slide }} slideLoad 4919 * @prop {{ slide: Slide }} appendHeavy can be default prevented 4920 * @prop {{ slide: Slide }} appendHeavyContent 4921 * @prop {{ slide: Slide }} slideActivate 4922 * @prop {{ slide: Slide }} slideDeactivate 4923 * @prop {{ slide: Slide }} slideDestroy 4924 * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo 4925 * @prop {{ slide: Slide }} zoomPanUpdate 4926 * @prop {{ slide: Slide }} initialZoomPan 4927 * @prop {{ slide: Slide }} calcSlideSize 4928 * @prop {undefined} resolutionChanged 4929 * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented 4930 * @prop {{ content: Content }} contentAppendImage can be default prevented 4931 * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented 4932 * @prop {undefined} lazyLoad 4933 * @prop {{ slide: Slide }} calcBounds 4934 * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate 4935 * 4936 * 4937 * legacy 4938 * 4939 * @prop {undefined} init 4940 * @prop {undefined} initialZoomIn 4941 * @prop {undefined} initialZoomOut 4942 * @prop {undefined} initialZoomInEnd 4943 * @prop {undefined} initialZoomOutEnd 4944 * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems 4945 * @prop {{ itemData: SlideData; index: number }} itemData 4946 * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds 4947 */ 4948 4949/** 4950 * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/ 4951 * 4952 * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems 4953 * Modify the total amount of slides. Example on Data sources page. 4954 * https://photoswipe.com/filters/#numitems 4955 * 4956 * @prop {(itemData: SlideData, index: number) => SlideData} itemData 4957 * Modify slide item data. Example on Data sources page. 4958 * https://photoswipe.com/filters/#itemdata 4959 * 4960 * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData 4961 * Modify item data when it's parsed from DOM element. Example on Data sources page. 4962 * https://photoswipe.com/filters/#domitemdata 4963 * 4964 * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex 4965 * Modify clicked gallery item index. 4966 * https://photoswipe.com/filters/#clickedindex 4967 * 4968 * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc 4969 * Modify placeholder image source. 4970 * https://photoswipe.com/filters/#placeholdersrc 4971 * 4972 * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading 4973 * Modify if the content is currently loading. 4974 * https://photoswipe.com/filters/#iscontentloading 4975 * 4976 * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable 4977 * Modify if the content can be zoomed. 4978 * https://photoswipe.com/filters/#iscontentzoomable 4979 * 4980 * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder 4981 * Modify if the placeholder should be used for the content. 4982 * https://photoswipe.com/filters/#usecontentplaceholder 4983 * 4984 * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder 4985 * Modify if the placeholder should be kept after the content is loaded. 4986 * https://photoswipe.com/filters/#iskeepingplaceholder 4987 * 4988 * 4989 * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement 4990 * Modify an element when the content has error state (for example, if image cannot be loaded). 4991 * https://photoswipe.com/filters/#contenterrorelement 4992 * 4993 * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement 4994 * Modify a UI element that's being created. 4995 * https://photoswipe.com/filters/#uielement 4996 * 4997 * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl 4998 * Modify the thumbnail element from which opening zoom animation starts or ends. 4999 * https://photoswipe.com/filters/#thumbel 5000 * 5001 * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds 5002 * Modify the thumbnail bounds from which opening zoom animation starts or ends. 5003 * https://photoswipe.com/filters/#thumbbounds 5004 * 5005 * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth 5006 * 5007 * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent 5008 * 5009 */ 5010 5011/** 5012 * @template {keyof PhotoSwipeFiltersMap} T 5013 * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter 5014 */ 5015 5016/** 5017 * @template {keyof PhotoSwipeEventsMap} T 5018 * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent 5019 */ 5020 5021/** 5022 * @template {keyof PhotoSwipeEventsMap} T 5023 * @typedef {(event: AugmentedEvent<T>) => void} EventCallback 5024 */ 5025 5026/** 5027 * Base PhotoSwipe event object 5028 * 5029 * @template {keyof PhotoSwipeEventsMap} T 5030 */ 5031class PhotoSwipeEvent { 5032 /** 5033 * @param {T} type 5034 * @param {PhotoSwipeEventsMap[T]} [details] 5035 */ 5036 constructor(type, details) { 5037 this.type = type; 5038 this.defaultPrevented = false; 5039 5040 if (details) { 5041 Object.assign(this, details); 5042 } 5043 } 5044 5045 preventDefault() { 5046 this.defaultPrevented = true; 5047 } 5048 5049} 5050/** 5051 * PhotoSwipe base class that can listen and dispatch for events. 5052 * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js 5053 */ 5054 5055 5056class Eventable { 5057 constructor() { 5058 /** 5059 * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }} 5060 */ 5061 this._listeners = {}; 5062 /** 5063 * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }} 5064 */ 5065 5066 this._filters = {}; 5067 /** @type {PhotoSwipe | undefined} */ 5068 5069 this.pswp = undefined; 5070 /** @type {PhotoSwipeOptions | undefined} */ 5071 5072 this.options = undefined; 5073 } 5074 /** 5075 * @template {keyof PhotoSwipeFiltersMap} T 5076 * @param {T} name 5077 * @param {PhotoSwipeFiltersMap[T]} fn 5078 * @param {number} priority 5079 */ 5080 5081 5082 addFilter(name, fn, priority = 100) { 5083 var _this$_filters$name, _this$_filters$name2, _this$pswp; 5084 5085 if (!this._filters[name]) { 5086 this._filters[name] = []; 5087 } 5088 5089 (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({ 5090 fn, 5091 priority 5092 }); 5093 (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority); 5094 (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority); 5095 } 5096 /** 5097 * @template {keyof PhotoSwipeFiltersMap} T 5098 * @param {T} name 5099 * @param {PhotoSwipeFiltersMap[T]} fn 5100 */ 5101 5102 5103 removeFilter(name, fn) { 5104 if (this._filters[name]) { 5105 // @ts-expect-error 5106 this._filters[name] = this._filters[name].filter(filter => filter.fn !== fn); 5107 } 5108 5109 if (this.pswp) { 5110 this.pswp.removeFilter(name, fn); 5111 } 5112 } 5113 /** 5114 * @template {keyof PhotoSwipeFiltersMap} T 5115 * @param {T} name 5116 * @param {Parameters<PhotoSwipeFiltersMap[T]>} args 5117 * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]} 5118 */ 5119 5120 5121 applyFilters(name, ...args) { 5122 var _this$_filters$name3; 5123 5124 (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach(filter => { 5125 // @ts-expect-error 5126 args[0] = filter.fn.apply(this, args); 5127 }); 5128 return args[0]; 5129 } 5130 /** 5131 * @template {keyof PhotoSwipeEventsMap} T 5132 * @param {T} name 5133 * @param {EventCallback<T>} fn 5134 */ 5135 5136 5137 on(name, fn) { 5138 var _this$_listeners$name, _this$pswp2; 5139 5140 if (!this._listeners[name]) { 5141 this._listeners[name] = []; 5142 } 5143 5144 (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn); // When binding events to lightbox, 5145 // also bind events to PhotoSwipe Core, 5146 // if it's open. 5147 5148 (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn); 5149 } 5150 /** 5151 * @template {keyof PhotoSwipeEventsMap} T 5152 * @param {T} name 5153 * @param {EventCallback<T>} fn 5154 */ 5155 5156 5157 off(name, fn) { 5158 var _this$pswp3; 5159 5160 if (this._listeners[name]) { 5161 // @ts-expect-error 5162 this._listeners[name] = this._listeners[name].filter(listener => fn !== listener); 5163 } 5164 5165 (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn); 5166 } 5167 /** 5168 * @template {keyof PhotoSwipeEventsMap} T 5169 * @param {T} name 5170 * @param {PhotoSwipeEventsMap[T]} [details] 5171 * @returns {AugmentedEvent<T>} 5172 */ 5173 5174 5175 dispatch(name, details) { 5176 var _this$_listeners$name2; 5177 5178 if (this.pswp) { 5179 return this.pswp.dispatch(name, details); 5180 } 5181 5182 const event = 5183 /** @type {AugmentedEvent<T>} */ 5184 new PhotoSwipeEvent(name, details); 5185 (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach(listener => { 5186 listener.call(this, event); 5187 }); 5188 return event; 5189 } 5190 5191} 5192 5193class Placeholder { 5194 /** 5195 * @param {string | false} imageSrc 5196 * @param {HTMLElement} container 5197 */ 5198 constructor(imageSrc, container) { 5199 // Create placeholder 5200 // (stretched thumbnail or simple div behind the main image) 5201 5202 /** @type {HTMLImageElement | HTMLDivElement | null} */ 5203 this.element = createElement('pswp__img pswp__img--placeholder', imageSrc ? 'img' : 'div', container); 5204 5205 if (imageSrc) { 5206 const imgEl = 5207 /** @type {HTMLImageElement} */ 5208 this.element; 5209 imgEl.decoding = 'async'; 5210 imgEl.alt = ''; 5211 imgEl.src = imageSrc; 5212 imgEl.setAttribute('role', 'presentation'); 5213 } 5214 5215 this.element.setAttribute('aria-hidden', 'true'); 5216 } 5217 /** 5218 * @param {number} width 5219 * @param {number} height 5220 */ 5221 5222 5223 setDisplayedSize(width, height) { 5224 if (!this.element) { 5225 return; 5226 } 5227 5228 if (this.element.tagName === 'IMG') { 5229 // Use transform scale() to modify img placeholder size 5230 // (instead of changing width/height directly). 5231 // This helps with performance, specifically in iOS15 Safari. 5232 setWidthHeight(this.element, 250, 'auto'); 5233 this.element.style.transformOrigin = '0 0'; 5234 this.element.style.transform = toTransformString(0, 0, width / 250); 5235 } else { 5236 setWidthHeight(this.element, width, height); 5237 } 5238 } 5239 5240 destroy() { 5241 var _this$element; 5242 5243 if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) { 5244 this.element.remove(); 5245 } 5246 5247 this.element = null; 5248 } 5249 5250} 5251
vendor: 8,643 bytes, lines 5252-5605
5252/** @typedef {import('./slide.js').default} Slide */ 5253 5254/** @typedef {import('./slide.js').SlideData} SlideData */ 5255 5256/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ 5257 5258/** @typedef {import('../util/util.js').LoadState} LoadState */ 5259 5260class Content { 5261 /** 5262 * @param {SlideData} itemData Slide data 5263 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance 5264 * @param {number} index 5265 */ 5266 constructor(itemData, instance, index) { 5267 this.instance = instance; 5268 this.data = itemData; 5269 this.index = index; 5270 /** @type {HTMLImageElement | HTMLDivElement | undefined} */ 5271 5272 this.element = undefined; 5273 /** @type {Placeholder | undefined} */ 5274 5275 this.placeholder = undefined; 5276 /** @type {Slide | undefined} */ 5277 5278 this.slide = undefined; 5279 this.displayedImageWidth = 0; 5280 this.displayedImageHeight = 0; 5281 this.width = Number(this.data.w) || Number(this.data.width) || 0; 5282 this.height = Number(this.data.h) || Number(this.data.height) || 0; 5283 this.isAttached = false; 5284 this.hasSlide = false; 5285 this.isDecoding = false; 5286 /** @type {LoadState} */ 5287 5288 this.state = LOAD_STATE.IDLE; 5289 5290 if (this.data.type) { 5291 this.type = this.data.type; 5292 } else if (this.data.src) { 5293 this.type = 'image'; 5294 } else { 5295 this.type = 'html'; 5296 } 5297 5298 this.instance.dispatch('contentInit', { 5299 content: this 5300 }); 5301 } 5302 5303 removePlaceholder() { 5304 if (this.placeholder && !this.keepPlaceholder()) { 5305 // With delay, as image might be loaded, but not rendered 5306 setTimeout(() => { 5307 if (this.placeholder) { 5308 this.placeholder.destroy(); 5309 this.placeholder = undefined; 5310 } 5311 }, 1000); 5312 } 5313 } 5314 /** 5315 * Preload content 5316 * 5317 * @param {boolean} isLazy 5318 * @param {boolean} [reload] 5319 */ 5320 5321 5322 load(isLazy, reload) { 5323 if (this.slide && this.usePlaceholder()) { 5324 if (!this.placeholder) { 5325 const placeholderSrc = this.instance.applyFilters('placeholderSrc', // use image-based placeholder only for the first slide, 5326 // as rendering (even small stretched thumbnail) is an expensive operation 5327 this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false, this); 5328 this.placeholder = new Placeholder(placeholderSrc, this.slide.container); 5329 } else { 5330 const placeholderEl = this.placeholder.element; // Add placeholder to DOM if it was already created 5331 5332 if (placeholderEl && !placeholderEl.parentElement) { 5333 this.slide.container.prepend(placeholderEl); 5334 } 5335 } 5336 } 5337 5338 if (this.element && !reload) { 5339 return; 5340 } 5341 5342 if (this.instance.dispatch('contentLoad', { 5343 content: this, 5344 isLazy 5345 }).defaultPrevented) { 5346 return; 5347 } 5348 5349 if (this.isImageContent()) { 5350 this.element = createElement('pswp__img', 'img'); // Start loading only after width is defined, as sizes might depend on it. 5351 // Due to Safari feature, we must define sizes before srcset. 5352 5353 if (this.displayedImageWidth) { 5354 this.loadImage(isLazy); 5355 } 5356 } else { 5357 this.element = createElement('pswp__content', 'div'); 5358 this.element.innerHTML = this.data.html || ''; 5359 } 5360 5361 if (reload && this.slide) { 5362 this.slide.updateContentSize(true); 5363 } 5364 } 5365 /** 5366 * Preload image 5367 * 5368 * @param {boolean} isLazy 5369 */ 5370 5371 5372 loadImage(isLazy) { 5373 var _this$data$src, _this$data$alt; 5374 5375 if (!this.isImageContent() || !this.element || this.instance.dispatch('contentLoadImage', { 5376 content: this, 5377 isLazy 5378 }).defaultPrevented) { 5379 return; 5380 } 5381 5382 const imageElement = 5383 /** @type HTMLImageElement */ 5384 this.element; 5385 this.updateSrcsetSizes(); 5386 5387 if (this.data.srcset) { 5388 imageElement.srcset = this.data.srcset; 5389 } 5390 5391 imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : ''; 5392 imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : ''; 5393 this.state = LOAD_STATE.LOADING; 5394 5395 if (imageElement.complete) { 5396 this.onLoaded(); 5397 } else { 5398 imageElement.onload = () => { 5399 this.onLoaded(); 5400 }; 5401 5402 imageElement.onerror = () => { 5403 this.onError(); 5404 }; 5405 } 5406 } 5407 /** 5408 * Assign slide to content 5409 * 5410 * @param {Slide} slide 5411 */ 5412 5413 5414 setSlide(slide) { 5415 this.slide = slide; 5416 this.hasSlide = true; 5417 this.instance = slide.pswp; // todo: do we need to unset slide? 5418 } 5419 /** 5420 * Content load success handler 5421 */ 5422 5423 5424 onLoaded() { 5425 this.state = LOAD_STATE.LOADED; 5426 5427 if (this.slide && this.element) { 5428 this.instance.dispatch('loadComplete', { 5429 slide: this.slide, 5430 content: this 5431 }); // if content is reloaded 5432 5433 if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) { 5434 this.append(); 5435 this.slide.updateContentSize(true); 5436 } 5437 5438 if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) { 5439 this.removePlaceholder(); 5440 } 5441 } 5442 } 5443 /** 5444 * Content load error handler 5445 */ 5446 5447 5448 onError() { 5449 this.state = LOAD_STATE.ERROR; 5450 5451 if (this.slide) { 5452 this.displayError(); 5453 this.instance.dispatch('loadComplete', { 5454 slide: this.slide, 5455 isError: true, 5456 content: this 5457 }); 5458 this.instance.dispatch('loadError', { 5459 slide: this.slide, 5460 content: this 5461 }); 5462 } 5463 } 5464 /** 5465 * @returns {Boolean} If the content is currently loading 5466 */ 5467 5468 5469 isLoading() { 5470 return this.instance.applyFilters('isContentLoading', this.state === LOAD_STATE.LOADING, this); 5471 } 5472 /** 5473 * @returns {Boolean} If the content is in error state 5474 */ 5475 5476 5477 isError() { 5478 return this.state === LOAD_STATE.ERROR; 5479 } 5480 /** 5481 * @returns {boolean} If the content is image 5482 */ 5483 5484 5485 isImageContent() { 5486 return this.type === 'image'; 5487 } 5488 /** 5489 * Update content size 5490 * 5491 * @param {Number} width 5492 * @param {Number} height 5493 */ 5494 5495 5496 setDisplayedSize(width, height) { 5497 if (!this.element) { 5498 return; 5499 } 5500 5501 if (this.placeholder) { 5502 this.placeholder.setDisplayedSize(width, height); 5503 } 5504 5505 if (this.instance.dispatch('contentResize', { 5506 content: this, 5507 width, 5508 height 5509 }).defaultPrevented) { 5510 return; 5511 } 5512 5513 setWidthHeight(this.element, width, height); 5514 5515 if (this.isImageContent() && !this.isError()) { 5516 const isInitialSizeUpdate = !this.displayedImageWidth && width; 5517 this.displayedImageWidth = width; 5518 this.displayedImageHeight = height; 5519 5520 if (isInitialSizeUpdate) { 5521 this.loadImage(false); 5522 } else { 5523 this.updateSrcsetSizes(); 5524 } 5525 5526 if (this.slide) { 5527 this.instance.dispatch('imageSizeChange', { 5528 slide: this.slide, 5529 width, 5530 height, 5531 content: this 5532 }); 5533 } 5534 } 5535 } 5536 /** 5537 * @returns {boolean} If the content can be zoomed 5538 */ 5539 5540 5541 isZoomable() { 5542 return this.instance.applyFilters('isContentZoomable', this.isImageContent() && this.state !== LOAD_STATE.ERROR, this); 5543 } 5544 /** 5545 * Update image srcset sizes attribute based on width and height 5546 */ 5547 5548 5549 updateSrcsetSizes() { 5550 // Handle srcset sizes attribute. 5551 // 5552 // Never lower quality, if it was increased previously. 5553 // Chrome does this automatically, Firefox and Safari do not, 5554 // so we store largest used size in dataset. 5555 if (!this.isImageContent() || !this.element || !this.data.srcset) { 5556 return; 5557 } 5558 5559 const image = 5560 /** @type HTMLImageElement */ 5561 this.element; 5562 const sizesWidth = this.instance.applyFilters('srcsetSizesWidth', this.displayedImageWidth, this); 5563 5564 if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) { 5565 image.sizes = sizesWidth + 'px'; 5566 image.dataset.largestUsedSize = String(sizesWidth); 5567 } 5568 } 5569 /** 5570 * @returns {boolean} If content should use a placeholder (from msrc by default) 5571 */ 5572 5573 5574 usePlaceholder() { 5575 return this.instance.applyFilters('useContentPlaceholder', this.isImageContent(), this); 5576 } 5577 /** 5578 * Preload content with lazy-loading param 5579 */ 5580 5581 5582 lazyLoad() { 5583 if (this.instance.dispatch('contentLazyLoad', { 5584 content: this 5585 }).defaultPrevented) { 5586 return; 5587 } 5588 5589 this.load(true); 5590 } 5591 /** 5592 * @returns {boolean} If placeholder should be kept after content is loaded 5593 */ 5594 5595 5596 keepPlaceholder() { 5597 return this.instance.applyFilters('isKeepingPlaceholder', this.isLoading(), this); 5598 } 5599 /** 5600 * Destroy the content 5601 */ 5602 5603 5604 destroy() { 5605 this.hasSlide = false;
vendor: 52,317 bytes, lines 5606-7504
5606 this.slide = undefined; 5607 5608 if (this.instance.dispatch('contentDestroy', { 5609 content: this 5610 }).defaultPrevented) { 5611 return; 5612 } 5613 5614 this.remove(); 5615 5616 if (this.placeholder) { 5617 this.placeholder.destroy(); 5618 this.placeholder = undefined; 5619 } 5620 5621 if (this.isImageContent() && this.element) { 5622 this.element.onload = null; 5623 this.element.onerror = null; 5624 this.element = undefined; 5625 } 5626 } 5627 /** 5628 * Display error message 5629 */ 5630 5631 5632 displayError() { 5633 if (this.slide) { 5634 var _this$instance$option, _this$instance$option2; 5635 5636 let errorMsgEl = createElement('pswp__error-msg', 'div'); 5637 errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : ''; 5638 errorMsgEl = 5639 /** @type {HTMLDivElement} */ 5640 this.instance.applyFilters('contentErrorElement', errorMsgEl, this); 5641 this.element = createElement('pswp__content pswp__error-msg-container', 'div'); 5642 this.element.appendChild(errorMsgEl); 5643 this.slide.container.innerText = ''; 5644 this.slide.container.appendChild(this.element); 5645 this.slide.updateContentSize(true); 5646 this.removePlaceholder(); 5647 } 5648 } 5649 /** 5650 * Append the content 5651 */ 5652 5653 5654 append() { 5655 if (this.isAttached || !this.element) { 5656 return; 5657 } 5658 5659 this.isAttached = true; 5660 5661 if (this.state === LOAD_STATE.ERROR) { 5662 this.displayError(); 5663 return; 5664 } 5665 5666 if (this.instance.dispatch('contentAppend', { 5667 content: this 5668 }).defaultPrevented) { 5669 return; 5670 } 5671 5672 const supportsDecode = ('decode' in this.element); 5673 5674 if (this.isImageContent()) { 5675 // Use decode() on nearby slides 5676 // 5677 // Nearby slide images are in DOM and not hidden via display:none. 5678 // However, they are placed offscreen (to the left and right side). 5679 // 5680 // Some browsers do not composite the image until it's actually visible, 5681 // using decode() helps. 5682 // 5683 // You might ask "why dont you just decode() and then append all images", 5684 // that's because I want to show image before it's fully loaded, 5685 // as browser can render parts of image while it is loading. 5686 // We do not do this in Safari due to partial loading bug. 5687 if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) { 5688 this.isDecoding = true; // purposefully using finally instead of then, 5689 // as if srcset sizes changes dynamically - it may cause decode error 5690 5691 /** @type {HTMLImageElement} */ 5692 5693 this.element.decode().catch(() => {}).finally(() => { 5694 this.isDecoding = false; 5695 this.appendImage(); 5696 }); 5697 } else { 5698 this.appendImage(); 5699 } 5700 } else if (this.slide && !this.element.parentNode) { 5701 this.slide.container.appendChild(this.element); 5702 } 5703 } 5704 /** 5705 * Activate the slide, 5706 * active slide is generally the current one, 5707 * meaning the user can see it. 5708 */ 5709 5710 5711 activate() { 5712 if (this.instance.dispatch('contentActivate', { 5713 content: this 5714 }).defaultPrevented || !this.slide) { 5715 return; 5716 } 5717 5718 if (this.isImageContent() && this.isDecoding && !isSafari()) { 5719 // add image to slide when it becomes active, 5720 // even if it's not finished decoding 5721 this.appendImage(); 5722 } else if (this.isError()) { 5723 this.load(false, true); // try to reload 5724 } 5725 5726 if (this.slide.holderElement) { 5727 this.slide.holderElement.setAttribute('aria-hidden', 'false'); 5728 } 5729 } 5730 /** 5731 * Deactivate the content 5732 */ 5733 5734 5735 deactivate() { 5736 this.instance.dispatch('contentDeactivate', { 5737 content: this 5738 }); 5739 5740 if (this.slide && this.slide.holderElement) { 5741 this.slide.holderElement.setAttribute('aria-hidden', 'true'); 5742 } 5743 } 5744 /** 5745 * Remove the content from DOM 5746 */ 5747 5748 5749 remove() { 5750 this.isAttached = false; 5751 5752 if (this.instance.dispatch('contentRemove', { 5753 content: this 5754 }).defaultPrevented) { 5755 return; 5756 } 5757 5758 if (this.element && this.element.parentNode) { 5759 this.element.remove(); 5760 } 5761 5762 if (this.placeholder && this.placeholder.element) { 5763 this.placeholder.element.remove(); 5764 } 5765 } 5766 /** 5767 * Append the image content to slide container 5768 */ 5769 5770 5771 appendImage() { 5772 if (!this.isAttached) { 5773 return; 5774 } 5775 5776 if (this.instance.dispatch('contentAppendImage', { 5777 content: this 5778 }).defaultPrevented) { 5779 return; 5780 } // ensure that element exists and is not already appended 5781 5782 5783 if (this.slide && this.element && !this.element.parentNode) { 5784 this.slide.container.appendChild(this.element); 5785 } 5786 5787 if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) { 5788 this.removePlaceholder(); 5789 } 5790 } 5791 5792} 5793 5794/** @typedef {import('./content.js').default} Content */ 5795 5796/** @typedef {import('./slide.js').default} Slide */ 5797 5798/** @typedef {import('./slide.js').SlideData} SlideData */ 5799 5800/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ 5801 5802/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 5803 5804const MIN_SLIDES_TO_CACHE = 5; 5805/** 5806 * Lazy-load an image 5807 * This function is used both by Lightbox and PhotoSwipe core, 5808 * thus it can be called before dialog is opened. 5809 * 5810 * @param {SlideData} itemData Data about the slide 5811 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance 5812 * @param {number} index 5813 * @returns {Content} Image that is being decoded or false. 5814 */ 5815 5816function lazyLoadData(itemData, instance, index) { 5817 const content = instance.createContentFromData(itemData, index); 5818 /** @type {ZoomLevel | undefined} */ 5819 5820 let zoomLevel; 5821 const { 5822 options 5823 } = instance; // We need to know dimensions of the image to preload it, 5824 // as it might use srcset, and we need to define sizes 5825 5826 if (options) { 5827 zoomLevel = new ZoomLevel(options, itemData, -1); 5828 let viewportSize; 5829 5830 if (instance.pswp) { 5831 viewportSize = instance.pswp.viewportSize; 5832 } else { 5833 viewportSize = getViewportSize(options, instance); 5834 } 5835 5836 const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index); 5837 zoomLevel.update(content.width, content.height, panAreaSize); 5838 } 5839 5840 content.lazyLoad(); 5841 5842 if (zoomLevel) { 5843 content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial)); 5844 } 5845 5846 return content; 5847} 5848/** 5849 * Lazy-loads specific slide. 5850 * This function is used both by Lightbox and PhotoSwipe core, 5851 * thus it can be called before dialog is opened. 5852 * 5853 * By default, it loads image based on viewport size and initial zoom level. 5854 * 5855 * @param {number} index Slide index 5856 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance 5857 * @returns {Content | undefined} 5858 */ 5859 5860function lazyLoadSlide(index, instance) { 5861 const itemData = instance.getItemData(index); 5862 5863 if (instance.dispatch('lazyLoadSlide', { 5864 index, 5865 itemData 5866 }).defaultPrevented) { 5867 return; 5868 } 5869 5870 return lazyLoadData(itemData, instance, index); 5871} 5872 5873class ContentLoader { 5874 /** 5875 * @param {PhotoSwipe} pswp 5876 */ 5877 constructor(pswp) { 5878 this.pswp = pswp; // Total amount of cached images 5879 5880 this.limit = Math.max(pswp.options.preload[0] + pswp.options.preload[1] + 1, MIN_SLIDES_TO_CACHE); 5881 /** @type {Content[]} */ 5882 5883 this._cachedItems = []; 5884 } 5885 /** 5886 * Lazy load nearby slides based on `preload` option. 5887 * 5888 * @param {number} [diff] Difference between slide indexes that was changed recently, or 0. 5889 */ 5890 5891 5892 updateLazy(diff) { 5893 const { 5894 pswp 5895 } = this; 5896 5897 if (pswp.dispatch('lazyLoad').defaultPrevented) { 5898 return; 5899 } 5900 5901 const { 5902 preload 5903 } = pswp.options; 5904 const isForward = diff === undefined ? true : diff >= 0; 5905 let i; // preload[1] - num items to preload in forward direction 5906 5907 for (i = 0; i <= preload[1]; i++) { 5908 this.loadSlideByIndex(pswp.currIndex + (isForward ? i : -i)); 5909 } // preload[0] - num items to preload in backward direction 5910 5911 5912 for (i = 1; i <= preload[0]; i++) { 5913 this.loadSlideByIndex(pswp.currIndex + (isForward ? -i : i)); 5914 } 5915 } 5916 /** 5917 * @param {number} initialIndex 5918 */ 5919 5920 5921 loadSlideByIndex(initialIndex) { 5922 const index = this.pswp.getLoopedIndex(initialIndex); // try to get cached content 5923 5924 let content = this.getContentByIndex(index); 5925 5926 if (!content) { 5927 // no cached content, so try to load from scratch: 5928 content = lazyLoadSlide(index, this.pswp); // if content can be loaded, add it to cache: 5929 5930 if (content) { 5931 this.addToCache(content); 5932 } 5933 } 5934 } 5935 /** 5936 * @param {Slide} slide 5937 * @returns {Content} 5938 */ 5939 5940 5941 getContentBySlide(slide) { 5942 let content = this.getContentByIndex(slide.index); 5943 5944 if (!content) { 5945 // create content if not found in cache 5946 content = this.pswp.createContentFromData(slide.data, slide.index); 5947 this.addToCache(content); 5948 } // assign slide to content 5949 5950 5951 content.setSlide(slide); 5952 return content; 5953 } 5954 /** 5955 * @param {Content} content 5956 */ 5957 5958 5959 addToCache(content) { 5960 // move to the end of array 5961 this.removeByIndex(content.index); 5962 5963 this._cachedItems.push(content); 5964 5965 if (this._cachedItems.length > this.limit) { 5966 // Destroy the first content that's not attached 5967 const indexToRemove = this._cachedItems.findIndex(item => { 5968 return !item.isAttached && !item.hasSlide; 5969 }); 5970 5971 if (indexToRemove !== -1) { 5972 const removedItem = this._cachedItems.splice(indexToRemove, 1)[0]; 5973 5974 removedItem.destroy(); 5975 } 5976 } 5977 } 5978 /** 5979 * Removes an image from cache, does not destroy() it, just removes. 5980 * 5981 * @param {number} index 5982 */ 5983 5984 5985 removeByIndex(index) { 5986 const indexToRemove = this._cachedItems.findIndex(item => item.index === index); 5987 5988 if (indexToRemove !== -1) { 5989 this._cachedItems.splice(indexToRemove, 1); 5990 } 5991 } 5992 /** 5993 * @param {number} index 5994 * @returns {Content | undefined} 5995 */ 5996 5997 5998 getContentByIndex(index) { 5999 return this._cachedItems.find(content => content.index === index); 6000 } 6001 6002 destroy() { 6003 this._cachedItems.forEach(content => content.destroy()); 6004 6005 this._cachedItems = []; 6006 } 6007 6008} 6009 6010/** @typedef {import("../photoswipe.js").default} PhotoSwipe */ 6011 6012/** @typedef {import("../slide/slide.js").SlideData} SlideData */ 6013 6014/** 6015 * PhotoSwipe base class that can retrieve data about every slide. 6016 * Shared by PhotoSwipe Core and PhotoSwipe Lightbox 6017 */ 6018 6019class PhotoSwipeBase extends Eventable { 6020 /** 6021 * Get total number of slides 6022 * 6023 * @returns {number} 6024 */ 6025 getNumItems() { 6026 var _this$options; 6027 6028 let numItems = 0; 6029 const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource; 6030 6031 if (dataSource && 'length' in dataSource) { 6032 // may be an array or just object with length property 6033 numItems = dataSource.length; 6034 } else if (dataSource && 'gallery' in dataSource) { 6035 // query DOM elements 6036 if (!dataSource.items) { 6037 dataSource.items = this._getGalleryDOMElements(dataSource.gallery); 6038 } 6039 6040 if (dataSource.items) { 6041 numItems = dataSource.items.length; 6042 } 6043 } // legacy event, before filters were introduced 6044 6045 6046 const event = this.dispatch('numItems', { 6047 dataSource, 6048 numItems 6049 }); 6050 return this.applyFilters('numItems', event.numItems, dataSource); 6051 } 6052 /** 6053 * @param {SlideData} slideData 6054 * @param {number} index 6055 * @returns {Content} 6056 */ 6057 6058 6059 createContentFromData(slideData, index) { 6060 return new Content(slideData, this, index); 6061 } 6062 /** 6063 * Get item data by index. 6064 * 6065 * "item data" should contain normalized information that PhotoSwipe needs to generate a slide. 6066 * For example, it may contain properties like 6067 * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image. 6068 * 6069 * @param {number} index 6070 * @returns {SlideData} 6071 */ 6072 6073 6074 getItemData(index) { 6075 var _this$options2; 6076 6077 const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource; 6078 /** @type {SlideData | HTMLElement} */ 6079 6080 let dataSourceItem = {}; 6081 6082 if (Array.isArray(dataSource)) { 6083 // Datasource is an array of elements 6084 dataSourceItem = dataSource[index]; 6085 } else if (dataSource && 'gallery' in dataSource) { 6086 // dataSource has gallery property, 6087 // thus it was created by Lightbox, based on 6088 // gallery and children options 6089 // query DOM elements 6090 if (!dataSource.items) { 6091 dataSource.items = this._getGalleryDOMElements(dataSource.gallery); 6092 } 6093 6094 dataSourceItem = dataSource.items[index]; 6095 } 6096 6097 let itemData = dataSourceItem; 6098 6099 if (itemData instanceof Element) { 6100 itemData = this._domElementToItemData(itemData); 6101 } // Dispatching the itemData event, 6102 // it's a legacy verion before filters were introduced 6103 6104 6105 const event = this.dispatch('itemData', { 6106 itemData: itemData || {}, 6107 index 6108 }); 6109 return this.applyFilters('itemData', event.itemData, index); 6110 } 6111 /** 6112 * Get array of gallery DOM elements, 6113 * based on childSelector and gallery element. 6114 * 6115 * @param {HTMLElement} galleryElement 6116 * @returns {HTMLElement[]} 6117 */ 6118 6119 6120 _getGalleryDOMElements(galleryElement) { 6121 var _this$options3, _this$options4; 6122 6123 if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) { 6124 return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || []; 6125 } 6126 6127 return [galleryElement]; 6128 } 6129 /** 6130 * Converts DOM element to item data object. 6131 * 6132 * @param {HTMLElement} element DOM element 6133 * @returns {SlideData} 6134 */ 6135 6136 6137 _domElementToItemData(element) { 6138 /** @type {SlideData} */ 6139 const itemData = { 6140 element 6141 }; 6142 const linkEl = 6143 /** @type {HTMLAnchorElement} */ 6144 element.tagName === 'A' ? element : element.querySelector('a'); 6145 6146 if (linkEl) { 6147 // src comes from data-pswp-src attribute, 6148 // if it's empty link href is used 6149 itemData.src = linkEl.dataset.pswpSrc || linkEl.href; 6150 6151 if (linkEl.dataset.pswpSrcset) { 6152 itemData.srcset = linkEl.dataset.pswpSrcset; 6153 } 6154 6155 itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0; 6156 itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0; // support legacy w & h properties 6157 6158 itemData.w = itemData.width; 6159 itemData.h = itemData.height; 6160 6161 if (linkEl.dataset.pswpType) { 6162 itemData.type = linkEl.dataset.pswpType; 6163 } 6164 6165 const thumbnailEl = element.querySelector('img'); 6166 6167 if (thumbnailEl) { 6168 var _thumbnailEl$getAttri; 6169 6170 // msrc is URL to placeholder image that's displayed before large image is loaded 6171 // by default it's displayed only for the first slide 6172 itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src; 6173 itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute('alt')) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : ''; 6174 } 6175 6176 if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) { 6177 itemData.thumbCropped = true; 6178 } 6179 } 6180 6181 return this.applyFilters('domItemData', itemData, element, linkEl); 6182 } 6183 /** 6184 * Lazy-load by slide data 6185 * 6186 * @param {SlideData} itemData Data about the slide 6187 * @param {number} index 6188 * @returns {Content} Image that is being decoded or false. 6189 */ 6190 6191 6192 lazyLoadData(itemData, index) { 6193 return lazyLoadData(itemData, this, index); 6194 } 6195 6196} 6197 6198/** @typedef {import('./photoswipe.js').default} PhotoSwipe */ 6199 6200/** @typedef {import('./slide/get-thumb-bounds.js').Bounds} Bounds */ 6201 6202/** @typedef {import('./util/animations.js').AnimationProps} AnimationProps */ 6203// some browsers do not paint 6204// elements which opacity is set to 0, 6205// since we need to pre-render elements for the animation - 6206// we set it to the minimum amount 6207 6208const MIN_OPACITY = 0.003; 6209/** 6210 * Manages opening and closing transitions of the PhotoSwipe. 6211 * 6212 * It can perform zoom, fade or no transition. 6213 */ 6214 6215class Opener { 6216 /** 6217 * @param {PhotoSwipe} pswp 6218 */ 6219 constructor(pswp) { 6220 this.pswp = pswp; 6221 this.isClosed = true; 6222 this.isOpen = false; 6223 this.isClosing = false; 6224 this.isOpening = false; 6225 /** 6226 * @private 6227 * @type {number | false | undefined} 6228 */ 6229 6230 this._duration = undefined; 6231 /** @private */ 6232 6233 this._useAnimation = false; 6234 /** @private */ 6235 6236 this._croppedZoom = false; 6237 /** @private */ 6238 6239 this._animateRootOpacity = false; 6240 /** @private */ 6241 6242 this._animateBgOpacity = false; 6243 /** 6244 * @private 6245 * @type { HTMLDivElement | HTMLImageElement | null | undefined } 6246 */ 6247 6248 this._placeholder = undefined; 6249 /** 6250 * @private 6251 * @type { HTMLDivElement | undefined } 6252 */ 6253 6254 this._opacityElement = undefined; 6255 /** 6256 * @private 6257 * @type { HTMLDivElement | undefined } 6258 */ 6259 6260 this._cropContainer1 = undefined; 6261 /** 6262 * @private 6263 * @type { HTMLElement | null | undefined } 6264 */ 6265 6266 this._cropContainer2 = undefined; 6267 /** 6268 * @private 6269 * @type {Bounds | undefined} 6270 */ 6271 6272 this._thumbBounds = undefined; 6273 this._prepareOpen = this._prepareOpen.bind(this); // Override initial zoom and pan position 6274 6275 pswp.on('firstZoomPan', this._prepareOpen); 6276 } 6277 6278 open() { 6279 this._prepareOpen(); 6280 6281 this._start(); 6282 } 6283 6284 close() { 6285 if (this.isClosed || this.isClosing || this.isOpening) { 6286 // if we close during opening animation 6287 // for now do nothing, 6288 // browsers aren't good at changing the direction of the CSS transition 6289 return; 6290 } 6291 6292 const slide = this.pswp.currSlide; 6293 this.isOpen = false; 6294 this.isOpening = false; 6295 this.isClosing = true; 6296 this._duration = this.pswp.options.hideAnimationDuration; 6297 6298 if (slide && slide.currZoomLevel * slide.width >= this.pswp.options.maxWidthToAnimate) { 6299 this._duration = 0; 6300 } 6301 6302 this._applyStartProps(); 6303 6304 setTimeout(() => { 6305 this._start(); 6306 }, this._croppedZoom ? 30 : 0); 6307 } 6308 /** @private */ 6309 6310 6311 _prepareOpen() { 6312 this.pswp.off('firstZoomPan', this._prepareOpen); 6313 6314 if (!this.isOpening) { 6315 const slide = this.pswp.currSlide; 6316 this.isOpening = true; 6317 this.isClosing = false; 6318 this._duration = this.pswp.options.showAnimationDuration; 6319 6320 if (slide && slide.zoomLevels.initial * slide.width >= this.pswp.options.maxWidthToAnimate) { 6321 this._duration = 0; 6322 } 6323 6324 this._applyStartProps(); 6325 } 6326 } 6327 /** @private */ 6328 6329 6330 _applyStartProps() { 6331 const { 6332 pswp 6333 } = this; 6334 const slide = this.pswp.currSlide; 6335 const { 6336 options 6337 } = pswp; 6338 6339 if (options.showHideAnimationType === 'fade') { 6340 options.showHideOpacity = true; 6341 this._thumbBounds = undefined; 6342 } else if (options.showHideAnimationType === 'none') { 6343 options.showHideOpacity = false; 6344 this._duration = 0; 6345 this._thumbBounds = undefined; 6346 } else if (this.isOpening && pswp._initialThumbBounds) { 6347 // Use initial bounds if defined 6348 this._thumbBounds = pswp._initialThumbBounds; 6349 } else { 6350 this._thumbBounds = this.pswp.getThumbBounds(); 6351 } 6352 6353 this._placeholder = slide === null || slide === void 0 ? void 0 : slide.getPlaceholderElement(); 6354 pswp.animations.stopAll(); // Discard animations when duration is less than 50ms 6355 6356 this._useAnimation = Boolean(this._duration && this._duration > 50); 6357 this._animateZoom = Boolean(this._thumbBounds) && (slide === null || slide === void 0 ? void 0 : slide.content.usePlaceholder()) && (!this.isClosing || !pswp.mainScroll.isShifted()); 6358 6359 if (!this._animateZoom) { 6360 this._animateRootOpacity = true; 6361 6362 if (this.isOpening && slide) { 6363 slide.zoomAndPanToInitial(); 6364 slide.applyCurrentZoomPan(); 6365 } 6366 } else { 6367 var _options$showHideOpac; 6368 6369 this._animateRootOpacity = (_options$showHideOpac = options.showHideOpacity) !== null && _options$showHideOpac !== void 0 ? _options$showHideOpac : false; 6370 } 6371 6372 this._animateBgOpacity = !this._animateRootOpacity && this.pswp.options.bgOpacity > MIN_OPACITY; 6373 this._opacityElement = this._animateRootOpacity ? pswp.element : pswp.bg; 6374 6375 if (!this._useAnimation) { 6376 this._duration = 0; 6377 this._animateZoom = false; 6378 this._animateBgOpacity = false; 6379 this._animateRootOpacity = true; 6380 6381 if (this.isOpening) { 6382 if (pswp.element) { 6383 pswp.element.style.opacity = String(MIN_OPACITY); 6384 } 6385 6386 pswp.applyBgOpacity(1); 6387 } 6388 6389 return; 6390 } 6391 6392 if (this._animateZoom && this._thumbBounds && this._thumbBounds.innerRect) { 6393 var _this$pswp$currSlide; 6394 6395 // Properties are used when animation from cropped thumbnail 6396 this._croppedZoom = true; 6397 this._cropContainer1 = this.pswp.container; 6398 this._cropContainer2 = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.holderElement; 6399 6400 if (pswp.container) { 6401 pswp.container.style.overflow = 'hidden'; 6402 pswp.container.style.width = pswp.viewportSize.x + 'px'; 6403 } 6404 } else { 6405 this._croppedZoom = false; 6406 } 6407 6408 if (this.isOpening) { 6409 // Apply styles before opening transition 6410 if (this._animateRootOpacity) { 6411 if (pswp.element) { 6412 pswp.element.style.opacity = String(MIN_OPACITY); 6413 } 6414 6415 pswp.applyBgOpacity(1); 6416 } else { 6417 if (this._animateBgOpacity && pswp.bg) { 6418 pswp.bg.style.opacity = String(MIN_OPACITY); 6419 } 6420 6421 if (pswp.element) { 6422 pswp.element.style.opacity = '1'; 6423 } 6424 } 6425 6426 if (this._animateZoom) { 6427 this._setClosedStateZoomPan(); 6428 6429 if (this._placeholder) { 6430 // tell browser that we plan to animate the placeholder 6431 this._placeholder.style.willChange = 'transform'; // hide placeholder to allow hiding of 6432 // elements that overlap it (such as icons over the thumbnail) 6433 6434 this._placeholder.style.opacity = String(MIN_OPACITY); 6435 } 6436 } 6437 } else if (this.isClosing) { 6438 // hide nearby slides to make sure that 6439 // they are not painted during the transition 6440 if (pswp.mainScroll.itemHolders[0]) { 6441 pswp.mainScroll.itemHolders[0].el.style.display = 'none'; 6442 } 6443 6444 if (pswp.mainScroll.itemHolders[2]) { 6445 pswp.mainScroll.itemHolders[2].el.style.display = 'none'; 6446 } 6447 6448 if (this._croppedZoom) { 6449 if (pswp.mainScroll.x !== 0) { 6450 // shift the main scroller to zero position 6451 pswp.mainScroll.resetPosition(); 6452 pswp.mainScroll.resize(); 6453 } 6454 } 6455 } 6456 } 6457 /** @private */ 6458 6459 6460 _start() { 6461 if (this.isOpening && this._useAnimation && this._placeholder && this._placeholder.tagName === 'IMG') { 6462 // To ensure smooth animation 6463 // we wait till the current slide image placeholder is decoded, 6464 // but no longer than 250ms, 6465 // and no shorter than 50ms 6466 // (just using requestanimationframe is not enough in Firefox, 6467 // for some reason) 6468 new Promise(resolve => { 6469 let decoded = false; 6470 let isDelaying = true; 6471 decodeImage( 6472 /** @type {HTMLImageElement} */ 6473 this._placeholder).finally(() => { 6474 decoded = true; 6475 6476 if (!isDelaying) { 6477 resolve(true); 6478 } 6479 }); 6480 setTimeout(() => { 6481 isDelaying = false; 6482 6483 if (decoded) { 6484 resolve(true); 6485 } 6486 }, 50); 6487 setTimeout(resolve, 250); 6488 }).finally(() => this._initiate()); 6489 } else { 6490 this._initiate(); 6491 } 6492 } 6493 /** @private */ 6494 6495 6496 _initiate() { 6497 var _this$pswp$element, _this$pswp$element2; 6498 6499 (_this$pswp$element = this.pswp.element) === null || _this$pswp$element === void 0 || _this$pswp$element.style.setProperty('--pswp-transition-duration', this._duration + 'ms'); 6500 this.pswp.dispatch(this.isOpening ? 'openingAnimationStart' : 'closingAnimationStart'); // legacy event 6501 6502 this.pswp.dispatch( 6503 /** @type {'initialZoomIn' | 'initialZoomOut'} */ 6504 'initialZoom' + (this.isOpening ? 'In' : 'Out')); 6505 (_this$pswp$element2 = this.pswp.element) === null || _this$pswp$element2 === void 0 || _this$pswp$element2.classList.toggle('pswp--ui-visible', this.isOpening); 6506 6507 if (this.isOpening) { 6508 if (this._placeholder) { 6509 // unhide the placeholder 6510 this._placeholder.style.opacity = '1'; 6511 } 6512 6513 this._animateToOpenState(); 6514 } else if (this.isClosing) { 6515 this._animateToClosedState(); 6516 } 6517 6518 if (!this._useAnimation) { 6519 this._onAnimationComplete(); 6520 } 6521 } 6522 /** @private */ 6523 6524 6525 _onAnimationComplete() { 6526 const { 6527 pswp 6528 } = this; 6529 this.isOpen = this.isOpening; 6530 this.isClosed = this.isClosing; 6531 this.isOpening = false; 6532 this.isClosing = false; 6533 pswp.dispatch(this.isOpen ? 'openingAnimationEnd' : 'closingAnimationEnd'); // legacy event 6534 6535 pswp.dispatch( 6536 /** @type {'initialZoomInEnd' | 'initialZoomOutEnd'} */ 6537 'initialZoom' + (this.isOpen ? 'InEnd' : 'OutEnd')); 6538 6539 if (this.isClosed) { 6540 pswp.destroy(); 6541 } else if (this.isOpen) { 6542 var _pswp$currSlide; 6543 6544 if (this._animateZoom && pswp.container) { 6545 pswp.container.style.overflow = 'visible'; 6546 pswp.container.style.width = '100%'; 6547 } 6548 6549 (_pswp$currSlide = pswp.currSlide) === null || _pswp$currSlide === void 0 || _pswp$currSlide.applyCurrentZoomPan(); 6550 } 6551 } 6552 /** @private */ 6553 6554 6555 _animateToOpenState() { 6556 const { 6557 pswp 6558 } = this; 6559 6560 if (this._animateZoom) { 6561 if (this._croppedZoom && this._cropContainer1 && this._cropContainer2) { 6562 this._animateTo(this._cropContainer1, 'transform', 'translate3d(0,0,0)'); 6563 6564 this._animateTo(this._cropContainer2, 'transform', 'none'); 6565 } 6566 6567 if (pswp.currSlide) { 6568 pswp.currSlide.zoomAndPanToInitial(); 6569 6570 this._animateTo(pswp.currSlide.container, 'transform', pswp.currSlide.getCurrentTransform()); 6571 } 6572 } 6573 6574 if (this._animateBgOpacity && pswp.bg) { 6575 this._animateTo(pswp.bg, 'opacity', String(pswp.options.bgOpacity)); 6576 } 6577 6578 if (this._animateRootOpacity && pswp.element) { 6579 this._animateTo(pswp.element, 'opacity', '1'); 6580 } 6581 } 6582 /** @private */ 6583 6584 6585 _animateToClosedState() { 6586 const { 6587 pswp 6588 } = this; 6589 6590 if (this._animateZoom) { 6591 this._setClosedStateZoomPan(true); 6592 } // do not animate opacity if it's already at 0 6593 6594 6595 if (this._animateBgOpacity && pswp.bgOpacity > 0.01 && pswp.bg) { 6596 this._animateTo(pswp.bg, 'opacity', '0'); 6597 } 6598 6599 if (this._animateRootOpacity && pswp.element) { 6600 this._animateTo(pswp.element, 'opacity', '0'); 6601 } 6602 } 6603 /** 6604 * @private 6605 * @param {boolean} [animate] 6606 */ 6607 6608 6609 _setClosedStateZoomPan(animate) { 6610 if (!this._thumbBounds) return; 6611 const { 6612 pswp 6613 } = this; 6614 const { 6615 innerRect 6616 } = this._thumbBounds; 6617 const { 6618 currSlide, 6619 viewportSize 6620 } = pswp; 6621 6622 if (this._croppedZoom && innerRect && this._cropContainer1 && this._cropContainer2) { 6623 const containerOnePanX = -viewportSize.x + (this._thumbBounds.x - innerRect.x) + innerRect.w; 6624 const containerOnePanY = -viewportSize.y + (this._thumbBounds.y - innerRect.y) + innerRect.h; 6625 const containerTwoPanX = viewportSize.x - innerRect.w; 6626 const containerTwoPanY = viewportSize.y - innerRect.h; 6627 6628 if (animate) { 6629 this._animateTo(this._cropContainer1, 'transform', toTransformString(containerOnePanX, containerOnePanY)); 6630 6631 this._animateTo(this._cropContainer2, 'transform', toTransformString(containerTwoPanX, containerTwoPanY)); 6632 } else { 6633 setTransform(this._cropContainer1, containerOnePanX, containerOnePanY); 6634 setTransform(this._cropContainer2, containerTwoPanX, containerTwoPanY); 6635 } 6636 } 6637 6638 if (currSlide) { 6639 equalizePoints(currSlide.pan, innerRect || this._thumbBounds); 6640 currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width; 6641 6642 if (animate) { 6643 this._animateTo(currSlide.container, 'transform', currSlide.getCurrentTransform()); 6644 } else { 6645 currSlide.applyCurrentZoomPan(); 6646 } 6647 } 6648 } 6649 /** 6650 * @private 6651 * @param {HTMLElement} target 6652 * @param {'transform' | 'opacity'} prop 6653 * @param {string} propValue 6654 */ 6655 6656 6657 _animateTo(target, prop, propValue) { 6658 if (!this._duration) { 6659 target.style[prop] = propValue; 6660 return; 6661 } 6662 6663 const { 6664 animations 6665 } = this.pswp; 6666 /** @type {AnimationProps} */ 6667 6668 const animProps = { 6669 duration: this._duration, 6670 easing: this.pswp.options.easing, 6671 onComplete: () => { 6672 if (!animations.activeAnimations.length) { 6673 this._onAnimationComplete(); 6674 } 6675 }, 6676 target 6677 }; 6678 animProps[prop] = propValue; 6679 animations.startTransition(animProps); 6680 } 6681 6682} 6683 6684/** 6685 * @template T 6686 * @typedef {import('./types.js').Type<T>} Type<T> 6687 */ 6688 6689/** @typedef {import('./slide/slide.js').SlideData} SlideData */ 6690 6691/** @typedef {import('./slide/zoom-level.js').ZoomLevelOption} ZoomLevelOption */ 6692 6693/** @typedef {import('./ui/ui-element.js').UIElementData} UIElementData */ 6694 6695/** @typedef {import('./main-scroll.js').ItemHolder} ItemHolder */ 6696 6697/** @typedef {import('./core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */ 6698 6699/** @typedef {import('./core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */ 6700 6701/** @typedef {import('./slide/get-thumb-bounds').Bounds} Bounds */ 6702 6703/** 6704 * @template {keyof PhotoSwipeEventsMap} T 6705 * @typedef {import('./core/eventable.js').EventCallback<T>} EventCallback<T> 6706 */ 6707 6708/** 6709 * @template {keyof PhotoSwipeEventsMap} T 6710 * @typedef {import('./core/eventable.js').AugmentedEvent<T>} AugmentedEvent<T> 6711 */ 6712 6713/** @typedef {{ x: number; y: number; id?: string | number }} Point */ 6714 6715/** @typedef {{ top: number; bottom: number; left: number; right: number }} Padding */ 6716 6717/** @typedef {SlideData[]} DataSourceArray */ 6718 6719/** @typedef {{ gallery: HTMLElement; items?: HTMLElement[] }} DataSourceObject */ 6720 6721/** @typedef {DataSourceArray | DataSourceObject} DataSource */ 6722 6723/** @typedef {(point: Point, originalEvent: PointerEvent) => void} ActionFn */ 6724 6725/** @typedef {'close' | 'next' | 'zoom' | 'zoom-or-close' | 'toggle-controls'} ActionType */ 6726 6727/** @typedef {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} PhotoSwipeModule */ 6728 6729/** @typedef {PhotoSwipeModule | Promise<PhotoSwipeModule> | (() => Promise<PhotoSwipeModule>)} PhotoSwipeModuleOption */ 6730 6731/** 6732 * @typedef {string | NodeListOf<HTMLElement> | HTMLElement[] | HTMLElement} ElementProvider 6733 */ 6734 6735/** @typedef {Partial<PreparedPhotoSwipeOptions>} PhotoSwipeOptions https://photoswipe.com/options/ */ 6736 6737/** 6738 * @typedef {Object} PreparedPhotoSwipeOptions 6739 * 6740 * @prop {DataSource} [dataSource] 6741 * Pass an array of any items via dataSource option. Its length will determine amount of slides 6742 * (which may be modified further from numItems event). 6743 * 6744 * Each item should contain data that you need to generate slide 6745 * (for image slide it would be src (image URL), width (image width), height, srcset, alt). 6746 * 6747 * If these properties are not present in your initial array, you may "pre-parse" each item from itemData filter. 6748 * 6749 * @prop {number} bgOpacity 6750 * Background backdrop opacity, always define it via this option and not via CSS rgba color. 6751 * 6752 * @prop {number} spacing 6753 * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport). 6754 * 6755 * @prop {boolean} allowPanToNext 6756 * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events. 6757 * 6758 * @prop {boolean} loop 6759 * If set to true you'll be able to swipe from the last to the first image. 6760 * Option is always false when there are less than 3 slides. 6761 * 6762 * @prop {boolean} [wheelToZoom] 6763 * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel. 6764 * 6765 * @prop {boolean} pinchToClose 6766 * Pinch touch gesture to close the gallery. 6767 * 6768 * @prop {boolean} closeOnVerticalDrag 6769 * Vertical drag gesture to close the PhotoSwipe. 6770 * 6771 * @prop {Padding} [padding] 6772 * Slide area padding (in pixels). 6773 * 6774 * @prop {(viewportSize: Point, itemData: SlideData, index: number) => Padding} [paddingFn] 6775 * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example: 6776 * 6777 * @prop {number | false} hideAnimationDuration 6778 * Transition duration in milliseconds, can be 0. 6779 * 6780 * @prop {number | false} showAnimationDuration 6781 * Transition duration in milliseconds, can be 0. 6782 * 6783 * @prop {number | false} zoomAnimationDuration 6784 * Transition duration in milliseconds, can be 0. 6785 * 6786 * @prop {string} easing 6787 * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions. 6788 * 6789 * @prop {boolean} escKey 6790 * Esc key to close. 6791 * 6792 * @prop {boolean} arrowKeys 6793 * Left/right arrow keys for navigation. 6794 * 6795 * @prop {boolean} trapFocus 6796 * Trap focus within PhotoSwipe element while it's open. 6797 * 6798 * @prop {boolean} returnFocus 6799 * Restore focus the last active element after PhotoSwipe is closed. 6800 * 6801 * @prop {boolean} clickToCloseNonZoomable 6802 * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it. 6803 * 6804 * @prop {ActionType | ActionFn | false} imageClickAction 6805 * Refer to click and tap actions page. 6806 * 6807 * @prop {ActionType | ActionFn | false} bgClickAction 6808 * Refer to click and tap actions page. 6809 * 6810 * @prop {ActionType | ActionFn | false} tapAction 6811 * Refer to click and tap actions page. 6812 * 6813 * @prop {ActionType | ActionFn | false} doubleTapAction 6814 * Refer to click and tap actions page. 6815 * 6816 * @prop {number} preloaderDelay 6817 * Delay before the loading indicator will be displayed, 6818 * if image is loaded during it - the indicator will not be displayed at all. Can be zero. 6819 * 6820 * @prop {string} indexIndicatorSep 6821 * Used for slide count indicator ("1 of 10 "). 6822 * 6823 * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipeBase) => Point} [getViewportSizeFn] 6824 * A function that should return slide viewport width and height, in format {x: 100, y: 100}. 6825 * 6826 * @prop {string} errorMsg 6827 * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter. 6828 * 6829 * @prop {[number, number]} preload 6830 * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers, 6831 * first one - number of items to preload before the current image, second one - after the current image. 6832 * Two nearby images are always loaded. 6833 * 6834 * @prop {string} [mainClass] 6835 * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space. 6836 * Example on Styling page. 6837 * 6838 * @prop {HTMLElement} [appendToEl] 6839 * Element to which PhotoSwipe dialog will be appended when it opens. 6840 * 6841 * @prop {number} maxWidthToAnimate 6842 * Maximum width of image to animate, if initial rendered image width 6843 * is larger than this value - the opening/closing transition will be automatically disabled. 6844 * 6845 * @prop {string} [closeTitle] 6846 * Translating 6847 * 6848 * @prop {string} [zoomTitle] 6849 * Translating 6850 * 6851 * @prop {string} [arrowPrevTitle] 6852 * Translating 6853 * 6854 * @prop {string} [arrowNextTitle] 6855 * Translating 6856 * 6857 * @prop {'zoom' | 'fade' | 'none'} [showHideAnimationType] 6858 * To adjust opening or closing transition type use lightbox option `showHideAnimationType` (`String`). 6859 * It supports three values - `zoom` (default), `fade` (default if there is no thumbnail) and `none`. 6860 * 6861 * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`. 6862 * 6863 * @prop {number} index 6864 * Defines start slide index. 6865 * 6866 * @prop {(e: MouseEvent) => number} [getClickedIndexFn] 6867 * 6868 * @prop {boolean} [arrowPrev] 6869 * @prop {boolean} [arrowNext] 6870 * @prop {boolean} [zoom] 6871 * @prop {boolean} [close] 6872 * @prop {boolean} [counter] 6873 * 6874 * @prop {string} [arrowPrevSVG] 6875 * @prop {string} [arrowNextSVG] 6876 * @prop {string} [zoomSVG] 6877 * @prop {string} [closeSVG] 6878 * @prop {string} [counterSVG] 6879 * 6880 * @prop {string} [arrowPrevTitle] 6881 * @prop {string} [arrowNextTitle] 6882 * @prop {string} [zoomTitle] 6883 * @prop {string} [closeTitle] 6884 * @prop {string} [counterTitle] 6885 * 6886 * @prop {ZoomLevelOption} [initialZoomLevel] 6887 * @prop {ZoomLevelOption} [secondaryZoomLevel] 6888 * @prop {ZoomLevelOption} [maxZoomLevel] 6889 * 6890 * @prop {boolean} [mouseMovePan] 6891 * @prop {Point | null} [initialPointerPos] 6892 * @prop {boolean} [showHideOpacity] 6893 * 6894 * @prop {PhotoSwipeModuleOption} [pswpModule] 6895 * @prop {() => Promise<any>} [openPromise] 6896 * @prop {boolean} [preloadFirstSlide] 6897 * @prop {ElementProvider} [gallery] 6898 * @prop {string} [gallerySelector] 6899 * @prop {ElementProvider} [children] 6900 * @prop {string} [childSelector] 6901 * @prop {string | false} [thumbSelector] 6902 */ 6903 6904/** @type {PreparedPhotoSwipeOptions} */ 6905 6906const defaultOptions = { 6907 allowPanToNext: true, 6908 spacing: 0.1, 6909 loop: true, 6910 pinchToClose: true, 6911 closeOnVerticalDrag: true, 6912 hideAnimationDuration: 333, 6913 showAnimationDuration: 333, 6914 zoomAnimationDuration: 333, 6915 escKey: true, 6916 arrowKeys: true, 6917 trapFocus: true, 6918 returnFocus: true, 6919 maxWidthToAnimate: 4000, 6920 clickToCloseNonZoomable: true, 6921 imageClickAction: 'zoom-or-close', 6922 bgClickAction: 'close', 6923 tapAction: 'toggle-controls', 6924 doubleTapAction: 'zoom', 6925 indexIndicatorSep: ' / ', 6926 preloaderDelay: 2000, 6927 bgOpacity: 0.8, 6928 index: 0, 6929 errorMsg: 'The image cannot be loaded', 6930 preload: [1, 2], 6931 easing: 'cubic-bezier(.4,0,.22,1)' 6932}; 6933/** 6934 * PhotoSwipe Core 6935 */ 6936 6937class PhotoSwipe extends PhotoSwipeBase { 6938 /** 6939 * @param {PhotoSwipeOptions} [options] 6940 */ 6941 constructor(options) { 6942 super(); 6943 this.options = this._prepareOptions(options || {}); 6944 /** 6945 * offset of viewport relative to document 6946 * 6947 * @type {Point} 6948 */ 6949 6950 this.offset = { 6951 x: 0, 6952 y: 0 6953 }; 6954 /** 6955 * @type {Point} 6956 * @private 6957 */ 6958 6959 this._prevViewportSize = { 6960 x: 0, 6961 y: 0 6962 }; 6963 /** 6964 * Size of scrollable PhotoSwipe viewport 6965 * 6966 * @type {Point} 6967 */ 6968 6969 this.viewportSize = { 6970 x: 0, 6971 y: 0 6972 }; 6973 /** 6974 * background (backdrop) opacity 6975 */ 6976 6977 this.bgOpacity = 1; 6978 this.currIndex = 0; 6979 this.potentialIndex = 0; 6980 this.isOpen = false; 6981 this.isDestroying = false; 6982 this.hasMouse = false; 6983 /** 6984 * @private 6985 * @type {SlideData} 6986 */ 6987 6988 this._initialItemData = {}; 6989 /** @type {Bounds | undefined} */ 6990 6991 this._initialThumbBounds = undefined; 6992 /** @type {HTMLDivElement | undefined} */ 6993 6994 this.topBar = undefined; 6995 /** @type {HTMLDivElement | undefined} */ 6996 6997 this.element = undefined; 6998 /** @type {HTMLDivElement | undefined} */ 6999 7000 this.template = undefined; 7001 /** @type {HTMLDivElement | undefined} */ 7002 7003 this.container = undefined; 7004 /** @type {HTMLElement | undefined} */ 7005 7006 this.scrollWrap = undefined; 7007 /** @type {Slide | undefined} */ 7008 7009 this.currSlide = undefined; 7010 this.events = new DOMEvents(); 7011 this.animations = new Animations(); 7012 this.mainScroll = new MainScroll(this); 7013 this.gestures = new Gestures(this); 7014 this.opener = new Opener(this); 7015 this.keyboard = new Keyboard(this); 7016 this.contentLoader = new ContentLoader(this); 7017 } 7018 /** @returns {boolean} */ 7019 7020 7021 init() { 7022 if (this.isOpen || this.isDestroying) { 7023 return false; 7024 } 7025 7026 this.isOpen = true; 7027 this.dispatch('init'); // legacy 7028 7029 this.dispatch('beforeOpen'); 7030 7031 this._createMainStructure(); // add classes to the root element of PhotoSwipe 7032 7033 7034 let rootClasses = 'pswp--open'; 7035 7036 if (this.gestures.supportsTouch) { 7037 rootClasses += ' pswp--touch'; 7038 } 7039 7040 if (this.options.mainClass) { 7041 rootClasses += ' ' + this.options.mainClass; 7042 } 7043 7044 if (this.element) { 7045 this.element.className += ' ' + rootClasses; 7046 } 7047 7048 this.currIndex = this.options.index || 0; 7049 this.potentialIndex = this.currIndex; 7050 this.dispatch('firstUpdate'); // starting index can be modified here 7051 // initialize scroll wheel handler to block the scroll 7052 7053 this.scrollWheel = new ScrollWheel(this); // sanitize index 7054 7055 if (Number.isNaN(this.currIndex) || this.currIndex < 0 || this.currIndex >= this.getNumItems()) { 7056 this.currIndex = 0; 7057 } 7058 7059 if (!this.gestures.supportsTouch) { 7060 // enable mouse features if no touch support detected 7061 this.mouseDetected(); 7062 } // causes forced synchronous layout 7063 7064 7065 this.updateSize(); 7066 this.offset.y = window.pageYOffset; 7067 this._initialItemData = this.getItemData(this.currIndex); 7068 this.dispatch('gettingData', { 7069 index: this.currIndex, 7070 data: this._initialItemData, 7071 slide: undefined 7072 }); // *Layout* - calculate size and position of elements here 7073 7074 this._initialThumbBounds = this.getThumbBounds(); 7075 this.dispatch('initialLayout'); 7076 this.on('openingAnimationEnd', () => { 7077 const { 7078 itemHolders 7079 } = this.mainScroll; // Add content to the previous and next slide 7080 7081 if (itemHolders[0]) { 7082 itemHolders[0].el.style.display = 'block'; 7083 this.setContent(itemHolders[0], this.currIndex - 1); 7084 } 7085 7086 if (itemHolders[2]) { 7087 itemHolders[2].el.style.display = 'block'; 7088 this.setContent(itemHolders[2], this.currIndex + 1); 7089 } 7090 7091 this.appendHeavy(); 7092 this.contentLoader.updateLazy(); 7093 this.events.add(window, 'resize', this._handlePageResize.bind(this)); 7094 this.events.add(window, 'scroll', this._updatePageScrollOffset.bind(this)); 7095 this.dispatch('bindEvents'); 7096 }); // set content for center slide (first time) 7097 7098 if (this.mainScroll.itemHolders[1]) { 7099 this.setContent(this.mainScroll.itemHolders[1], this.currIndex); 7100 } 7101 7102 this.dispatch('change'); 7103 this.opener.open(); 7104 this.dispatch('afterInit'); 7105 return true; 7106 } 7107 /** 7108 * Get looped slide index 7109 * (for example, -1 will return the last slide) 7110 * 7111 * @param {number} index 7112 * @returns {number} 7113 */ 7114 7115 7116 getLoopedIndex(index) { 7117 const numSlides = this.getNumItems(); 7118 7119 if (this.options.loop) { 7120 if (index > numSlides - 1) { 7121 index -= numSlides; 7122 } 7123 7124 if (index < 0) { 7125 index += numSlides; 7126 } 7127 } 7128 7129 return clamp(index, 0, numSlides - 1); 7130 } 7131 7132 appendHeavy() { 7133 this.mainScroll.itemHolders.forEach(itemHolder => { 7134 var _itemHolder$slide; 7135 7136 (_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.appendHeavy(); 7137 }); 7138 } 7139 /** 7140 * Change the slide 7141 * @param {number} index New index 7142 */ 7143 7144 7145 goTo(index) { 7146 this.mainScroll.moveIndexBy(this.getLoopedIndex(index) - this.potentialIndex); 7147 } 7148 /** 7149 * Go to the next slide. 7150 */ 7151 7152 7153 next() { 7154 this.goTo(this.potentialIndex + 1); 7155 } 7156 /** 7157 * Go to the previous slide. 7158 */ 7159 7160 7161 prev() { 7162 this.goTo(this.potentialIndex - 1); 7163 } 7164 /** 7165 * @see slide/slide.js zoomTo 7166 * 7167 * @param {Parameters<Slide['zoomTo']>} args 7168 */ 7169 7170 7171 zoomTo(...args) { 7172 var _this$currSlide; 7173 7174 (_this$currSlide = this.currSlide) === null || _this$currSlide === void 0 || _this$currSlide.zoomTo(...args); 7175 } 7176 /** 7177 * @see slide/slide.js toggleZoom 7178 */ 7179 7180 7181 toggleZoom() { 7182 var _this$currSlide2; 7183 7184 (_this$currSlide2 = this.currSlide) === null || _this$currSlide2 === void 0 || _this$currSlide2.toggleZoom(); 7185 } 7186 /** 7187 * Close the gallery. 7188 * After closing transition ends - destroy it 7189 */ 7190 7191 7192 close() { 7193 if (!this.opener.isOpen || this.isDestroying) { 7194 return; 7195 } 7196 7197 this.isDestroying = true; 7198 this.dispatch('close'); 7199 this.events.removeAll(); 7200 this.opener.close(); 7201 } 7202 /** 7203 * Destroys the gallery: 7204 * - instantly closes the gallery 7205 * - unbinds events, 7206 * - cleans intervals and timeouts 7207 * - removes elements from DOM 7208 */ 7209 7210 7211 destroy() { 7212 var _this$element; 7213 7214 if (!this.isDestroying) { 7215 this.options.showHideAnimationType = 'none'; 7216 this.close(); 7217 return; 7218 } 7219 7220 this.dispatch('destroy'); 7221 this._listeners = {}; 7222 7223 if (this.scrollWrap) { 7224 this.scrollWrap.ontouchmove = null; 7225 this.scrollWrap.ontouchend = null; 7226 } 7227 7228 (_this$element = this.element) === null || _this$element === void 0 || _this$element.remove(); 7229 this.mainScroll.itemHolders.forEach(itemHolder => { 7230 var _itemHolder$slide2; 7231 7232 (_itemHolder$slide2 = itemHolder.slide) === null || _itemHolder$slide2 === void 0 || _itemHolder$slide2.destroy(); 7233 }); 7234 this.contentLoader.destroy(); 7235 this.events.removeAll(); 7236 } 7237 /** 7238 * Refresh/reload content of a slide by its index 7239 * 7240 * @param {number} slideIndex 7241 */ 7242 7243 7244 refreshSlideContent(slideIndex) { 7245 this.contentLoader.removeByIndex(slideIndex); 7246 this.mainScroll.itemHolders.forEach((itemHolder, i) => { 7247 var _this$currSlide$index, _this$currSlide3; 7248 7249 let potentialHolderIndex = ((_this$currSlide$index = (_this$currSlide3 = this.currSlide) === null || _this$currSlide3 === void 0 ? void 0 : _this$currSlide3.index) !== null && _this$currSlide$index !== void 0 ? _this$currSlide$index : 0) - 1 + i; 7250 7251 if (this.canLoop()) { 7252 potentialHolderIndex = this.getLoopedIndex(potentialHolderIndex); 7253 } 7254 7255 if (potentialHolderIndex === slideIndex) { 7256 // set the new slide content 7257 this.setContent(itemHolder, slideIndex, true); // activate the new slide if it's current 7258 7259 if (i === 1) { 7260 var _itemHolder$slide3; 7261 7262 this.currSlide = itemHolder.slide; 7263 (_itemHolder$slide3 = itemHolder.slide) === null || _itemHolder$slide3 === void 0 || _itemHolder$slide3.setIsActive(true); 7264 } 7265 } 7266 }); 7267 this.dispatch('change'); 7268 } 7269 /** 7270 * Set slide content 7271 * 7272 * @param {ItemHolder} holder mainScroll.itemHolders array item 7273 * @param {number} index Slide index 7274 * @param {boolean} [force] If content should be set even if index wasn't changed 7275 */ 7276 7277 7278 setContent(holder, index, force) { 7279 if (this.canLoop()) { 7280 index = this.getLoopedIndex(index); 7281 } 7282 7283 if (holder.slide) { 7284 if (holder.slide.index === index && !force) { 7285 // exit if holder already contains this slide 7286 // this could be common when just three slides are used 7287 return; 7288 } // destroy previous slide 7289 7290 7291 holder.slide.destroy(); 7292 holder.slide = undefined; 7293 } // exit if no loop and index is out of bounds 7294 7295 7296 if (!this.canLoop() && (index < 0 || index >= this.getNumItems())) { 7297 return; 7298 } 7299 7300 const itemData = this.getItemData(index); 7301 holder.slide = new Slide(itemData, index, this); // set current slide 7302 7303 if (index === this.currIndex) { 7304 this.currSlide = holder.slide; 7305 } 7306 7307 holder.slide.append(holder.el); 7308 } 7309 /** @returns {Point} */ 7310 7311 7312 getViewportCenterPoint() { 7313 return { 7314 x: this.viewportSize.x / 2, 7315 y: this.viewportSize.y / 2 7316 }; 7317 } 7318 /** 7319 * Update size of all elements. 7320 * Executed on init and on page resize. 7321 * 7322 * @param {boolean} [force] Update size even if size of viewport was not changed. 7323 */ 7324 7325 7326 updateSize(force) { 7327 // let item; 7328 // let itemIndex; 7329 if (this.isDestroying) { 7330 // exit if PhotoSwipe is closed or closing 7331 // (to avoid errors, as resize event might be delayed) 7332 return; 7333 } //const newWidth = this.scrollWrap.clientWidth; 7334 //const newHeight = this.scrollWrap.clientHeight; 7335 7336 7337 const newViewportSize = getViewportSize(this.options, this); 7338 7339 if (!force && pointsEqual(newViewportSize, this._prevViewportSize)) { 7340 // Exit if dimensions were not changed 7341 return; 7342 } //this._prevViewportSize.x = newWidth; 7343 //this._prevViewportSize.y = newHeight; 7344 7345 7346 equalizePoints(this._prevViewportSize, newViewportSize); 7347 this.dispatch('beforeResize'); 7348 equalizePoints(this.viewportSize, this._prevViewportSize); 7349 7350 this._updatePageScrollOffset(); 7351 7352 this.dispatch('viewportSize'); // Resize slides only after opener animation is finished 7353 // and don't re-calculate size on inital size update 7354 7355 this.mainScroll.resize(this.opener.isOpen); 7356 7357 if (!this.hasMouse && window.matchMedia('(any-hover: hover)').matches) { 7358 this.mouseDetected(); 7359 } 7360 7361 this.dispatch('resize'); 7362 } 7363 /** 7364 * @param {number} opacity 7365 */ 7366 7367 7368 applyBgOpacity(opacity) { 7369 this.bgOpacity = Math.max(opacity, 0); 7370 7371 if (this.bg) { 7372 this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity); 7373 } 7374 } 7375 /** 7376 * Whether mouse is detected 7377 */ 7378 7379 7380 mouseDetected() { 7381 if (!this.hasMouse) { 7382 var _this$element2; 7383 7384 this.hasMouse = true; 7385 (_this$element2 = this.element) === null || _this$element2 === void 0 || _this$element2.classList.add('pswp--has_mouse'); 7386 } 7387 } 7388 /** 7389 * Page resize event handler 7390 * 7391 * @private 7392 */ 7393 7394 7395 _handlePageResize() { 7396 this.updateSize(); // In iOS webview, if element size depends on document size, 7397 // it'll be measured incorrectly in resize event 7398 // 7399 // https://bugs.webkit.org/show_bug.cgi?id=170595 7400 // https://hackernoon.com/onresize-event-broken-in-mobile-safari-d8469027bf4d 7401 7402 if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) { 7403 setTimeout(() => { 7404 this.updateSize(); 7405 }, 500); 7406 } 7407 } 7408 /** 7409 * Page scroll offset is used 7410 * to get correct coordinates 7411 * relative to PhotoSwipe viewport. 7412 * 7413 * @private 7414 */ 7415 7416 7417 _updatePageScrollOffset() { 7418 this.setScrollOffset(0, window.pageYOffset); 7419 } 7420 /** 7421 * @param {number} x 7422 * @param {number} y 7423 */ 7424 7425 7426 setScrollOffset(x, y) { 7427 this.offset.x = x; 7428 this.offset.y = y; 7429 this.dispatch('updateScrollOffset'); 7430 } 7431 /** 7432 * Create main HTML structure of PhotoSwipe, 7433 * and add it to DOM 7434 * 7435 * @private 7436 */ 7437 7438 7439 _createMainStructure() { 7440 // root DOM element of PhotoSwipe (.pswp) 7441 this.element = createElement('pswp', 'div'); 7442 this.element.setAttribute('tabindex', '-1'); 7443 this.element.setAttribute('role', 'dialog'); // template is legacy prop 7444 7445 this.template = this.element; // Background is added as a separate element, 7446 // as animating opacity is faster than animating rgba() 7447 7448 this.bg = createElement('pswp__bg', 'div', this.element); 7449 this.scrollWrap = createElement('pswp__scroll-wrap', 'section', this.element); 7450 this.container = createElement('pswp__container', 'div', this.scrollWrap); // aria pattern: carousel 7451 7452 this.scrollWrap.setAttribute('aria-roledescription', 'carousel'); 7453 this.container.setAttribute('aria-live', 'off'); 7454 this.container.setAttribute('id', 'pswp__items'); 7455 this.mainScroll.appendHolders(); 7456 this.ui = new UI(this); 7457 this.ui.init(); // append to DOM 7458 7459 (this.options.appendToEl || document.body).appendChild(this.element); 7460 } 7461 /** 7462 * Get position and dimensions of small thumbnail 7463 * {x:,y:,w:} 7464 * 7465 * Height is optional (calculated based on the large image) 7466 * 7467 * @returns {Bounds | undefined} 7468 */ 7469 7470 7471 getThumbBounds() { 7472 return getThumbBounds(this.currIndex, this.currSlide ? this.currSlide.data : this._initialItemData, this); 7473 } 7474 /** 7475 * If the PhotoSwipe can have continuous loop 7476 * @returns Boolean 7477 */ 7478 7479 7480 canLoop() { 7481 return this.options.loop && this.getNumItems() > 2; 7482 } 7483 /** 7484 * @private 7485 * @param {PhotoSwipeOptions} options 7486 * @returns {PreparedPhotoSwipeOptions} 7487 */ 7488 7489 7490 _prepareOptions(options) { 7491 if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) { 7492 options.showHideAnimationType = 'none'; 7493 options.zoomAnimationDuration = 0; 7494 } 7495 /** @type {PreparedPhotoSwipeOptions} */ 7496 7497 7498 return { ...defaultOptions, 7499 ...options 7500 }; 7501 } 7502 7503} 7504
7505 7506 7507;(() => { 7508// ----- script.js ----- 7509/* empty css */ 7510 7511function initScrollHint() { 7512 new ScrollHint(".js-scrollable", { 7513 suggestiveShadow: true, 7514 i18n: { scrollable: "ã¹ã¯ãã¼ã«ã§ãã¾ã" } 7515 }); 7516} 7517if (document.readyState === "loading") { 7518 document.addEventListener("DOMContentLoaded", initScrollHint); 7519} else { 7520 initScrollHint(); 7521} 7522 7523})(); 7524 7525;(() => { 7526// ----- script10.js ----- 7527document.querySelector(".sdgs-side-navi")?.addEventListener("click", (e) => { 7528 const link = e.target.closest('a[href^="#"]'); 7529 if (!link) return; 7530 const hash = link.getAttribute("href"); 7531 const target = document.querySelector(hash); 7532 if (!target) return; 7533 e.preventDefault(); 7534 target.scrollIntoView({ behavior: "smooth" }); 7535 history.pushState(null, "", hash); 7536 target.focus({ preventScroll: true }); 7537}); 7538 7539})(); 7540 7541;(() => { 7542// ----- script11.js ----- 7543function initTextSlideUp() { 7544 document.querySelectorAll("[data-text-slide-up]").forEach((el) => { 7545 if (el.dataset.textSlideInit) return; 7546 el.dataset.textSlideInit = "true"; 7547 const lines = el.querySelectorAll(".text-slide-up__line"); 7548 lines.forEach((line, index) => { 7549 const html = line.innerHTML; 7550 line.innerHTML = ""; 7551 const inner = document.createElement("span"); 7552 inner.innerHTML = html; 7553 inner.style.transitionDelay = `${index * 0.2}s`; 7554 line.appendChild(inner); 7555 }); 7556 const observer = new IntersectionObserver( 7557 (entries) => { 7558 entries.forEach((entry) => { 7559 if (entry.isIntersecting) { 7560 entry.target.classList.add("is-inview"); 7561 observer.unobserve(entry.target); 7562 } 7563 }); 7564 }, 7565 { rootMargin: "0px", threshold: 0.2 } 7566 ); 7567 observer.observe(el); 7568 }); 7569} 7570if (document.readyState === "loading") {
7571 document.addEventListener("DOMContentLoaded", initTextSlideUp); 7572} else { 7573 initTextSlideUp(); 7574} 7575 7576})(); 7577 7578;(() => { 7579// ----- script12.js ----- 7580function initCharSlideUp() { 7581 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") return; 7582 gsap.registerPlugin(ScrollTrigger); 7583 document.querySelectorAll("[data-char-slide-up]").forEach((el) => { 7584 if (el.dataset.charSlideInit) return; 7585 el.dataset.charSlideInit = "true"; 7586 const isSp = window.innerWidth < 768; 7587 const textSp = el.dataset.textSp; 7588 const text = isSp && textSp || el.dataset.text || (el.textContent || "").replace(/\s+/g, " ").trim(); 7589 if (!text) return; 7590 el.innerHTML = ""; 7591 const chars = []; 7592 for (let i = 0; i < text.length; i++) { 7593 const ch = text[i]; 7594 if (ch === "|") { 7595 const br = document.createElement("span"); 7596 br.className = "char-slide-up__break"; 7597 el.appendChild(br); 7598 } else if (ch === " " || ch === "ã") { 7599 const wrap = document.createElement("span"); 7600 wrap.className = "char-slide-up__char-wrap char-slide-up__char-wrap--space"; 7601 wrap.innerHTML = " "; 7602 el.appendChild(wrap); 7603 } else { 7604 const wrap = document.createElement("span"); 7605 wrap.className = "char-slide-up__char-wrap"; 7606 const inner = document.createElement("span"); 7607 inner.className = "char-slide-up__char-inner"; 7608 inner.textContent = ch; 7609 wrap.appendChild(inner); 7610 el.appendChild(wrap); 7611 chars.push(inner); 7612 } 7613 } 7614 gsap.from(chars, { 7615 y: "100%", 7616 duration: 0.6, 7617 ease: "power3.out", 7618 stagger: 0.04, 7619 scrollTrigger: { 7620 trigger: el, 7621 start: "top 90%", 7622 toggleActions: "play none none none" 7623 } 7624 }); 7625 }); 7626} 7627if (document.readyState === "loading") { 7628 document.addEventListener("DOMContentLoaded", initCharSlideUp); 7629} else { 7630 initCharSlideUp(); 7631} 7632 7633})(); 7634 7635;(() => { 7636// ----- script2.js ----- 7637/* empty css */ 7638 7639document.addEventListener("DOMContentLoaded", () => { 7640 if (window.matchMedia("(max-width: 1023px)").matches) { 7641 new ScrollHint(".js-scrollable", { 7642 suggestiveShadow: false, 7643 i18n: { scrollable: "ã¹ã¯ãã¼ã«ã§ãã¾ã" } 7644 }); 7645 } 7646}); 7647 7648})(); 7649 7650;(() => { 7651// ----- script3.js ----- 7652function initScrollHint() { 7653 new ScrollHint(".js-scrollable", { 7654 suggestiveShadow: false, 7655 i18n: { scrollable: "ã¹ã¯ãã¼ã«ã§ãã¾ã" } 7656 }); 7657} 7658if (document.readyState === "loading") { 7659 document.addEventListener("DOMContentLoaded", initScrollHint); 7660} else { 7661 initScrollHint(); 7662} 7663 7664})(); 7665 7666;(() => { 7667// ----- script4.js ----- 7668function initScrollHint() { 7669 new ScrollHint(".js-scrollable", { 7670 suggestiveShadow: true, 7671 i18n: { 7672 scrollable: "ã¹ã¯ãã¼ã«ã§ãã¾ã" 7673 } 7674 }); 7675} 7676if (document.readyState === "loading") { 7677 document.addEventListener("DOMContentLoaded", initScrollHint); 7678} else { 7679 initScrollHint(); 7680} 7681const btn = document.querySelector(".editor-filter__controls .round-link-btn"); 7682const select = document.getElementById("monthly-year-select"); 7683btn?.addEventListener("click", (e) => { 7684 e.preventDefault(); 7685 const year = select?.value; 7686 if (!year) return; 7687 const target = document.getElementById(`section-${year}`); 7688 if (!target) return; 7689 target.scrollIntoView({ behavior: "smooth" }); 7690}); 7691 7692})(); 7693 7694;(() => { 7695// ----- script5.js ----- 7696/* empty css */ 7697 7698function initScrollHint() { 7699 new ScrollHint(".js-scrollable", { 7700 suggestiveShadow: true, 7701 i18n: { scrollable: "ã¹ã¯ãã¼ã«ã§ãã¾ã" } 7702 }); 7703} 7704if (document.readyState === "loading") { 7705 document.addEventListener("DOMContentLoaded", initScrollHint); 7706} else { 7707 initScrollHint(); 7708} 7709 7710})(); 7711 7712;(() => { 7713// ----- script6.js ----- 7714function initScrollHint() { 7715 new ScrollHint(".js-scrollable", { 7716 suggestiveShadow: true, 7717 i18n: { 7718 scrollable: "ã¹ã¯ãã¼ã«ã§ãã¾ã" 7719 } 7720 }); 7721} 7722if (document.readyState === "loading") { 7723 document.addEventListener("DOMContentLoaded", initScrollHint); 7724} else { 7725 initScrollHint(); 7726} 7727 7728})(); 7729 7730;(() => { 7731// ----- script7.js ----- 7732document.addEventListener('DOMContentLoaded', () => { 7733 const triggers = document.querySelectorAll('[data-accordion-trigger]'); 7734
7735 triggers.forEach(trigger => { 7736 trigger.addEventListener('click', () => { 7737 const target = trigger.nextElementSibling; 7738 7739 trigger.classList.toggle('is-open'); 7740 if (target && target.hasAttribute('data-accordion-target')) { 7741 target.classList.toggle('is-open'); 7742 } 7743 }); 7744 }); 7745}); 7746 7747// Shared animation helpers to reduce refresh/listener conflicts across modules. 7748 7749let refreshTimer = null; 7750let refreshQueuedInFrame = false; 7751 7752function scheduleScrollTriggerRefresh(delay = 0) { 7753 if (typeof ScrollTrigger === "undefined") return; 7754 7755 if (refreshTimer) { 7756 clearTimeout(refreshTimer); 7757 } 7758 7759 refreshTimer = window.setTimeout(() => { 7760 refreshTimer = null; 7761 7762 if (refreshQueuedInFrame) return; 7763 refreshQueuedInFrame = true; 7764 7765 requestAnimationFrame(() => { 7766 refreshQueuedInFrame = false; 7767 if (typeof ScrollTrigger !== "undefined") { 7768 ScrollTrigger.refresh(); 7769 } 7770 }); 7771 }, delay); 7772} 7773 7774function addDebouncedResizeListener(callback, delay = 200) { 7775 let resizeTimer = null; 7776 7777 const handler = () => { 7778 clearTimeout(resizeTimer); 7779 resizeTimer = setTimeout(() => { 7780 callback(); 7781 }, delay); 7782 }; 7783 7784 window.addEventListener("resize", handler); 7785 7786 return () => { 7787 clearTimeout(resizeTimer); 7788 window.removeEventListener("resize", handler); 7789 }; 7790} 7791 7792/** 7793 * Recruitã»ã¯ã·ã§ã³ 7794 * - åæ: èæ¯è²ï¼äººï¼ã«ã¼ãï¼ã®ã¿è¡¨ç¤º 7795 * - ã¹ã¯ãã¼ã«ã§: ãRecruitã䏿åãã¤ ï¼ ãStep Forward with US!ã1è¡ã»ããã§ã¢ãã表示 7796 * - ãã®å¾: 両ãµã¤ãã®åçããã§ã¼ãã¤ã³ãããã©ã©ãã¯ã¹å¹æï¼GSAP + ScrollTriggerãSPã¯ç§»åéæ§ããï¼ 7797 */ 7798 7799 7800const RECRUIT_BREAKPOINT_SP = 767; 7801 7802function isBrVisibleAsLineBreak$1(br, isSp) { 7803 const hasSpOnly = br.classList.contains("sp-only"); 7804 const hasPcOnly = br.classList.contains("pc-only"); 7805 if (hasSpOnly) return isSp; 7806 if (hasPcOnly) return !isSp; 7807 return true; 7808} 7809 7810function parseRecruitCopyLines(container) { 7811 const isSp = window.innerWidth <= RECRUIT_BREAKPOINT_SP; 7812 const lines = []; 7813 let current = ""; 7814 for (const node of container.childNodes) { 7815 if (node.nodeType === Node.TEXT_NODE) { 7816 current += node.textContent; 7817 } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "BR") { 7818 const shouldBreak = isBrVisibleAsLineBreak$1(node, isSp); 7819 if (shouldBreak) { 7820 lines.push(current.trim()); 7821 current = ""; 7822 } 7823 } 7824 } 7825 if (current.length) lines.push(current.trim()); 7826 return lines; 7827} 7828 7829function initRecruitHeadingCopyAnimation() { 7830 const content = document.querySelector(".recruit-section__content"); 7831 const heading = document.querySelector(".recruit-section__heading"); 7832 const copy = document.querySelector(".recruit-section__copy"); 7833 if (!content || !heading || !copy) return; 7834 7835 // 1è¡ç®: è¦åºãã1æåã㤠.char ã«åå²ï¼å å´ã©ããã¼ã§ overflow ãéããï¼ 7836 const headingContent = heading.textContent.trim().replace(/\s+/g, " "); 7837 heading.textContent = ""; 7838 const inner = document.createElement("span"); 7839 inner.className = "recruit-section__heading-inner"; 7840 headingContent.split("").forEach((char, index) => { 7841 const span = document.createElement("span"); 7842 span.className = char === " " ? "char space" : "char"; 7843 span.textContent = char; 7844 span.style.setProperty("--char-delay", `${index * 0.05}s`); 7845 inner.appendChild(span); 7846 }); 7847 heading.appendChild(inner); 7848 7849 // ã³ãã¼ãè¡ãã¨ã«ã©ããï¼SPã§ã¯ <br class="sp-only"> ã§ãwithãã®åã§æãè¿ãï¼ 7850 const lines = parseRecruitCopyLines(copy); 7851 if (lines.length > 0) { 7852 copy.textContent = ""; 7853 const baseDelay = 0.25; 7854 lines.forEach((lineText, index) => { 7855 const wrapper = document.createElement("span"); 7856 wrapper.className = "recruit-section__copy-line-wrapper"; 7857 wrapper.style.setProperty( 7858 "--line-delay", 7859 `${baseDelay + index * 0.15}s`, 7860 ); 7861 const line = document.createElement("span"); 7862 line.className = "recruit-section__copy-line"; 7863 line.textContent = lineText; 7864 wrapper.appendChild(line); 7865 copy.appendChild(wrapper); 7866 });
7867 } 7868} 7869 7870window.addEventListener("DOMContentLoaded", () => { 7871 initRecruitHeadingCopyAnimation(); 7872 7873 const imagesLeft = document.querySelector(".recruit-section__images--left"); 7874 const imagesRight = document.querySelector(".recruit-section__images--right"); 7875 const recruitSection = document.querySelector(".recruit-section"); 7876 const content = document.querySelector(".recruit-section__content"); 7877 7878 // ç»åï¼å·¦å³ï¼: ãã§ã¼ãã¤ã³ç¨ã®é å»¶ãä»ä¸ï¼ææ°å¯å¤ï¼ 7879 if (recruitSection) { 7880 const leftImgs = recruitSection.querySelectorAll( 7881 ".recruit-section__images--left .recruit-section__image", 7882 ); 7883 const rightImgs = recruitSection.querySelectorAll( 7884 ".recruit-section__images--right .recruit-section__image", 7885 ); 7886 const perImageDelay = 0.12; // seconds 7887 7888 leftImgs.forEach((img, index) => { 7889 img.style.setProperty("--recruit-image-delay", `${index * perImageDelay}s`); 7890 }); 7891 rightImgs.forEach((img, index) => { 7892 img.style.setProperty("--recruit-image-delay", `${index * perImageDelay}s`); 7893 }); 7894 } 7895 7896 // ç»é¢ã«å ¥ã£ãã: ã³ã³ãã³ã表示 â Recruit 䏿åãã¤ ï¼ Step 1è¡ã»ããã§ã¢ãã â ãã®å¾ä¸¡ãµã¤ãåçãã§ã¼ãã¤ã³ 7897 if (content && recruitSection) { 7898 const heading = document.querySelector(".recruit-section__heading"); 7899 const copy = document.querySelector(".recruit-section__copy"); 7900 7901 const observer = new IntersectionObserver( 7902 (entries) => { 7903 entries.forEach((entry) => { 7904 if (entry.isIntersecting) { 7905 observer.unobserve(entry.target); 7906 content.classList.add("is-show"); 7907 if (heading) heading.classList.add("is-inview"); 7908 if (copy) copy.classList.add("is-inview"); 7909 // recruit-sectionãç»é¢ã«å ¥ã£ããå·¦å³ç»åããã§ã¼ãã¤ã³ï¼ããã¹ãå¾ã§ã¯ãªãå³æï¼ 7910 recruitSection.classList.add("is-inview"); 7911 // äºæ: æ¢åCSS/HTMLå´ã§åç §ãã¦ããå¯è½æ§ãããããæ®ã 7912 if (imagesLeft) imagesLeft.classList.add("is-visible"); 7913 if (imagesRight) imagesRight.classList.add("is-visible"); 7914 } 7915 }); 7916 }, 7917 { rootMargin: "0px", threshold: 0.2 } 7918 ); 7919 7920 observer.observe(recruitSection); 7921 } 7922 7923 // ç»åã®ãã©ã©ãã¯ã¹ï¼GSAP + ScrollTriggerã»CDN èªã¿è¾¼ã¿æ¸ã¿åæï¼ 7924 // SP 㯠AboutVision ããç§»åè·é¢ãæãã yPercentï¼PC ãããå¼±ãï¼ 7925 if (typeof gsap !== "undefined" && typeof ScrollTrigger !== "undefined") { 7926 gsap.registerPlugin(ScrollTrigger); 7927 7928 const isSp = window.innerWidth <= RECRUIT_BREAKPOINT_SP; 7929 7930 const leftImages = document.querySelectorAll( 7931 ".recruit-section__images--left .recruit-section__image", 7932 ); 7933 const rightImages = document.querySelectorAll( 7934 ".recruit-section__images--right .recruit-section__image", 7935 ); 7936 const parallaxRange = isSp ? 42 : 120; // å¯ååï¼%ï¼ãSP ã¯æ§ãã 7937 const staggerPerIndex = isSp ? 10 : 25; 7938 const scrub = isSp ? 2.8 : 3.5; 7939 7940 if (leftImages.length > 0 && recruitSection) { 7941 leftImages.forEach((img, index) => { 7942 const distance = parallaxRange + index * staggerPerIndex; 7943 gsap.set(img, { yPercent: -30 }); 7944 gsap.to(img, { 7945 yPercent: -30 - distance, 7946 ease: "none", 7947 scrollTrigger: { 7948 trigger: recruitSection, 7949 start: "top bottom", 7950 end: "bottom top", 7951 scrub, 7952 }, 7953 }); 7954 }); 7955 } 7956 7957 if (rightImages.length > 0 && recruitSection) { 7958 rightImages.forEach((img, index) => { 7959 const distance = parallaxRange + index * staggerPerIndex; 7960 gsap.set(img, { yPercent: -70 }); 7961 gsap.to(img, { 7962 yPercent: -70 + distance, 7963 ease: "none", 7964 scrollTrigger: { 7965 trigger: recruitSection, 7966 start: "top bottom", 7967 end: "bottom top", 7968 scrub, 7969 }, 7970 }); 7971 }); 7972 } 7973 7974 window.addEventListener("load", () =>
7974 scheduleScrollTriggerRefresh()); 7975 addDebouncedResizeListener(() => { 7976 scheduleScrollTriggerRefresh(); 7977 }, 120); 7978 } 7979}); 7980 7981/** 7982 * Newsã»ã¯ã·ã§ã³ ãããã¼ã¢ãã¡ã¼ã·ã§ã³ 7983 * 1è¡ç®ï¼Newsï¼â 2è¡ç®ï¼ãç¥ããï¼ã®é ã§ä¸ããã¹ã©ã¤ãã¤ã³ï¼vision / ir-section-kv ã¨åæ§ï¼ 7984 */ 7985function initNewsSectionHeadingAnimation() { 7986 const heading = document.querySelector(".news-section__heading"); 7987 const headingEn = document.querySelector(".news-section__heading-en"); 7988 const headingJa = document.querySelector(".news-section__heading-ja"); 7989 if (!heading || !headingEn) return; 7990 7991 // 1è¡ç®: è±èªè¦åºãã1æåã㤠.char ã«åå²ï¼å å´ã©ããã¼ã§ overflow ãéããï¼ 7992 const enContent = headingEn.textContent.trim().replace(/\s+/g, " "); 7993 headingEn.textContent = ""; 7994 const inner = document.createElement("span"); 7995 inner.className = "news-section__heading-en-inner"; 7996 enContent.split("").forEach((char, index) => { 7997 const span = document.createElement("span"); 7998 span.className = char === " " ? "char space" : "char"; 7999 span.textContent = char; 8000 span.style.setProperty("--char-delay", `${index * 0.05}s`); 8001 inner.appendChild(span); 8002 }); 8003 headingEn.appendChild(inner); 8004 8005 const headingObserver = new IntersectionObserver( 8006 (entries) => { 8007 entries.forEach((entry) => { 8008 if (entry.isIntersecting) { 8009 headingEn.classList.add("is-inview"); 8010 if (headingJa) headingJa.classList.add("is-inview"); 8011 } 8012 }); 8013 }, 8014 { rootMargin: "0px", threshold: 0.2 } 8015 ); 8016 headingObserver.observe(heading); 8017 8018 // 2è¡ç®: æ¥æ¬èªè¦åºããè¡ã§ã©ããï¼SP ã®ã¿è¡¨ç¤ºã®ããç¥ãããï¼ 8019 if (headingJa) { 8020 const lineText = headingJa.textContent.trim(); 8021 if (lineText) { 8022 headingJa.textContent = ""; 8023 const lineDelay = 0.25; 8024 const wrapper = document.createElement("span"); 8025 wrapper.className = "news-section__heading-ja-line-wrapper"; 8026 wrapper.style.setProperty("--line-delay", `${lineDelay}s`); 8027 const line = document.createElement("span"); 8028 line.className = "news-section__heading-ja-line"; 8029 line.textContent = lineText; 8030 wrapper.appendChild(line); 8031 headingJa.appendChild(wrapper); 8032 } 8033 } 8034} 8035 8036/** 8037 * CTAã»ã¯ã·ã§ã³ ããã«ã¿ã¤ãã«ã»ãµãã¿ã¤ãã«ã¢ãã¡ã¼ã·ã§ã³ 8038 * 1è¡ç®ï¼ã¿ã¤ãã«ï¼â 2è¡ç®ï¼ãµãã¿ã¤ãã«ï¼ã®é ã§ä¸ããã¹ã©ã¤ãã¤ã³ï¼vision / ir-section-kv ã¨åæ§ï¼ 8039 * å·¦å³ä¸¡ããã«ã«é©ç¨ 8040 */ 8041function initCtaPanelTitleAnimation() { 8042 const wrappers = document.querySelectorAll(".cta__panel-title-wrapper"); 8043 if (!wrappers.length) return; 8044 8045 wrappers.forEach((wrapper) => { 8046 const title = wrapper.querySelector(".cta__panel-title"); 8047 const subtitle = wrapper.querySelector(".cta__panel-subtitle"); 8048 if (!title) return; 8049 8050 // 1è¡ç®: ã¿ã¤ãã«ã1æåã㤠.char ã«åå²ï¼æ¹è¡ã»ä½åãªç©ºç½ãé¤ãï¼ 8051 const titleContent = title.textContent.trim().replace(/\s+/g, " "); 8052 title.textContent = ""; 8053 const inner = document.createElement("span"); 8054 inner.className = "cta__panel-title-inner"; 8055 titleContent.split("").forEach((char, index) => { 8056 const span = document.createElement("span"); 8057 span.className = char === " " ? "char space" : "char"; 8058 span.textContent = char; 8059 span.style.setProperty("--char-delay", `${index * 0.05}s`); 8060 inner.appendChild(span); 8061 }); 8062 title.appendChild(inner); 8063 8064 const wrapperObserver = new IntersectionObserver( 8065 (entries) => { 8066 entries.forEach((entry) => { 8067 if (entry.isIntersecting) { 8068 title.classList.add("is-inview"); 8069 if (subtitle) subtitle.classList.add("is-inview"); 8070 } 8071 }); 8072 }, 8073 { rootMargin: "0px", threshold: 0.2 } 8074 ); 8075 wrapperObserver.observe(wrapper); 8076 8077 // 2è¡ç®: ãµãã¿ã¤ãã«ãè¡ã§ã©ãã 8078 if (subtitle) { 8079 const lineText = subtitle.textContent.trim(); 8080 if (lineText) { 8081 subtitle.textContent = ""; 8082 const lineDelay = 0.25; 8083 const lineWrapper = document.createElement("span"); 8084 lineWrapper.className = "cta__panel-subtitle-line-wrapper"; 8085 lineWrapper.style.setProperty("--line-delay", `${lineDelay}s`); 8086 const line = document.createElement("span"); 8087 line.className = "cta__panel-subtitle-line"; 8088 line.textContent = lineText; 8089 lineWrapper.appendChild(line); 8090 subtitle.appendChild(lineWrapper); 8091 } 8092 } 8093 }); 8094} 8095 8096/** 8097 * PCãããã¼ã®ãããããã¦ã³ã¡ãã¥ã¼å¶å¾¡ 8098 * 768px以ä¸ã§ã®ã¿åä½ 8099 * 8100 * ãã¼ã¸é·ç§»ç´å¾ã¯ãã¦ã¹ããªã³ã¯ä½ç½®ã«æ®ã£ã¦ããããã
8101 * ã«ã¼ã½ã«ãå®éã«åãï¼mousemoveï¼ã¾ã§ããã¼ãç¡å¹åããã 8102 */ 8103 8104/** ãã¼ã¸èªã¿è¾¼ã¿ç´å¾ã¯ trueãmousemove ã§ false ã«æ»ã */ 8105let ignoreHover = true; 8106 8107function setupHoverReset() { 8108 const onMove = () => { 8109 ignoreHover = false; 8110 }; 8111 document.addEventListener("mousemove", onMove, { once: true }); 8112} 8113 8114/** 表示ä¸ã®ã¡ã¬ã¡ãã¥ã¼æ°ï¼getComputedStyle 㯠opacity ãã©ã³ã¸ã·ã§ã³ä¸ã«èª¤å¤å®ãããã JS ã§è¿½è·¡ããï¼ */ 8115let megaMenuBackdropRef = 0; 8116 8117/** ã¡ã¬è¡¨ç¤ºä¸ã¯ document ã¹ã¯ãã¼ã«ãæ¢ããï¼Lenis 㯠stop/startï¼ */ 8118function setPcMegaMenuScrollLock(locked) { 8119 if (locked) { 8120 document.documentElement.style.overflow = "hidden"; 8121 document.body.style.overflow = "hidden"; 8122 window.lenis?.stop?.(); 8123 } else { 8124 document.documentElement.style.overflow = ""; 8125 document.body.style.overflow = ""; 8126 window.lenis?.start?.(); 8127 } 8128} 8129 8130function updateMegaMenuBackdrop() { 8131 const headerPc = document.querySelector(".header-pc"); 8132 if (!headerPc) return; 8133 const open = megaMenuBackdropRef > 0; 8134 headerPc.classList.toggle("is-mega-menu-open", open); 8135 setPcMegaMenuScrollLock(open); 8136} 8137 8138/** ã¡ã¬ã¡ãã¥ã¼ãéããã¨ãã®ããã¯ããããåç §ã 1 æ¸ããï¼éè¤å¼ã³åºã鲿¢ã®ãã表示ä¸ã®ã¿ï¼ */ 8139function releaseMegaMenuBackdropIfOpen(isMegaMenu, menuVisible) { 8140 if (!isMegaMenu || !menuVisible) return; 8141 megaMenuBackdropRef = Math.max(0, megaMenuBackdropRef - 1); 8142 updateMegaMenuBackdrop(); 8143} 8144 8145function initHeaderPc() { 8146 // ã¡ãã£ã¢ã¯ã¨ãªãã§ãã¯ï¼768px以ä¸ï¼ 8147 const mediaQuery = window.matchMedia("(min-width: 768px)"); 8148 8149 if (!mediaQuery.matches) { 8150 return; 8151 } 8152 8153 // ãã®ãã¼ã¸èªã¿è¾¼ã¿ç´å¾ã¯ããã¼ç¡å¹ãååã® mousemove ã§è§£é¤ 8154 ignoreHover = true; 8155 setupHoverReset(); 8156 8157 // ã¦ã¼ãã£ãªãã£ãã¼ã®ãããããã¦ã³ï¼ããã¼ã§è¡¨ç¤ºï¼ 8158 const dropdowns = document.querySelectorAll(".header-pc__dropdown"); 8159 const headerPcEl = document.querySelector(".header-pc"); 8160 8161 dropdowns.forEach((dropdown) => { 8162 const trigger = dropdown.querySelector(".header-pc__dropdown-trigger"); 8163 const menu = dropdown.querySelector(".header-pc__dropdown-menu"); 8164 8165 if (!trigger || !menu) return; 8166 8167 let hideTimer = null; 8168 8169 const clearHideTimer = () => { 8170 if (hideTimer) { 8171 clearTimeout(hideTimer); 8172 hideTimer = null; 8173 } 8174 }; 8175 8176 const hideMenu = () => { 8177 trigger.classList.remove("is-open"); 8178 menu.style.pointerEvents = "none"; 8179 menu.style.opacity = "0"; 8180 menu.style.visibility = "hidden"; 8181 menu.style.transform = "translateY(-10px)"; 8182 }; 8183 8184 const scheduleHide = () => { 8185 hideTimer = setTimeout(() => { 8186 hideMenu(); 8187 hideTimer = null; 8188 }, 200); 8189 }; 8190 8191 // ãããã¼å ã®å¥è¦ç´ ã¸ã®ç§»åã¯å³æéããï¼ã«ã¼ã鲿¢ï¼ 8192 const isInsideHeader = (el) => el && headerPcEl && headerPcEl.contains(el); 8193 8194 // ããã¼æã®å¦çï¼é·ç§»ç´å¾ã¯ç¡å¹ï¼ 8195 dropdown.addEventListener("mouseenter", () => { 8196 if (ignoreHover) return; 8197 clearHideTimer(); 8198 trigger.classList.add("is-open"); 8199 menu.style.pointerEvents = ""; 8200 menu.style.opacity = "1"; 8201 menu.style.visibility = "visible"; 8202 menu.style.transform = "translateY(0)"; 8203 }); 8204 8205 dropdown.addEventListener("mouseleave", (e) => { 8206 if (isInsideHeader(e.relatedTarget)) { 8207 clearHideTimer(); 8208 hideMenu(); 8209 return; 8210 } 8211 scheduleHide(); 8212 }); 8213 8214 menu.addEventListener("mouseleave", (e) => { 8215 if (e.relatedTarget && dropdown.contains(e.relatedTarget)) return; 8216 if (isInsideHeader(e.relatedTarget)) { 8217 clearHideTimer(); 8218 hideMenu(); 8219 return; 8220 } 8221 scheduleHide(); 8222 }); 8223 8224 // ãã¼ãã¼ãæä½ã®ãµãã¼ã 8225 trigger.addEventListener("keydown", (e) => { 8226 if (e.key === "Enter" || e.key === " ") { 8227 e.preventDefault(); 8228 const isOpen = menu.style.visibility === "visible"; 8229 if (isOpen) { 8230 hideMenu(); 8231 } else { 8232 trigger.classList.add("is-open"); 8233 menu.style.pointerEvents = ""; 8234 menu.style.opacity = "1"; 8235 menu.style.visibility = "visible"; 8236 menu.style.transform = "translateY(0)"; 8237 } 8238 } 8239 });
8240 }); 8241 8242 // ã¡ã¤ã³ããã²ã¼ã·ã§ã³ã®ãããããã¦ã³ï¼ããã¼ã§è¡¨ç¤ºï¼ 8243 const navDropdowns = document.querySelectorAll(".header-pc__nav-dropdown"); 8244 8245 // ä»ã®ã¡ãã¥ã¼ã峿éããããã®é¢æ°ãå ¨ãããããã¦ã³åä¿æããé å 8246 const allHideImmediately = []; 8247 8248 navDropdowns.forEach((navDropdown) => { 8249 // ã¡ã¬ã¡ãã¥ã¼ï¼HeaderDropdownã³ã³ãã¼ãã³ãï¼ã¾ãã¯é常ã®ãããããã¦ã³ã¡ãã¥ã¼ãæ¢ã 8250 const menu = navDropdown.querySelector(".header-dropdown") || 8251 navDropdown.querySelector(".header-pc__dropdown-menu"); 8252 8253 if (!menu) return; 8254 8255 // ã¡ã¬ã¡ãã¥ã¼ãã©ãããå¤å®ï¼.header-dropdownã¯ã©ã¹ãããã°ã¡ã¬ã¡ãã¥ã¼ï¼ 8256 const isMegaMenu = menu.classList.contains("header-dropdown"); 8257 8258 let hideTimer = null; 8259 let isMenuVisible = false; 8260 const navLink = navDropdown.querySelector(".header-pc__nav-link"); 8261 8262 // ã¡ãã¥ã¼ã峿é表示ã«ãã颿°ï¼å¥ããé ç®ãããã¼ãããéã«å¼ã°ããï¼ 8263 const hideMenuImmediately = () => { 8264 if (hideTimer) { 8265 clearTimeout(hideTimer); 8266 hideTimer = null; 8267 } 8268 if (navLink) navLink.classList.remove("is-menu-open"); 8269 releaseMegaMenuBackdropIfOpen(isMegaMenu, isMenuVisible); 8270 menu.style.pointerEvents = "none"; 8271 menu.style.opacity = "0"; 8272 menu.style.visibility = "hidden"; 8273 menu.style.transform = isMegaMenu 8274 ? "translate(-50%, -10px)" 8275 : "translateY(-10px)"; 8276 isMenuVisible = false; 8277 }; 8278 8279 // ãã® navDropdown ã®å³æéã颿°ãç»é² 8280 const selfIndex = allHideImmediately.push(hideMenuImmediately) - 1; 8281 8282 // ã¡ãã¥ã¼ã表示ãã颿° 8283 const showMenu = (force = false) => { 8284 // ä»ã®ãã¹ã¦ã®ã¡ãã¥ã¼ã峿éãã 8285 allHideImmediately.forEach((fn, i) => { 8286 if (i !== selfIndex) fn(); 8287 }); 8288 8289 // ã¿ã¤ãã¼ãã¯ãªã¢ï¼å¸¸ã«å®è¡ï¼ 8290 if (hideTimer) { 8291 clearTimeout(hideTimer); 8292 hideTimer = null; 8293 } 8294 8295 // æ¢ã«ã¡ãã¥ã¼ã表示ããã¦ããå ´åã§ãforceãfalseã®å ´åã¯ãã¹ã¿ã¤ã«ãåè¨å®ããªã 8296 // ããã«ãããCSSã®ããã¼ã¢ãã¡ã¼ã·ã§ã³ã䏿ãããªã 8297 if (isMenuVisible && !force) { 8298 return; 8299 } 8300 8301 const openingFromHidden = !isMenuVisible; 8302 8303 menu.style.pointerEvents = ""; 8304 menu.style.opacity = "1"; 8305 menu.style.visibility = "visible"; 8306 menu.style.transform = isMegaMenu ? "translate(-50%, 0)" : "translateY(0)"; 8307 isMenuVisible = true; 8308 if (navLink) navLink.classList.add("is-menu-open"); 8309 8310 if (isMegaMenu && openingFromHidden) { 8311 megaMenuBackdropRef++; 8312 updateMegaMenuBackdrop(); 8313 } 8314 }; 8315 8316 // ã¡ãã¥ã¼ãé表示ã«ãã颿°ï¼hoverè§£é¤ã§éããï¼ 8317 const hideMenu = () => { 8318 if (hideTimer) { 8319 clearTimeout(hideTimer); 8320 hideTimer = null; 8321 } 8322 8323 if (navLink) navLink.classList.remove("is-menu-open"); 8324 8325 hideTimer = setTimeout(() => { 8326 releaseMegaMenuBackdropIfOpen(isMegaMenu, isMenuVisible); 8327 menu.style.pointerEvents = "none"; 8328 menu.style.opacity = "0"; 8329 menu.style.visibility = "hidden"; 8330 menu.style.transform = isMegaMenu 8331 ? "translate(-50%, -10px)" 8332 : "translateY(-10px)"; 8333 isMenuVisible = false; 8334 hideTimer = null; 8335 }, 300); 8336 }; 8337 8338 // ã¡ãã¥ã¼å ã®è¦ç´ ãããã¼ãã¦ããããã§ãã¯ãã颿° 8339 const isHoveringMenu = (element) => { 8340 if (!element) return false; 8341 // ã¡ãã¥ã¼å ã®è¦ç´ ããnavDropdownå ã®è¦ç´ ãããã§ã㯠8342 return menu.contains(element) || navDropdown.contains(element); 8343 }; 8344 8345 // navDropdownã¨menuãä¸ã¤ã®ããã¼é åã¨ãã¦æ±ã 8346 // navDropdownã«ãã¦ã¹ãå ¥ã£ãã¨ãï¼é·ç§»ç´å¾ã¯ç¡å¹ï¼ 8347 navDropdown.addEventListener("mouseenter", () => { 8348 if (ignoreHover) return; 8349 showMenu(true); // å¼·å¶çã«è¡¨ç¤º 8350 });
8351 8352 // navDropdownãããã¦ã¹ãé¢ããã¨ãï¼ã¡ãã¥ã¼ã¸ã®ç§»åãèæ ®ï¼ 8353 navDropdown.addEventListener("mouseleave", (e) => { 8354 // ãã¦ã¹ãmenuã«ç§»åãã¦ããããã§ã㯠8355 const relatedTarget = e.relatedTarget; 8356 if (relatedTarget && isHoveringMenu(relatedTarget)) { 8357 // ã¡ãã¥ã¼ã«ç§»åãã¦ããå ´åã¯ä½ãããªã 8358 return; 8359 } 8360 hideMenu(); 8361 }); 8362 8363 // menuã«ãã¦ã¹ãå ¥ã£ãã¨ãï¼é·ç§»ç´å¾ã¯ç¡å¹ï¼ 8364 menu.addEventListener("mouseenter", () => { 8365 if (ignoreHover) return; 8366 showMenu(true); // å¼·å¶çã«è¡¨ç¤º 8367 }); 8368 8369 // menuãããã¦ã¹ãé¢ããã¨ã 8370 menu.addEventListener("mouseleave", (e) => { 8371 // ãã¦ã¹ãnavDropdownã«æ»ã£ã¦ããããã§ã㯠8372 const relatedTarget = e.relatedTarget; 8373 if (relatedTarget && isHoveringMenu(relatedTarget)) { 8374 // navDropdownã«æ»ã£ã¦ããå ´åã¯ä½ãããªã 8375 return; 8376 } 8377 hideMenu(); 8378 }); 8379 8380 // ã¡ãã¥ã¼å ã®ãã¹ã¦ã®è¦ç´ ï¼ãªã³ã¯ãSVGãªã©ï¼ã«å¯¾ãã¦mouseenterãç£è¦ 8381 // ããã«ããããã¹ããããè¦ç´ ãããã¼ãã¦ãã¡ãã¥ã¼ãéããã¾ã¾ã«ãªã 8382 // header-dropdown__footerã¨ãã®åè¦ç´ ã¯é¤å¤ï¼CSSã¢ãã¡ã¼ã·ã§ã³ã®ã¿ã§åä½ï¼ 8383 const menuElements = menu.querySelectorAll("a, button, span, svg, img"); 8384 menuElements.forEach((element) => { 8385 // header-dropdown__footerã¨ãã®åè¦ç´ ã¯é¤å¤ 8386 if (element.closest(".header-dropdown__footer")) { 8387 return; 8388 } 8389 element.addEventListener("mouseenter", () => { 8390 if (ignoreHover) return; 8391 showMenu(); 8392 }); 8393 }); 8394 8395 const megaRight = isMegaMenu 8396 ? menu.querySelector(".header-dropdown__right") 8397 : null; 8398 if (megaRight) { 8399 menu.addEventListener( 8400 "wheel", 8401 (e) => { 8402 if (megaRight.scrollHeight <= megaRight.clientHeight) return; 8403 if (megaRight.contains(e.target)) return; 8404 e.preventDefault(); 8405 megaRight.scrollTop += e.deltaY; 8406 }, 8407 { passive: false }, 8408 ); 8409 } 8410 8411 // ãã¼ãã¼ãæä½: Space ã§ã¡ã¬ã¡ãã¥ã¼ééï¼Enter ã¯ãªã³ã¯é·ç§»ããã®ã¾ã¾ä½¿ç¨ï¼ 8412 if (navLink) { 8413 navLink.addEventListener("keydown", (e) => { 8414 if (e.key !== " ") return; 8415 e.preventDefault(); 8416 if (isMenuVisible) { 8417 hideMenuImmediately(); 8418 } else { 8419 showMenu(true); 8420 } 8421 }); 8422 } 8423 }); 8424 8425 // ã¡ã¬ã¡ãã¥ã¼ãæããªãç´ãªã³ã¯ï¼ãã¥ã¼ã¹ã»ãã©ã³ãã»æ¡ç¨æ å ±ãªã©ï¼ãããã¼ããéã« 8426 // 表示ä¸ã®ãã¹ã¦ã®ã¡ã¬ã¡ãã¥ã¼ã峿éãã 8427 const plainNavLinks = document.querySelectorAll(".header-pc__nav > .header-pc__nav-link"); 8428 plainNavLinks.forEach((link) => { 8429 link.addEventListener("mouseenter", () => { 8430 if (ignoreHover) return; 8431 allHideImmediately.forEach((fn) => fn()); 8432 }); 8433 }); 8434} 8435 8436/** 8437 * SPãããã¼ã®ééå¶å¾¡ 8438 */ 8439function initHeaderSp() { 8440 const header = document.querySelector("[data-header-sp]");
8441 const toggle = document.querySelector("[data-header-sp-toggle]"); 8442 const openLabel = toggle?.querySelector(".header-sp__toggle-label--open"); 8443 const closedLabel = toggle?.querySelector(".header-sp__toggle-label--closed"); 8444 8445 if (!header || !toggle) return; 8446 8447 let savedScrollY = 0; 8448 8449 function lockBodyScroll() { 8450 savedScrollY = window.scrollY; 8451 document.body.style.top = `-${savedScrollY}px`; 8452 } 8453 8454 function unlockBodyScroll() { 8455 document.body.style.top = ""; 8456 window.scrollTo(0, savedScrollY); 8457 } 8458 8459 toggle.addEventListener("click", () => { 8460 const isOpen = header.classList.toggle("is-open"); 8461 toggle.setAttribute("aria-expanded", String(isOpen)); 8462 if (isOpen) { 8463 lockBodyScroll(); 8464 document.body.classList.add("is-header-sp-open"); 8465 window.lenis?.stop(); 8466 } else { 8467 document.body.classList.remove("is-header-sp-open"); 8468 unlockBodyScroll(); 8469 window.lenis?.start(); 8470 } 8471 if (!openLabel || !closedLabel) { 8472 toggle.textContent = isOpen ? "CLOSE" : "MENU"; 8473 } 8474 }); 8475 8476 // ã¢ã³ã¼ãã£ãªã³ã¡ãã¥ã¼ã®å±é/æãããã¿å¶å¾¡ 8477 const accordionToggles = document.querySelectorAll("[data-accordion-toggle]"); 8478 accordionToggles.forEach((toggle) => { 8479 toggle.addEventListener("click", () => { 8480 const item = toggle.closest(".header-sp__item--accordion"); 8481 const isExpanded = toggle.getAttribute("aria-expanded") === "true"; 8482 8483 // ç¾å¨ã®ç¶æ ãå転 8484 const newExpanded = !isExpanded; 8485 toggle.setAttribute("aria-expanded", String(newExpanded)); 8486 item.classList.toggle("is-expanded", newExpanded); 8487 }); 8488 }); 8489} 8490 8491gsap.registerPlugin(ScrollTrigger); 8492 8493// DOMã®èªã¿è¾¼ã¿å®äºãå¾ ã¤ 8494window.addEventListener('DOMContentLoaded', () => { 8495 // è±èªã¿ã¤ãã«ã䏿åãã¤åå²ãã¦ã¢ãã¡ã¼ã·ã§ã³ï¼PC/SP両æ¹å¯¾å¿ï¼ 8496 document.querySelectorAll('.page-heading__title-en').forEach((titleEn) => { 8497 titleEn.style.opacity = 1; 8498 8499 // åèªãã¨ã« overflow:hidden ã® span ã§å ãã§ã¢ãã¡ã¼ã·ã§ã³ãå¯¾å¿ 8500 // <br> ã¯å¼·å¶æ¹è¡ã¨ãã¦ä¿æãåèªéã¹ãã¼ã¹ã¯ span.char.space ã§è¡¨ç¾ 8501 const brSegments = titleEn.innerHTML 8502 .split(/<br\s*\/?>/i) 8503 .map((s) => s.trim()) 8504 .filter((s) => s.length > 0); 8505 titleEn.innerHTML = ''; 8506 titleEn.style.overflow = 'visible'; 8507 8508 let charIndex = 0; 8509 brSegments.forEach((segText, segIdx) => { 8510 if (segIdx > 0) titleEn.appendChild(document.createElement('br')); 8511 8512 const words = segText.split(/\s+/).filter((w) => w.length > 0); 8513 words.forEach((word, wordIdx) => { 8514 const wordWrap = document.createElement('span'); 8515 wordWrap.style.display = 'inline-block'; 8516 wordWrap.style.overflow = 'hidden'; 8517 wordWrap.style.paddingBottom = '0.15em'; 8518 word.split('').forEach((char) => { 8519 const span = document.createElement('span'); 8520 span.className = 'char'; 8521 span.textContent = char; 8522 span.setAttribute('data-char', char); 8523 span.style.setProperty('--char-delay', `${charIndex * 0.05}s`); 8524 wordWrap.appendChild(span); 8525 charIndex++; 8526 }); 8527 titleEn.appendChild(wordWrap); 8528 8529 if (wordIdx < words.length - 1) { 8530 const space = document.createElement('span'); 8531 space.className = 'char space'; 8532 space.textContent = ' '; 8533 space.style.setProperty('--char-delay', `${charIndex * 0.05}s`); 8534 titleEn.appendChild(space); 8535 charIndex++; 8536 } 8537 }); 8538 }); 8539 }); 8540 8541 // ãµãã¿ã¤ãã«ã®ããã¹ãã䏿åãã¤åå²ãã¦ã¢ãã¡ã¼ã·ã§ã³ 8542 const titleJa = document.querySelector('.page-heading__title-ja'); 8543 if (titleJa) { 8544 titleJa.style.opacity = 1; 8545 const text = titleJa.textContent; 8546 titleJa.innerHTML = ''; 8547 const baseDelay = 0.6; 8548 8549 text.split('').forEach((char) => { 8550 const span = document.createElement('span'); 8551 span.className = 'char'; 8552 span.textContent = char; 8553 span.style.animationDelay = `${baseDelay}s`; 8554 titleJa.appendChild(span); 8555 });
8556 } 8557 8558 const imageWrap = document.querySelector('.page-heading__image-wrap'); 8559 8560 // ç»åã®ããªãã³ã°ä½ç½®ãæ¡å¤§ããã¢ãã¡ã¼ã·ã§ã³ 8561 if (imageWrap) { 8562 let scrollTriggerInstance = null; 8563 8564 // ã¢ãã¡ã¼ã·ã§ã³ãåæåãã颿° 8565 const initAnimation = () => { 8566 // æ¢åã®ScrollTriggerãåé¤ 8567 if (scrollTriggerInstance) { 8568 scrollTriggerInstance.kill(); 8569 } 8570 8571 // ã¢ã¹ãã¯ãæ¯ã管çãããªãã¸ã§ã¯ãï¼åæå¤: 17:8 = 47.06%ï¼ 8572 const aspectRatioObj = { ratio: 47.06 }; 8573 8574 // GSAPã®ã¿ã¤ã ã©ã¤ã³ã使ï¼è¤æ°ã®ã¢ãã¡ã¼ã·ã§ã³ãåæå®è¡ï¼ 8575 const tl = gsap.timeline({ 8576 scrollTrigger: { 8577 trigger: imageWrap, // ãã®è¦ç´ ãããªã¬ã¼ 8578 start: 'top bottom', // è¦ç´ ã®ä¸ç«¯ãç»é¢ã®ä¸ç«¯ã«æ¥ããéå§ 8579 end: 'center top', // è¦ç´ ã®ä¸å¤®ãç»é¢ã®ä¸ç«¯ã«æ¥ããçµäº 8580 scrub: 1, // ã¹ã¯ãã¼ã«ã«åæï¼1ç§ã®é å»¶ï¼ 8581 markers: false // ãããã°ç¨ãã¼ã«ã¼ãé表示 8582 } 8583 }); 8584 8585 scrollTriggerInstance = tl.scrollTrigger; 8586 8587 // SPãµã¤ãºã®æã ãå¹ ã®æ¡å¤§ã¢ãã¡ã¼ã·ã§ã³ã追å 8588 if (window.innerWidth <= 767) { 8589 tl.to(imageWrap, { 8590 borderRadius: '0', 8591 ease: 'none' 8592 }, 0); 8593 } 8594 8595 tl.to(imageWrap, { 8596 width: '100%', 8597 ease: 'none' 8598 }, 0); 8599 8600 // ã¢ã¹ãã¯ãæ¯ã17:8(47.06%)ãã12:5(41.67%)ã«å¤æ´ 8601 tl.to(aspectRatioObj, { 8602 ratio: window.innerWidth > 767 ? 41.67 : 56.25, 8603 ease: 'none', 8604 onUpdate: () => { 8605 // ã¢ãã¡ã¼ã·ã§ã³ä¸ãæ¯ãã¬ã¼ã CSS夿°ãæ´æ° 8606 imageWrap.style.setProperty('--aspect-ratio', `${aspectRatioObj.ratio}%`); 8607 } 8608 }, 0); 8609 }; 8610 8611 // ååå®è¡ 8612 initAnimation(); 8613 8614 const image = imageWrap.querySelector('.page-heading__image'); 8615 gsap.set(imageWrap, { 8616 autoAlpha: 0 8617 }); 8618 gsap.to(imageWrap, { 8619 autoAlpha: 1, 8620 duration: 0.01, 8621 delay: 1 8622 }); 8623 8624 if (image) { 8625 gsap.set(image, { 8626 opacity: 0, 8627 scale: 1.1, 8628 transformOrigin: 'center center' 8629 }); 8630 gsap.to(image, { 8631 opacity: 1, 8632 scale: 1, 8633 duration: 1.2, 8634 delay: 1, 8635 ease: 'power2.out' 8636 }); 8637 } 8638 8639 // CSS夿°ã使ç¨ãã¦ã¢ã¹ãã¯ãæ¯ãåçã«å¤æ´ããã¹ã¿ã¤ã«ã追å 8640 const style = document.createElement('style'); 8641 style.textContent = ` 8642 .page-heading__image-wrap::before { 8643 padding-top: var(--aspect-ratio, 47.06%) !important; 8644 } 8645 `; 8646 document.head.appendChild(style); 8647 8648 // ãªãµã¤ãºæã«ã¢ãã¡ã¼ã·ã§ã³ãååæå 8649 addDebouncedResizeListener(() => { 8650 initAnimation(); 8651 }, 200); 8652 } 8653 8654 // ScrollTriggerããªãã¬ãã·ã¥ 8655 // ç»åã¨ãã©ã³ãã®èªã¿è¾¼ã¿ãå®äºãã¦ããä½ç½®ãåè¨ç® 8656 window.addEventListener('load', () => { 8657 scheduleScrollTriggerRefresh(); 8658 }); 8659 8660 // ãªãµã¤ãºæã«ScrollTriggerãæ´æ° 8661 // ç»é¢ãµã¤ãºãå¤ãã£ãæã«åè¦ç´ ã®ä½ç½®ãåè¨ç® 8662 addDebouncedResizeListener(() => { 8663 scheduleScrollTriggerRefresh(); 8664 }, 120); 8665}); 8666 8667/** 8668 * vision__title: 䏿åãã¤åå²ããç»é¢ã«å ¥ã£ãã¿ã¤ãã³ã°ã§ä¸ããä¸ã¸æãã¢ãã¡ã¼ã·ã§ã³ 8669 * vision__subtitle: è¡ãã¨ã«åå²ããç»é¢ã«å ¥ã£ãã¿ã¤ãã³ã°ã§ä¸ããä¸ã¸ãã¹ã¯ã¹ã©ã¤ãã¤ã³ 8670 * vision__text: dummy-text-body ã¨åæ§ã®ã°ã©ãã¼ã·ã§ã³ãã¹ã¯ã§ã¹ã¯ãã¼ã«ã«å¿ãã¦å·¦âå³ã«è¡¨ç¤º 8671 */ 8672 8673const VISION_BREAKPOINT_SP = 767; 8674 8675function isBrVisibleAsLineBreak(br, isSp) { 8676 const hasSpOnly = br.classList.contains("sp-only"); 8677 const hasPcOnly = br.classList.contains("pc-only"); 8678 if (hasSpOnly) return isSp; 8679 if (hasPcOnly) return !isSp; 8680 return true; 8681} 8682 8683function parseSubtitleLines(container, options = {}) { 8684 const isSp = options.responsiveBr 8685 ? window.innerWidth <= VISION_BREAKPOINT_SP 8686 : true; 8687 const lines = []; 8688 let current = ""; 8689 for (const node of container.childNodes) { 8690 if (node.nodeType === Node.TEXT_NODE) { 8691 current += node.textContent; 8692 } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "BR") { 8693 const shouldBreak = options.responsiveBr 8694 ? isBrVisibleAsLineBreak(node, isSp) 8695 : true; 8696 if (shouldBreak) { 8697 lines.push(current.trim()); 8698 current = ""; 8699 } 8700 } 8701 } 8702 if (current.length) lines.push(current.trim()); 8703 return lines; 8704} 8705 8706function initVisionTitleAnimation() { 8707 const title = document.querySelector(".vision__title"); 8708 if (!title) return; 8709 8710 const text = title.textContent; 8711 title.textContent = ""; 8712 8713 text.split("").forEach((char, index) => { 8714 const span = document.createElement("span"); 8715 span.className = char === " " ? "char space" : "char"; 8716 span.textContent = char; 8717 span.setAttribute("data-char", char); 8718 span.style.setProperty("--char-delay", `${index * 0.05}s`); 8719 title.appendChild(span); 8720 }); 8721 8722 const titleObserver = new IntersectionObserver( 8723 (entries) => {
8724 entries.forEach((entry) => { 8725 if (entry.isIntersecting) entry.target.classList.add("is-inview"); 8726 }); 8727 }, 8728 { rootMargin: "0px", threshold: 0.2 } 8729 ); 8730 titleObserver.observe(title); 8731} 8732 8733function initVisionSubtitleAnimation() { 8734 const subtitle = document.querySelector(".vision__subtitle[data-mask-text-item='lines, split']"); 8735 if (!subtitle) return; 8736 8737 const lines = parseSubtitleLines(subtitle); 8738 if (lines.length === 0) return; 8739 8740 subtitle.textContent = ""; 8741 const lineDelay = 0.25; 8742 8743 lines.forEach((lineText, index) => { 8744 const wrapper = document.createElement("span"); 8745 wrapper.className = "vision__subtitle-line-wrapper"; 8746 wrapper.style.setProperty("--line-delay", `${index * lineDelay}s`); 8747 const line = document.createElement("span"); 8748 line.className = "vision__subtitle-line"; 8749 line.textContent = lineText; 8750 wrapper.appendChild(line); 8751 subtitle.appendChild(wrapper); 8752 }); 8753 8754 const subtitleObserver = new IntersectionObserver( 8755 (entries) => { 8756 entries.forEach((entry) => { 8757 if (entry.isIntersecting) entry.target.classList.add("is-inview"); 8758 }); 8759 }, 8760 { rootMargin: "0px", threshold: 0.2 } 8761 ); 8762 subtitleObserver.observe(subtitle); 8763} 8764 8765let visionTextOriginalHTML = null; 8766let visionTextLastIsSp = null; 8767 8768function killVisionTextScrollTriggers() { 8769 if (typeof ScrollTrigger === "undefined") return; 8770 ScrollTrigger.getAll().forEach((st) => { 8771 if (st.vars?.id?.startsWith?.("vision-text-line-")) st.kill(); 8772 }); 8773} 8774 8775function initVisionTextMaskAnimation() { 8776 const textContainer = document.querySelector(".vision__text"); 8777 if (!textContainer) return; 8778 8779 const isSp = window.innerWidth <= VISION_BREAKPOINT_SP; 8780 8781 if (visionTextOriginalHTML === null) { 8782 visionTextOriginalHTML = textContainer.innerHTML; 8783 } 8784 8785 if (visionTextLastIsSp === isSp && textContainer.querySelector(".vision__text-line")) { 8786 return; 8787 } 8788 visionTextLastIsSp = isSp; 8789 8790 killVisionTextScrollTriggers(); 8791 8792 textContainer.innerHTML = visionTextOriginalHTML; 8793 const paragraphs = textContainer.querySelectorAll("p"); 8794 if (paragraphs.length === 0) return; 8795 8796 const lineWrappers = []; 8797 paragraphs.forEach((p, pIndex) => { 8798 const lines = parseSubtitleLines(p, { responsiveBr: true }); 8799 lines.forEach((lineText, lineIndex) => { 8800 const wrapper = document.createElement("span"); 8801 wrapper.className = "vision__text-line-wrapper"; 8802 if (pIndex > 0 && lineIndex === 0) { 8803 wrapper.classList.add("vision__text-line-wrapper--new-para"); 8804 } 8805 const line = document.createElement("span"); 8806 line.className = "vision__text-line"; 8807 line.textContent = lineText; 8808 wrapper.appendChild(line); 8809 lineWrappers.push({ wrapper, line }); 8810 }); 8811 }); 8812 8813 const fragment = document.createDocumentFragment(); 8814 lineWrappers.forEach(({ wrapper }) => fragment.appendChild(wrapper)); 8815 textContainer.innerHTML = ""; 8816 textContainer.appendChild(fragment); 8817 8818 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") { 8819 return; 8820 } 8821 gsap.registerPlugin(ScrollTrigger); 8822 8823 // PC: éå§ãå°ãé ãããï¼ç´2è¡ã¶ãå¾ãã§ãã§ã¼ãéå§ï¼ 8824 // SP: åãåºéã ã¨ã¹ã¯ãã¼ã«ãé·ãæããã®ã§ãçµäºãæ©ãã¦å ¨æè¡¨ç¤ºã¾ã§ãçããã 8825 const textFadeStart = isSp ? "top 90%" : "top 100%"; 8826 const textFadeEnd = isSp ? "top 70%" : "top 70%"; 8827 8828 lineWrappers.forEach(({ line }, index) => { 8829 gsap.to(line, { 8830 ease: "none", 8831 scrollTrigger: { 8832 trigger: line, 8833 start: textFadeStart, 8834 end: textFadeEnd, 8835 scrub: true, 8836 id: `vision-text-line-${index}`, 8837 onUpdate(self) { 8838 if (document.documentElement.classList.contains("is-brand-list-active")) { 8839 return; 8840 } 8841 8842 const progress = self.progress * 100; 8843 const fadeWidth = 40; 8844 const front = (progress * (100 + fadeWidth * 2)) / 100 - fadeWidth; 8845 8846 let maskValue; 8847 if (front <= 0) { 8848 maskValue = 8849 "linear-gradient(to right, transparent 0%, transparent 100%)"; 8850 } else if (front >= 100 + fadeWidth) { 8851 maskValue = "linear-gradient(to right, black 0%, black 100%)"; 8852 } else { 8853 const solidEnd = Math.max(0, front - fadeWidth);
8854 const transEnd = Math.min(100, front); 8855 maskValue = `linear-gradient(to right, black 0%, black ${solidEnd}%, transparent ${transEnd}%, transparent 100%)`; 8856 } 8857 8858 line.style.webkitMaskImage = maskValue; 8859 line.style.maskImage = maskValue; 8860 }, 8861 }, 8862 }); 8863 }); 8864} 8865 8866function onVisionResize() { 8867 const isSp = window.innerWidth <= VISION_BREAKPOINT_SP; 8868 if (visionTextLastIsSp !== null && visionTextLastIsSp !== isSp) { 8869 initVisionTextMaskAnimation(); 8870 } 8871} 8872 8873window.addEventListener("DOMContentLoaded", () => { 8874 initVisionTitleAnimation(); 8875 initVisionSubtitleAnimation(); 8876 initVisionTextMaskAnimation(); 8877}); 8878 8879window.addEventListener("resize", onVisionResize); 8880 8881/** 8882 * IRã»ã¯ã·ã§ã³KV 8883 * - ãããã¼: ã¿ã¤ãã«ï¼1è¡ç®ï¼ã»ãµãã¿ã¤ãã«ï¼2è¡ç®ï¼ã®é ã§ä¸ããã¹ã©ã¤ãã¤ã³ï¼vision ã¨åæ§ï¼ 8884 * - ç»å: clip-path ã§ä¸ããä¸ã¸ãªãã¼ã« 8885 * - 説ææ: clip-path ã§ä¸ããä¸ã¸ãªãã¼ã« 8886 * 8887 * ãããã§ã¯ main-content-block ã transform ã§ã¹ã©ã¤ãã¤ã³ããããã 8888 * ããªã¬ã¼ã phase5 ã«ããã¨çºç«ã¿ã¤ãã³ã°ãè¦è¦ã¨ä¸è´ããã 8889 */ 8890 8891function initIrSectionKvHeaderAnimation(onInview) { 8892 const header = document.querySelector(".ir-section-kv__header"); 8893 const title = document.querySelector(".ir-section-kv__header .ir-section-kv__title"); 8894 const subtitle = document.querySelector(".ir-section-kv__header .ir-section-kv__subtitle"); 8895 if (!header || !title) return; 8896 8897 // 1è¡ç®: ã¿ã¤ãã«ã1æåã㤠.char ã«åå²ï¼æ¹è¡ã»ä½åãªç©ºç½ãé¤ãï¼ 8898 const titleText = title.textContent.trim().replace(/\s+/g, " "); 8899 title.textContent = ""; 8900 titleText.split("").forEach((char, index) => { 8901 const span = document.createElement("span"); 8902 span.className = char === " " ? "char space" : "char"; 8903 span.textContent = char; 8904 span.style.setProperty("--char-delay", `${index * 0.05}s`); 8905 title.appendChild(span); 8906 }); 8907 8908 let headerRevealScheduled = false; 8909 const applyInview = () => { 8910 if (headerRevealScheduled) return; 8911 headerRevealScheduled = true; 8912 title.classList.add("is-inview"); 8913 if (subtitle) subtitle.classList.add("is-inview"); 8914 if (typeof onInview === "function") { 8915 onInview(); 8916 } 8917 }; 8918 8919 const headerObserver = new IntersectionObserver( 8920 (entries) => { 8921 entries.forEach((entry) => { 8922 if (entry.isIntersecting) { 8923 applyInview(); 8924 headerObserver.disconnect(); 8925 } 8926 }); 8927 }, 8928 { rootMargin: "0px", threshold: 0.2 } 8929 ); 8930 headerObserver.observe(header); 8931 8932 // Safari対ç: æ¢ã«ãã¥ã¼ãã¼ãå ã«ããå ´åã®å³æçºç« 8933 const check = () => { 8934 const rect = header.getBoundingClientRect(); 8935 if (rect.top < window.innerHeight * 0.9 && rect.bottom > 0) { 8936 applyInview(); 8937 } 8938 }; 8939 check(); 8940 window.addEventListener("load", () => { 8941 check(); 8942 setTimeout(check, 300); 8943 }, { once: true }); 8944 8945 // 2è¡ç®: ãµãã¿ã¤ãã«ãè¡ã§ã©ããï¼1è¡ã®ã¿ã§ãåãæ§é ã§é å»¶è¡¨ç¤ºï¼ 8946 if (subtitle) { 8947 const lineText = subtitle.textContent.trim(); 8948 if (lineText) { 8949 subtitle.textContent = ""; 8950 const lineDelay = 0.25; // 1è¡ç®è¡¨ç¤ºå¾ã«2è¡ç® 8951 const wrapper = document.createElement("span"); 8952 wrapper.className = "ir-section-kv__subtitle-line-wrapper"; 8953 wrapper.style.setProperty("--line-delay", `${lineDelay}s`); 8954 const line = document.createElement("span"); 8955 line.className = "ir-section-kv__subtitle-line"; 8956 line.textContent = lineText; 8957 wrapper.appendChild(line); 8958 subtitle.appendChild(wrapper); 8959 } 8960 } 8961} 8962 8963function initIrSectionKv() { 8964 if (window.innerWidth < 768) { 8965 initIrSectionKvHeaderAnimation(); 8966 return; 8967 } 8968 8969 const wrapper = document.querySelector(".ir-section-kv__image-wrapper"); 8970 const img = document.querySelector( 8971 ".ir-section-kv__image-wrapper .ir-section-kv__image", 8972 ); 8973 8974 if (!wrapper || !img) { 8975 initIrSectionKvHeaderAnimation(); 8976 return; 8977 } 8978 8979 const descriptionEl = document.querySelector(".ir-section-kv__description"); 8980 const descriptionParagraphs = document.querySelectorAll( 8981 ".ir-section-kv__description p", 8982 ); 8983 8984 if (typeof gsap === "undefined") { 8985 initIrSectionKvHeaderAnimation(); 8986 return; 8987 } 8988 8989 gsap.set(img, { clipPath: "inset(0 0 100% 0)" }); 8990 if (descriptionEl && descriptionParagraphs.length > 0) { 8991 gsap.set(descriptionParagraphs, { clipPath: "inset(0 0 100% 0)" }); 8992 } 8993 8994 const IMAGE_REVEAL_DURATION = 2; 8995 8996 const revealDescription = () => { 8997 if (!descriptionEl || descriptionParagraphs.length === 0) return; 8998 gsap.to(descriptionParagraphs, { 8999 clipPath: "inset(0 0 0% 0)", 9000 duration: 1.2, 9001 ease: "power4.out", 9002 stagger: 0.15, 9003 });
9004 }; 9005 9006 // ãããã¼è¡¨ç¤ºã¨ç»åãåæéå§ããç»åã®ä¸ç¤ã§èª¬ææã表示 9007 const onInview = () => { 9008 const tl = gsap.timeline(); 9009 tl.to(img, { 9010 clipPath: "inset(0 0 0% 0)", 9011 duration: IMAGE_REVEAL_DURATION, 9012 ease: "power4.out", 9013 }); 9014 tl.add(revealDescription, IMAGE_REVEAL_DURATION * 0.5); 9015 }; 9016 9017 initIrSectionKvHeaderAnimation(onInview); 9018} 9019 9020/** 9021 * IRã»ã¯ã·ã§ã³ãªã¹ãï¼PCï¼ 9022 * - ç»åã¨ãªã¢ï¼.ir-section-list__image-sectionï¼ããã¥ã¼ãã¼ãã«å ¥ã£ãã2æã¾ã¨ãã¦ãªãã¼ã« 9023 * - å·¦å³ã©ããã¼ãåå¥ã«ç£è¦ããªãï¼çãPCå¹ ã§å·¦ç»åãç»é¢å¤ã«ãªããçæ¹ã ã交差ã¼ãã®ã¾ã¾ã«ãªãã®ãé²ãï¼ 9024 * - ååãã¼ãæã 768px æªæºã§ããã®ã¡ã«åºããã¨ãã«ã»ããã¢ããããï¼matchMediaï¼ 9025 */ 9026function initIrSectionList() { 9027 if (typeof gsap === "undefined") return; 9028 9029 const mq = window.matchMedia("(min-width: 768px)"); 9030 let observer = null; 9031 let hasPlayed = false; 9032 9033 const cleanupObserver = () => { 9034 if (observer) { 9035 observer.disconnect(); 9036 observer = null; 9037 } 9038 }; 9039 9040 const playReveal = (leftImage, rightImage) => { 9041 if (hasPlayed) return; 9042 hasPlayed = true; 9043 cleanupObserver(); 9044 9045 gsap.timeline().to( 9046 [leftImage, rightImage], 9047 { 9048 clipPath: "inset(0 0 0% 0)", 9049 duration: 2, 9050 ease: "power4.out", 9051 stagger: 0.15, 9052 }, 9053 0, 9054 ); 9055 }; 9056 9057 const setup = () => { 9058 if (!mq.matches || hasPlayed) return; 9059 9060 const imageSection = document.querySelector(".ir-section-list__image-section"); 9061 const leftImage = document.querySelector( 9062 ".ir-section-list__image-wrapper--left .ir-section-list__image", 9063 ); 9064 const rightImage = document.querySelector( 9065 ".ir-section-list__image-wrapper--right .ir-section-list__image", 9066 ); 9067 9068 if (!imageSection || !leftImage || !rightImage) return; 9069 9070 cleanupObserver(); 9071 gsap.set([leftImage, rightImage], { clipPath: "inset(0 0 100% 0)" }); 9072 9073 const checkAlreadyVisible = () => { 9074 if (hasPlayed || !mq.matches) return; 9075 const rect = imageSection.getBoundingClientRect(); 9076 const vh = window.innerHeight || document.documentElement.clientHeight; 9077 if (rect.width <= 0 || rect.height <= 0) return; 9078 const verticallyInView = rect.top < vh * 0.9 && rect.bottom > vh * 0.1; 9079 if (verticallyInView) playReveal(leftImage, rightImage); 9080 }; 9081 9082 observer = new IntersectionObserver( 9083 (entries) => { 9084 for (const entry of entries) { 9085 if (entry.target === imageSection && entry.isIntersecting) { 9086 playReveal(leftImage, rightImage); 9087 return; 9088 } 9089 } 9090 }, 9091 { rootMargin: "0px", threshold: 0 }, 9092 ); 9093 9094 observer.observe(imageSection); 9095 9096 checkAlreadyVisible(); 9097 requestAnimationFrame(() => { 9098 requestAnimationFrame(checkAlreadyVisible); 9099 }); 9100 9101 window.addEventListener( 9102 "load", 9103 () => { 9104 checkAlreadyVisible(); 9105 setTimeout(checkAlreadyVisible, 300); 9106 }, 9107 { once: true }, 9108 ); 9109 }; 9110 9111 setup(); 9112 9113 const onMqChange = () => { 9114 if (!mq.matches) { 9115 cleanupObserver(); 9116 return; 9117 } 9118 if (!hasPlayed) { 9119 requestAnimationFrame(() => { 9120 requestAnimationFrame(setup); 9121 }); 9122 } 9123 }; 9124 9125 if (typeof mq.addEventListener === "function") { 9126 mq.addEventListener("change", onMqChange); 9127 } else { 9128 mq.addListener(onMqChange); 9129 } 9130} 9131 9132/** Fv åç»ã® SP å¤å®ï¼_variables.scss ã® sp ã¨æããï¼ */ 9133const FV_VIDEO_SP_MAX = 767; 9134 9135/** ãªãµã¤ãºã§è¤æ°å load ããã¨ããå¤ã canplaythrough ãç¡å¹åãã */ 9136let fvVideoPlayAbort = null; 9137 9138/** 9139 * SP/PC ã«å¿ãã <source> ãçµã¿ç«ã¦ããURL ã1ã¤ãç¡ããã° falseã 9140 * WebMãå ã«è¿½å ãã対å¿ãã©ã¦ã¶ã¯WebMï¼æªå¯¾å¿ã¯MP4ã使ãã 9141 */ 9142function applyFvVideoSources(video, isSp) { 9143 const mp4Src = isSp ? video.dataset.srcSp : video.dataset.srcPc; 9144 const webmSrc = isSp ? video.dataset.srcSpWebm : video.dataset.srcPcWebm; 9145 9146 if (!mp4Src && !webmSrc) return false; 9147 9148 video.removeAttribute("src"); 9149 video.replaceChildren();
9150 9151 if (webmSrc) { 9152 const sWebm = document.createElement("source"); 9153 sWebm.src = webmSrc; 9154 sWebm.type = "video/webm"; 9155 video.appendChild(sWebm); 9156 } 9157 if (mp4Src) { 9158 const sMp4 = document.createElement("source"); 9159 sMp4.src = mp4Src; 9160 sMp4.type = "video/mp4"; 9161 video.appendChild(sMp4); 9162 } 9163 9164 return true; 9165} 9166 9167function scheduleFvVideoPlay(video) { 9168 if (fvVideoPlayAbort) fvVideoPlayAbort.abort(); 9169 fvVideoPlayAbort = new AbortController(); 9170 const { signal } = fvVideoPlayAbort; 9171 9172 const tryPlay = () => { 9173 const promise = video.play(); 9174 if (promise !== undefined) { 9175 promise.catch(() => { 9176 // autoplay ããããã¯ãããå ´åï¼Low Power Modeçï¼ã¯éãã«å¤±æ 9177 }); 9178 } 9179 }; 9180 9181 // canplaythrough å¾ã«åçï¼Safariã§åç»ãã¼ã¿ãæã£ã¦ãã確å®ã«åçï¼ 9182 if (video.readyState >= 3) { 9183 tryPlay(); 9184 } else { 9185 video.addEventListener("canplaythrough", tryPlay, { once: true, signal }); 9186 } 9187} 9188 9189/** 9190 * åç»ã® img ãã¹ã¿ã¼å±¤ãããã¤ãã£ã poster åæ§ã«èªã¿è¾¼ã¿ãæåã®ãã¬ã¼ã ã¾ã§è¡¨ç¤ºããã 9191 * loadstart ã§å表示ãloadeddata ã§é表示ï¼ã½ã¼ã¹å·®ãæ¿ãæãåæ§ï¼ã 9192 */ 9193function bindFvPosterLayer(video) { 9194 const wrap = video.closest(".fv__video-wrap"); 9195 if (!wrap) return; 9196 9197 const showPosterLayer = () => wrap.classList.remove("fv__video-wrap--has-frame"); 9198 const hidePosterLayer = () => wrap.classList.add("fv__video-wrap--has-frame"); 9199 9200 video.addEventListener("loadstart", showPosterLayer); 9201 video.addEventListener("loadeddata", hidePosterLayer); 9202 9203 if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) { 9204 hidePosterLayer(); 9205 } 9206} 9207 9208/** 9209 * FVèæ¯åç»ã®åæå 9210 * Safariã§ã¯ <source media="..."> ã video è¦ç´ ã§æªãµãã¼ãã®ããã 9211 * data屿§ã§URLãæããã¦JSã§<source>ãçµã¿ç«ã¦ãã 9212 * 767px ãè·¨ãã ãªãµã¤ãºæã¯ã½ã¼ã¹ãå·®ãæ¿ããï¼åçä¸ã¯åéã䏿忢ä¸ã¯åæ¢ã®ã¾ã¾ï¼ã 9213 */ 9214function initFvVideo() { 9215 const video = document.querySelector(".fv__video"); 9216 if (!video) return; 9217 9218 bindFvPosterLayer(video); 9219 9220 const isSpViewport = () => window.innerWidth <= FV_VIDEO_SP_MAX; 9221 9222 let lastSp = isSpViewport(); 9223 if (!applyFvVideoSources(video, lastSp)) return; 9224 9225 video.load(); 9226 scheduleFvVideoPlay(video); 9227 9228 addDebouncedResizeListener(() => { 9229 const nowSp = isSpViewport(); 9230 if (nowSp === lastSp) return; 9231 9232 const wantPlay = !video.paused; 9233 fvVideoPlayAbort?.abort(); 9234 9235 if (!applyFvVideoSources(video, nowSp)) return; 9236 9237 lastSp = nowSp; 9238 video.load(); 9239 if (wantPlay) { 9240 scheduleFvVideoPlay(video); 9241 } 9242 }); 9243} 9244 9245/** 9246 * Fvã®åºå®èæ¯ï¼ãããªï¼ããBrand List Phase 3 çµäºæã«é表示ã«ããã 9247 * 表示ã®å¾©å¸°ã¯ãFvã»ã¯ã·ã§ã³ã«æ»ã£ãã¨ããã®ã¿ï¼_brand-list.js ã§ Phase 3 çµäºæã«é表示ãå®è¡ï¼ã 9248 */ 9249function initFvBgHide() { 9250 const fv = document.querySelector(".fv"); 9251 const fvBg = document.querySelector(".fv__bg"); 9252 if (!fv || !fvBg) return; 9253 9254 const setupScrollTrigger = () => { 9255 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") { 9256 setTimeout(setupScrollTrigger, 50); 9257 return; 9258 } 9259 gsap.registerPlugin(ScrollTrigger); 9260 9261 // Fvãé¢ãã¦ããããªã¯é表示ã«ããªãï¼è±å½¢æ¡å¤§ã¾ã§ã¯è¡¨ç¤ºããããï¼ 9262 // Phase 3 çµäºæã®é表示㯠_brand-list.js ã§è¡ã 9263 ScrollTrigger.create({ 9264 trigger: fv, 9265 start: "top top", 9266 end: "bottom top", 9267 onEnterBack: () => { 9268 gsap.set(fvBg, { autoAlpha: 1, pointerEvents: "none" }); 9269 }, 9270 markers: false, 9271 id: "fv-bg-hide", 9272 }); 9273 }; 9274 9275 setupScrollTrigger(); 9276} 9277 9278function initFvScroll() { 9279 const fv = document.querySelector(".fv"); 9280 const btn = document.querySelector("[data-fv-scroll]"); 9281 if (!fv) return; 9282 9283 const FADE_START = 0; 9284 const FADE_END = 120; 9285 const NEWS_TICKER_FADE_END = 100; // 100pxå°éãããªã¬ã¼ã«èªåãã§ã¼ãã¢ã¦ã 9286 9287 const getScrollY = () => 9288 typeof window.lenis !== "undefined" && window.lenis != null 9289 ? window.lenis.scroll 9290 : window.scrollY; 9291 9292 const updateScrolledState = () => { 9293 const scrollY = getScrollY(); 9294 const scrolled = scrollY > 8; 9295 const hideScroll = scrollY >= FADE_END; 9296 const opacity = 9297 scrollY <= FADE_START 9298 ? 1 9299 : scrollY >= FADE_END 9300 ? 0 9301 : 1 - (scrollY - FADE_START) / (FADE_END - FADE_START); 9302 document.body.classList.toggle("is-fv-scrolled", scrolled); 9303 document.body.classList.toggle("is-fv-scroll-hidden", hideScroll); 9304 document.body.classList.toggle( 9305 "is-fv-news-ticker-faded", 9306 scrollY >= NEWS_TICKER_FADE_END, 9307 ); 9308 document.body.style.setProperty("--fv-scroll-opacity", String(opacity)); 9309 }; 9310 9311 updateScrolledState(); 9312 window.addEventListener("scroll", updateScrolledState, { passive: true });
9313 if (typeof window.lenis !== "undefined" && window.lenis != null) { 9314 window.lenis.on("scroll", updateScrolledState); 9315 } 9316 9317 if (btn) { 9318 btn.addEventListener("click", () => { 9319 window.scrollBy({ 9320 top: Math.round(window.innerHeight * 0.9), 9321 left: 0, 9322 behavior: "smooth", 9323 }); 9324 }); 9325 } 9326} 9327 9328const FV_NEWS_TICKER_SP_MAX = 767; 9329 9330function initFvNewsTickerAlign() { 9331 const ticker = document.querySelector(".fv__news-ticker"); 9332 if (!ticker) return; 9333 const title = ticker.querySelector(".fv__news-ticker-title"); 9334 if (!title) return; 9335 9336 const update = () => { 9337 if (window.innerWidth > FV_NEWS_TICKER_SP_MAX) { 9338 ticker.classList.remove("fv__news-ticker--single-line"); 9339 return; 9340 } 9341 const lineHeight = parseFloat(getComputedStyle(title).lineHeight); 9342 title.style.webkitLineClamp = "unset"; 9343 const naturalHeight = title.scrollHeight; 9344 title.style.webkitLineClamp = ""; 9345 const isSingleLine = naturalHeight <= Math.ceil(lineHeight * 1.5); 9346 ticker.classList.toggle("fv__news-ticker--single-line", isSingleLine); 9347 }; 9348 9349 update(); 9350 addDebouncedResizeListener(update); 9351} 9352 9353function initFvVideoToggle() { 9354 const fv = document.querySelector(".fv"); 9355 if (!fv) return; 9356 9357 const video = fv.querySelector(".fv__video"); 9358 const btn = fv.querySelector("[data-fv-video-toggle]"); 9359 if (!video || !btn) return; 9360 9361 const setUiState = () => { 9362 const paused = video.paused; 9363 btn.classList.toggle("is-paused", paused); 9364 btn.setAttribute("aria-pressed", paused ? "true" : "false"); 9365 btn.setAttribute("aria-label", paused ? "åç»ãåç" : "åç»ã忢"); 9366 }; 9367 9368 setUiState(); 9369 9370 video.addEventListener("pause", setUiState); 9371 video.addEventListener("play", setUiState); 9372 video.addEventListener("ended", setUiState); 9373 9374 btn.addEventListener("click", async () => { 9375 try { 9376 if (video.paused) { 9377 const playPromise = video.play(); 9378 if (playPromise && typeof playPromise.then === "function") { 9379 await playPromise; 9380 } 9381 } else { 9382 video.pause(); 9383 } 9384 } catch (e) { 9385 // autoplayå¶éãªã©ã§play()ã失æãããã¨ãããããæ¡ãã¤ã¶ã 9386 } finally { 9387 setUiState(); 9388 } 9389 }); 9390} 9391 9392/** 9393 * ç»é¢å³ä¸åºå®ããã¥ã¼ã¹ãâããã£ãããã®å ¥ãæ¿ã 9394 * - åå: ãã¥ã¼ã¹è¡¨ç¤º 9395 * - fv__news-ticker ããã§ã¼ãããå¾: User Local popup iframe ã表示 9396 * - fv__news-ticker ãè¡¨ç¤ºç¶æ : ãã¥ã¼ã¹ã«åãæ¿ã 9397 * - ãã¬ãã¥ã¼: ?preview=fab ã§5ç§ãã¨ã«åãæ¿ã 9398 */ 9399 9400const PREVIEW_INTERVAL_MS = 5000; 9401const NEWS_TICKER_FADED_CLASS = "is-fv-news-ticker-faded"; 9402const NEWS_TICKER_FADE_DELAY_MS = 120; 9403const WIDGET_IFRAME_SELECTOR = "iframe.ul-widget-main-window"; 9404const EASE_OUT_QUART = "cubic-bezier(0.25, 1, 0.5, 1)"; 9405const SP_MAX_WIDTH = 767; 9406 9407function initFixedNewsChat() { 9408 const isPreviewMode = () => 9409 typeof window !== "undefined" && 9410 window.location.search.includes("preview=fab"); 9411 9412 const setChatVisible = (visible) => { 9413 document.body.classList.toggle("is-fixed-fab-chat", visible); 9414 }; 9415 9416 let showAfterFadeTimer = null; 9417 const clearShowAfterFadeTimer = () => { 9418 if (!showAfterFadeTimer) return; 9419 clearTimeout(showAfterFadeTimer); 9420 showAfterFadeTimer = null; 9421 }; 9422 9423 const updateByTickerFade = () => { 9424 const body = document.body; 9425 const hasTickerFadedClass = body.classList.contains(NEWS_TICKER_FADED_CLASS); 9426 9427 if (!hasTickerFadedClass) { 9428 clearShowAfterFadeTimer(); 9429 setChatVisible(false); 9430 return; 9431 } 9432 9433 // fv__news-ticker ã®ãã§ã¼ãã¢ãã¡å®äºå¾ã«ãã£ããã表示 9434 if (showAfterFadeTimer) return; 9435 showAfterFadeTimer = setTimeout(() => { 9436 setChatVisible(true); 9437 showAfterFadeTimer = null; 9438 }, NEWS_TICKER_FADE_DELAY_MS); 9439 }; 9440 9441 if (isPreviewMode()) { 9442 let previewTimer = null; 9443 const runPreviewToggle = () => { 9444 document.body.classList.toggle("is-fixed-fab-chat"); 9445 previewTimer = setTimeout(runPreviewToggle, PREVIEW_INTERVAL_MS); 9446 }; 9447 previewTimer = setTimeout(runPreviewToggle, PREVIEW_INTERVAL_MS); 9448 window.addEventListener("beforeunload", () => { 9449 if (previewTimer) clearTimeout(previewTimer); 9450 });
9451 } else { 9452 // åæè¡¨ç¤ºã¨ã¹ã¯ãã¼ã«ä¸ã®ä¸¡æ¹ã§ãticker ã®ãã§ã¼ãç¶æ ã«åæ 9453 updateByTickerFade(); 9454 window.addEventListener("scroll", updateByTickerFade, { passive: true }); 9455 const bodyClassObserver = new MutationObserver(updateByTickerFade); 9456 bodyClassObserver.observe(document.body, { 9457 attributes: true, 9458 attributeFilter: ["class"], 9459 }); 9460 } 9461 9462 const isChatVisible = () => document.body.classList.contains("is-fixed-fab-chat"); 9463 9464 const getWidgetIframes = () => 9465 Array.from(document.querySelectorAll(WIDGET_IFRAME_SELECTOR)); 9466 9467 /** 9468 * User Local ã®ãå³ä¸å°åãããã¢ãããã端æ«ä¾åããªãæ¡ä»¶ã§å¤å®ããã 9469 * ï¼åºå®é ç½® + å³ä¸ä»è¿ + å°ãµã¤ãºï¼ 9470 */ 9471 const isPopupIframe = (el) => 9472 el?.tagName === "IFRAME" && 9473 el.classList?.contains("ul-widget-main-window") && 9474 (() => { 9475 const computed = window.getComputedStyle(el); 9476 const rect = el.getBoundingClientRect(); 9477 const width = rect.width || parseFloat(computed.width) || 0; 9478 const height = rect.height || parseFloat(computed.height) || 0; 9479 const right = parseFloat(computed.right) || 0; 9480 const bottom = parseFloat(computed.bottom) || 0; 9481 const isFixed = computed.position === "fixed"; 9482 const isSmall = width > 0 && height > 0 && width <= 360 && height <= 140; 9483 const isDocked = right <= 40 && bottom <= 40; 9484 return isFixed && isSmall && isDocked; 9485 })(); 9486 9487 const syncPopupVisibility = () => { 9488 const visible = isChatVisible(); 9489 const isSp = window.innerWidth <= SP_MAX_WIDTH; 9490 getWidgetIframes() 9491 .filter(isPopupIframe) 9492 .forEach((el) => { 9493 // SPã®ã¿ä¸æ¹åã¹ã©ã¤ããä»ä¸ãPC㯠User Local æ¢å®ã¢ãã¡ã¼ã·ã§ã³ãå®å ¨ç¶æããã 9494 if (isSp) { 9495 el.style.setProperty( 9496 "transition", 9497 `opacity 0.5s ${EASE_OUT_QUART}, transform 0.5s ${EASE_OUT_QUART}, visibility 0s linear`, 9498 "important", 9499 ); 9500 el.style.setProperty("display", "block", "important"); 9501 el.style.setProperty( 9502 "transform", 9503 visible ? "translateY(0)" : "translateY(8px)", 9504 "important", 9505 ); 9506 el.style.setProperty("opacity", visible ? "1" : "0", "important"); 9507 } else { 9508 el.style.removeProperty("transition"); 9509 el.style.removeProperty("display"); 9510 el.style.removeProperty("transform"); 9511 el.style.removeProperty("opacity"); 9512 } 9513 // User Local æ¨æºUIã¯ç¶æãã表示/é表示ã®ã¿å¶å¾¡ãã 9514 el.style.setProperty("visibility", visible ? "visible" : "hidden", "important"); 9515 el.style.setProperty("pointer-events", visible ? "auto" : "none", "important"); 9516 }); 9517 }; 9518 9519 const popupKeeper = new MutationObserver((mutations) => { 9520 const shouldReapply = mutations.some((m) => { 9521 if (m.type === "childList" && m.addedNodes.length) return true; 9522 if (m.type === "attributes" && isPopupIframe(m.target)) return true; 9523 return false; 9524 }); 9525 if (!shouldReapply) return; 9526 popupKeeper.disconnect(); 9527 syncPopupVisibility(); 9528 requestAnimationFrame(() => { 9529 popupKeeper.observe(document.body, { 9530 childList: true, 9531 subtree: true, 9532 attributes: true, 9533 attributeFilter: ["style", "class"], 9534 }); 9535 }); 9536 }); 9537 syncPopupVisibility(); 9538 popupKeeper.observe(document.body, { 9539 childList: true, 9540 subtree: true, 9541 attributes: true, 9542 attributeFilter: ["style", "class"], 9543 }); 9544 window.addEventListener("scroll", syncPopupVisibility, { passive: true }); 9545 9546 // ãã£ããæ¬ä½ãããã° has-chat-open ãåæ 9547 const syncHasChatOpen = () => { 9548 const chatBody = getWidgetIframes().find((el) => !isPopupIframe(el)); 9549 const isOpen = chatBody && !chatBody.classList.contains("ul-widget-hide"); 9550 document.body.classList.toggle("has-chat-open", !!isOpen); 9551 }; 9552 const obs = new MutationObserver(syncHasChatOpen); 9553 obs.observe(document.body, { 9554 childList: true, 9555 subtree: true, 9556 attributes: true, 9557 attributeFilter: ["class", "style"], 9558 }); 9559 syncHasChatOpen(); 9560} 9561 9562// ãã©ã³ãä¸è¦§ã»è±å½¢å ã¹ã¯ãã¼ã«èæ¯ã®ç¸¦ã¹ã±ã¼ã«ï¼Firefox 㯠calc(100vh/rem) ã scale ã«ä½¿ããªããã JS ã§ç
9562¡æ¬¡å åï¼ 9563 9564/** SCSS ã® $brand-list-slice-heightï¼rem(900)ï¼ã¨åæ */ 9565const SLICE_PX_NUMERATOR = 900; 9566const SLICE_REM_DIVISOR = 16; 9567 9568/** 9569 * @param {HTMLElement | null} imageSlider .brand-list__image-slider 9570 */ 9571function applyBrandListImageSliderScale(imageSlider) { 9572 if (!imageSlider) return; 9573 const rootFs = parseFloat( 9574 getComputedStyle(document.documentElement).fontSize, 9575 ); 9576 if (!Number.isFinite(rootFs) || rootFs <= 0) return; 9577 const sliceHeightPx = (SLICE_PX_NUMERATOR / SLICE_REM_DIVISOR) * rootFs; 9578 // iOS Safari ã§ã¯ window.innerHeight / clientHeight ããã¼ã«ãã¼è¡¨ç¤ºã«é£åãã¦å¤åãããã 9579 // CSS ã® vh ã¯ã©ã¼ã¸ãã¥ã¼ãã¼ãï¼ãã¼ã«ãã¼é表示æï¼ãåºæºã¨ããã 9580 // å®éã« height:100vh ã®è¦ç´ ãè¨æ¸¬ãããã¨ã§ CSS vh ã¨ä¸è´ããå¤ãåå¾ããã 9581 const probe = document.createElement("div"); 9582 probe.style.cssText = 9583 "position:fixed;height:100vh;top:0;left:0;visibility:hidden;pointer-events:none"; 9584 document.documentElement.appendChild(probe); 9585 const cssVh100px = probe.getBoundingClientRect().height; 9586 probe.remove(); 9587 const isSp = window.matchMedia("(max-width: 767px)").matches; 9588 const scale = (isSp ? cssVh100px * 0.5 : cssVh100px) / sliceHeightPx; 9589 if (!Number.isFinite(scale) || scale <= 0) return; 9590 imageSlider.style.setProperty("--brand-list-bg-scale", scale.toFixed(5)); 9591} 9592 9593// ======================================== 9594// Brand Listã»ã¯ã·ã§ã³ã®ã¢ãã¡ã¼ã·ã§ã³ 9595// ======================================== 9596 9597function initBrandListAnimation() { 9598 const VISITED_KEY = "matsuya:hasVisited"; 9599 const isReturningVisitor = !!localStorage.getItem(VISITED_KEY); 9600 if (!isReturningVisitor) { 9601 localStorage.setItem(VISITED_KEY, "true"); 9602 } 9603 9604 const brandListSection = document.querySelector(".brand-list"); 9605 const maskSymbol = document.querySelector(".brand-list__mask-symbol"); 9606 const diamondClipPath = document.querySelector( 9607 ".brand-list__diamond-clip-path", 9608 ); 9609 const diamondMaskContainer = document.querySelector( 9610 ".brand-list__diamond-mask-container", 9611 ); 9612 const imageSlider = document.querySelector(".brand-list__image-slider"); 9613 const imageSliderInner = document.querySelector( 9614 ".brand-list__image-slider-inner", 9615 ); 9616 const imageLayer = document.querySelector(".brand-list__image-layer"); 9617 const gallery = document.querySelector(".brand-list__gallery"); 9618 const phase1 = document.querySelector(".brand-list__phase--1"); 9619 const phase2 = document.querySelector(".brand-list__phase--2"); 9620 const phase3 = document.querySelector(".brand-list__phase--3"); 9621 const phase4 = document.querySelector(".brand-list__phase--4"); 9622 const phase5 = document.querySelector(".brand-list__phase--5"); 9623 9624 if ( 9625 !brandListSection || 9626 !maskSymbol || 9627 !diamondClipPath || 9628 !diamondMaskContainer || 9629 !imageSlider || 9630 !imageSliderInner || 9631 !imageLayer || 9632 !phase1 || 9633 !phase2 || 9634 !phase3 || 9635 !phase4 || 9636 !phase5 9637 ) 9638 return; 9639 9640 // 2åç®ä»¥é: éç表示ï¼ã¹ã¯ãã¼ã«ã¢ãã¡ãªãããã©ã©ãã¯ã¹ãªãï¼ 9641 if (isReturningVisitor) { 9642 brandListSection.classList.add("brand-list--static"); 9643 applyBrandListImageSliderScale(imageSlider); 9644 9645 addDebouncedResizeListener(() => { 9646 applyBrandListImageSliderScale(imageSlider); 9647 }, 250); 9648 return; 9649 } 9650 9651 applyBrandListImageSliderScale(imageSlider); 9652 9653 // GSAPã¨ScrollTriggerã®èªã¿è¾¼ã¿ãå¾ ã¤ 9654 const checkGSAP = () => { 9655 if (typeof gsap !== "undefined" && typeof ScrollTrigger !== "undefined") { 9656 gsap.registerPlugin(ScrollTrigger); 9657 9658 applyBrandListImageSliderScale(imageSlider); 9659 9660 const ua = navigator.userAgent; 9661 const isChromeBrowser = 9662 /Chrome\//.test(ua) && !/Edg\//.test(ua) && !/OPR\//.test(ua); 9663 brandListSection.classList.toggle( 9664 "brand-list--chrome-clip-fallback", 9665 isChromeBrowser, 9666 ); 9667 9668 const lenis = window.lenis; 9669 /** 9670 * ç¸¦å¹ ãçãã®ã£ã©ãªã¼ã«å é¨ã¹ã¯ãã¼ã«ãããã¨ããIR ããä¸ã¸æ»ã㨠Phase4 scrub ã 9671 * scrollTop ã progress ã§åãããã¼ã¸ãæ¢ã¾ããåå ¥å ´ä¸ã¯æä¸é¨åºå®ï¼ãã¤ã¼ã«æ¨ªåãè§£é¤ç¨ã 9672 */ 9673 let phase4LockGridBottomAfterIrReentry = false; 9674 /** 9675 * Phase4 ããæåï¼phase3 60%ãï¼ã§ã galleryScrollActive ã«ãªããã¤ã¼ã«æ¨ªåããå§ã¾ãã 9676 * IR ããä¸ã¸æ»ãé㯠Phase4 ããã¯åã«ããã§æ¨ªåããæ¢ãããã¼ã¸ã¹ã¯ãã¼ã«ï¼Lenisï¼ã«ä»»ããã 9677 */ 9678 let bypassGalleryWheelUntilPhase4LeaveBack = false; 9679 9680 // ã«ã¼ããã©ã³ããµã¤ãºãåçã«åå¾ãã¦CSSã®remå¤ã¨ä¸è´ããã 9681 const rootFontSize = parseFloat( 9682 getComputedStyle(document.documentElement).fontSize, 9683 ); 9684 const BASE_WIDTH = Math.round((285 * rootFontSize) / 16); 9685 const BASE_HEIGHT = Math.round((225 * rootFontSize) / 16); 9686 const EXPANDED_SCALE = 50; 9687 const FINAL_WIDTH = BASE_WIDTH * EXPANDED_SCALE; 9688 const FINAL_HEIGHT = BASE_HEIGHT * EXPANDED_SCALE; 9689 9690 const svgElement = diamondClipPath.querySelector("svg"); 9691 9692 // åæç¶æ ãè¨å®ï¼ãã¹ã¦é表示ãåºå®ãµã¤ãºï¼ 9693 gsap.set(maskSymbol, { autoAlpha: 0 }); 9694 9695 gsap.set(diamondClipPath, { 9696 opacity: 0, 9697 width: BASE_WIDTH, 9698 height: BASE_HEIGHT, 9699 scale: 1, 9700 xPercent: -50, 9701 yPercent: -50, 9702 transformOrigin: "center center", 9703 }); 9704 9705 if (svgElement) { 9706 gsap.set(svgElement, { 9707 attr: { 9708 width: BASE_WIDTH, 9709 height: BASE_HEIGHT, 9710 }, 9711 transformOrigin: "center center", 9712 }); 9713 } 9714 9715 gsap.set(diamondMaskContainer, { 9716 opacity: 0, 9717 width: BASE_WIDTH, 9718 height: BASE_HEIGHT, 9719 scale: 1, 9720 xPercent: -50, 9721 yPercent: -50, 9722 transformOrigin: "center center", 9723 }); 9724 9725 // èæ¯ç»åã¬ã¤ã¤ã¼: çªã ãè±å½¢ã¨åæãä¸èº«ï¼ç»åï¼ã¯CSSã¢ãã¡ã®ã¿ã§ã¹ã¯ãã¼ã«éä¾å 9726 gsap.set(imageLayer, { 9727 opacity: 0, 9728 width: BASE_WIDTH, 9729 height: BASE_HEIGHT, 9730 xPercent: -50, 9731 yPercent: -50, 9732 transformOrigin: "center center", 9733 }); 9734 9735 // ================================
9736 // Phase 1: ãã´ï¼ç½ãè±å½¢ï¼ãã§ã¼ãã¤ã³ï¼Phase2 ã§ã¯ãã¹ãã§ã¼ãï¼ 9737 // ================================ 9738 const phase1Timeline = gsap.timeline({ 9739 scrollTrigger: { 9740 trigger: ".vision", 9741 start: "bottom 40%", 9742 end: "bottom -28vh", 9743 // Phase2 㨠maskSymbol ãäºéã¹ã¯ã©ããããªãããé å»¶ãªã 9744 scrub: true, 9745 markers: false, 9746 id: "brand-list-phase1", 9747 }, 9748 }); 9749 9750 // ç½ãã´ã®ã¿ãã§ã¼ãã¤ã³ï¼ãã§ã¼ãã¢ã¦ã㯠Phase 2 ã§ã¯ãã¹ãã§ã¼ãï¼ 9751 // autoAlpha ã使ã visibility ãã¹ã¯ã©ããèªå管çï¼2åç®ä»¥éã®ããã¨è¡¨ç¤ºãé²ãï¼ 9752 phase1Timeline.to(maskSymbol, { 9753 autoAlpha: 1, 9754 ease: "none", 9755 duration: 1, 9756 }); 9757 9758 // ================================ 9759 // Phase 2: ãã´âè±å½¢ãã¹ã¯åæ¿ï¼ã¹ãã¼ãµã¼é«ãã«é£åï¼ 9760 // ================================ 9761 // ãã¼ã«ãå°ºãSCSS ã® phase--2 é«ã㯠38vh à (1 + ãã®å¤) ã¨åæããã㨠9762 const PHASE2_PRE_CROSSFADE_HOLD = 0.24; 9763 const PHASE2_CROSSFADE_LEN = 1; 9764 9765 const phase2Timeline = gsap.timeline({ 9766 scrollTrigger: { 9767 trigger: phase2, 9768 start: "top center", 9769 end: "bottom center", 9770 // æ°å¤ scrubï¼ã©ã°ï¼ã ã¨é«éä¸ã¹ã¯ãã¼ã«æã« maskSymbol ãåãæ®ããããããã trueï¼Phase1 ã¨åæ§ã«å³åæï¼ 9771 scrub: true, 9772 markers: false, 9773 id: "brand-list-phase2", 9774 }, 9775 }); 9776 9777 // ãã¼ã«ãã§ã¯ maskSymbol ã«è§¦ããªãï¼Phase1 ã®ã¿ autoAlphaãäºéã¹ã¯ã©ãã§ä¸ç¬æ¶ããã®ãé²ãï¼ 9778 [diamondClipPath, diamondMaskContainer, imageLayer].forEach((el, i) => { 9779 phase2Timeline.fromTo( 9780 el, 9781 { opacity: 0 }, 9782 { 9783 opacity: 0, 9784 duration: PHASE2_PRE_CROSSFADE_HOLD, 9785 ease: "none", 9786 }, 9787 i === 0 ? 0 : "<", 9788 ); 9789 }); 9790 9791 // ã¯ãã¹ãã§ã¼ã 9792 phase2Timeline.to(maskSymbol, { 9793 autoAlpha: 0, 9794 ease: "power1.inOut", 9795 duration: PHASE2_CROSSFADE_LEN, 9796 immediateRender: false, 9797 }); 9798 phase2Timeline.to( 9799 diamondClipPath, 9800 { opacity: 1, ease: "power1.inOut", duration: PHASE2_CROSSFADE_LEN }, 9801 PHASE2_PRE_CROSSFADE_HOLD, 9802 ); 9803 phase2Timeline.to( 9804 diamondMaskContainer, 9805 { opacity: 1, ease: "power1.inOut", duration: PHASE2_CROSSFADE_LEN }, 9806 PHASE2_PRE_CROSSFADE_HOLD, 9807 ); 9808 phase2Timeline.to( 9809 imageLayer, 9810 { opacity: 1, ease: "power1.inOut", duration: PHASE2_CROSSFADE_LEN }, 9811 PHASE2_PRE_CROSSFADE_HOLD, 9812 ); 9813 9814 // ================================ 9815 // Phase 3: è±å½¢ã®å·¨å¤§åï¼ScrollTrigger + scrubãPhase2 㯠scrub:trueï¼ 9816 // ================================ 9817 const fvBg = document.querySelector(".fv__bg"); 9818 const phase3EndHideTargets = fvBg 9819 ? [diamondClipPath, fvBg] 9820 : [diamondClipPath]; 9821 9822 /** ã¿ã¤ã ã©ã¤ã³ä¸ã®å°ºæ¯ï¼æ§ time ç 1.8s : 0.4s ã«ç¸å½ï¼ */ 9823 const PHASE3_EXPAND_LEN = 1; 9824 /** æ¡å¤§åºéã®ã¤ã¼ã¸ã³ã°ï¼scrub ã§ãåºéå ã®è£éã«å¹ããnone ãªãã¹ã¯ãã¼ã«éã«å¯¾ãç·å½¢ï¼ */ 9825 const PHASE3_EXPAND_EASE = "power2.inOut"; 9826 const PHASE3_FADE_LEN = 0.4 / 1.8; 9827 // Phase2 ã¯ãã¹ãã§ã¼ãï¼opacityï¼ã¨æ¡å¤§ã®è¦ãæ¹ã®é åºãå´©ããªãããã®å é ãã¼ã«ã 9828 const PHASE3_PRE_EXPAND_HOLD = 0.42; 9829 9830 const phase3Timeline = gsap.timeline({ 9831 scrollTrigger: { 9832 trigger: phase3, 9833 start: "top 80%", 9834 end: "bottom center", 9835 scrub: 0.5, 9836 markers: false, 9837 id: "brand-list-phase3", 9838 }, 9839 defaults: { ease: "none" }, 9840 }); 9841 9842 // ãã¼ã«ã: è±å½¢ã»çªãµã¤ãºã¯åæã®ã¾ã¾ï¼Phase2 ã® opacity ã¯ãã¹ãã§ã¼ãå®äºãå¾ ã¤ï¼ 9843 phase3Timeline.fromTo( 9844 diamondClipPath, 9845 { 9846 scale: 1, 9847 xPercent: -50, 9848 yPercent: -50, 9849 transformOrigin: "center center", 9850 }, 9851 { 9852 scale: 1, 9853 xPercent: -50, 9854 yPercent: -50, 9855 transformOrigin: "center center", 9856 duration: PHASE3_PRE_EXPAND_HOLD, 9857 ease: "none", 9858 immediateRender: false, 9859 }, 9860 0, 9861 ); 9862 phase3Timeline.fromTo( 9863 diamondMaskContainer, 9864 { width: BASE_WIDTH, height: BASE_HEIGHT }, 9865 { 9866 width: BASE_WIDTH, 9867 height: BASE_HEIGHT, 9868 duration: PHASE3_PRE_EXPAND_HOLD, 9869 ease: "none", 9870 immediateRender: false, 9871 }, 9872 "<", 9873 ); 9874 phase3Timeline.fromTo( 9875 imageLayer, 9876 { 9877 width: BASE_WIDTH, 9878 height: BASE_HEIGHT, 9879 xPercent: -50, 9880 yPercent: -50, 9881 transformOrigin: "center center", 9882 }, 9883 { 9884 width: BASE_WIDTH, 9885 height: BASE_HEIGHT, 9886 xPercent: -50, 9887 yPercent: -50, 9888 transformOrigin: "center center", 9889 duration: PHASE3_PRE_EXPAND_HOLD, 9890 ease: "none", 9891 immediateRender: false, 9892 }, 9893 "<", 9894 ); 9895 9896 // diamondClipPath 㯠scale ã§æ¡å¤§ï¼ç½è±å½¢ã®å¤å½¢è¡¨ç¤ºç¨ï¼ 9897 phase3Timeline.to(diamondClipPath, { 9898 scale: EXPANDED_SCALE, 9899 xPercent: -50, 9900 yPercent: -50, 9901 transformOrigin: "center center", 9902 duration: PHASE3_EXPAND_LEN, 9903 ease: PHASE3_EXPAND_EASE, 9904 }); 9905 9906 // diamondMaskContainer 㯠width/height ã§æ¡å¤§ãã¦ã¸ã£ã®ã¼ãè»½æ¸ 9907 phase3Timeline.to( 9908 diamondMaskContainer, 9909 { 9910 width: FINAL_WIDTH, 9911 height: FINAL_HEIGHT, 9912 duration: PHASE3_EXPAND_LEN, 9913 ease: PHASE3_EXPAND_EASE, 9914 }, 9915 "<", 9916 ); 9917 9918 // ç»åã¬ã¤ã¤ã¼ã¯çªãµã¤ãº(width/height)ã ãæ¡å¤§ããèæ¯ç»åèªä½ã¯æ¡å¤§ããªã 9919 phase3Timeline.to( 9920 imageLayer, 9921 { 9922 width: FINAL_WIDTH, 9923 height: FINAL_HEIGHT, 9924 xPercent: -50, 9925 yPercent: -50, 9926 transformOrigin: "center center", 9927 duration: PHASE3_EXPAND_LEN, 9928 ease: PHASE3_EXPAND_EASE, 9929 }, 9930 "<", 9931 ); 9932 9933 // æ¡å¤§ã®çµç¤: è±å½¢ã¯ãªããï¼SVGï¼ã¨ FV èæ¯ã®ã¿ãã§ã¼ãã¢ã¦ãï¼ä¸ã¹ã¯ãã¼ã«ã§ scrub éåçï¼ 9934 phase3Timeline.to(phase3EndHideTargets, { 9935 opacity: 0, 9936 duration: PHASE3_FADE_LEN, 9937 ease: "power1.inOut", 9938 }); 9939 9940 // ã®ã£ã©ãªã¼ç³» ScrollTrigger ã®éå§/çµäºï¼è±
9940å½¢æ¡å¤§ TL ã¨ã¯å¥ãstart ã¯ç½ãã´ãã¼ãã¨ã®ã£ã©ãªã¼ã§æããï¼ 9941 const PHASE3_GALLERY_SCROLL_START = "30% center"; 9942 const PHASE3_GALLERY_REVEAL_END = "50% center"; 9943 9944 // ã®ã£ã©ãªã¼ã¹ã©ã¤ãã¤ã³éå§ãPhase5: ç½ãã´ã ãã常ã«é表示ï¼Phase1 scrub çã®å表示ãé²ãï¼ 9945 // diamondClipPath ã¯å«ããªã: Phase3 scrub ã opacity ãå¶å¾¡ããããç«¶åãã 9946 gsap.fromTo( 9947 maskSymbol, 9948 { autoAlpha: 0 }, 9949 { 9950 autoAlpha: 0, 9951 duration: 1, 9952 ease: "none", 9953 immediateRender: false, 9954 scrollTrigger: { 9955 trigger: phase3, 9956 start: PHASE3_GALLERY_SCROLL_START, 9957 end: "bottom center", 9958 scrub: true, 9959 markers: false, 9960 id: "brand-list-gallery-keep-logo-hidden", 9961 }, 9962 }, 9963 ); 9964 9965 // ãã´ãªã¹ããã§ã¼ãã®ã¿è¦ãã縦ã¹ã¯ãã¼ã«ãå¤ãããã¨ã㯠PHASE3_GALLERY_REVEAL_END ãèª¿æ´ 9966 // ã®ã£ã©ãªã¼ scrub(PHASE3_GALLERY_SCROLL_STARTã)ããé ããæ¡å¤§ã§åç»ãè¦ããã¾ã§éãã£ã«ã¿ã¼ãç¶æ 9967 const PHASE3_REMOVE_BLUE_START = "50% center"; 9968 9969 const applyGalleryVisibleNoBlue = () => { 9970 brandListSection.classList.add("brand-list--gallery-visible"); 9971 gsap.set(diamondMaskContainer, { backgroundColor: "transparent" }); 9972 }; 9973 9974 ScrollTrigger.create({ 9975 trigger: phase3, 9976 start: PHASE3_REMOVE_BLUE_START, 9977 end: "bottom center", 9978 onEnter: applyGalleryVisibleNoBlue, 9979 onEnterBack: applyGalleryVisibleNoBlue, 9980 onLeaveBack: () => { 9981 brandListSection.classList.remove("brand-list--gallery-visible"); 9982 gsap.set(diamondMaskContainer, { clearProps: "backgroundColor" }); 9983 }, 9984 markers: false, 9985 id: "brand-list-gallery-hide-blue", 9986 }); 9987 9988 // ã®ã£ã©ãªã¼ã¯ scrub ã§åæï¼å¾å¾©ã§ onEnter ç³»ã ãã ã¨è¡¨ç¤ºããããï¼ 9989 if (gallery) { 9990 const galleryGrid = gallery.querySelector(".brand-list__gallery-grid"); 9991 const setGalleryPointerEvents = (v) => () => 9992 gsap.set(gallery, { pointerEvents: v }); 9993 9994 gsap.set(gallery, { 9995 opacity: 0, 9996 x: "25%", 9997 transformOrigin: "right center", 9998 pointerEvents: "none", 9999 }); 10000 10001 gsap.fromTo( 10002 gallery, 10003 { 10004 opacity: 0, 10005 x: "25%", 10006 transformOrigin: "right center", 10007 }, 10008 { 10009 opacity: 1, 10010 x: "0%", 10011 transformOrigin: "right center", 10012 duration: 1, 10013 ease: "power2.out", 10014 immediateRender: false, 10015 scrollTrigger: { 10016 trigger: phase3, 10017 start: PHASE3_GALLERY_SCROLL_START, 10018 end: PHASE3_GALLERY_REVEAL_END, 10019 scrub: 0.55, 10020 markers: false, 10021 id: "brand-list-gallery-reveal", 10022 }, 10023 }, 10024 ); 10025 10026 ScrollTrigger.create({ 10027 trigger: phase3, 10028 start: "60% center", 10029 end: "61% center", 10030 onEnter: setGalleryPointerEvents("auto"), 10031 onEnterBack: setGalleryPointerEvents("auto"), 10032 onLeaveBack: setGalleryPointerEvents("none"), 10033 markers: false, 10034 id: "brand-list-gallery-pointer-events", 10035 }); 10036 10037 if (galleryGrid && lenis) { 10038 galleryGrid.setAttribute("data-lenis-prevent", ""); 10039 10040 // ã«ã¼ã½ã«ä½ç½®ã«é¢ããããã¼ã¸ã¹ã¯ãã¼ã«ã§ãã´ãªã¹ããã¹ã¯ãã¼ã« 10041 let galleryScrollActive = false; 10042 let targetScrollTop = 0; // ã¢ãã¡ã¼ã·ã§ã³ç®æ¨ä½ç½®ï¼ãã¤ã¼ã«æä½ã§å ç®ï¼ 10043 let galleryScrollTween = null; // å®è¡ä¸ã®GSAPãã¥ã¤ã¼ã³ 10044 let hasSkippedToNext = false; // ãã´ãè¦çµãã£ãå¾ã®æ¬¡ã»ã¯ã·ã§ã³ã¹ãããæ¸ã¿ãã©ã° 10045 10046 // ã®ã£ã©ãªã¼ãå®å ¨ã«è¡¨ç¤ºãããåºéã§ãã©ã°ã管ç 10047 ScrollTrigger.create({ 10048 trigger: phase3, 10049 start: "60% center", // å°ãé ããã¦ã¹ã¯ãã¼ã«å¶å¾¡ã®åãæ¿ããèªç¶ã« 10050 end: "bottom top", 10051 endTrigger: brandListSection, 10052 onEnter: () => { 10053 galleryScrollActive = true; 10054 lenis.scrollTo(lenis.scroll, { duration: 0.3 }); 10055 }, 10056 onLeave: () => { 10057 galleryScrollActive = false; 10058 hasSkippedToNext = false; 10059 // IR æ¹åã¸æããã¨ãã¯ãä¸è¦§ãè¦çµãããä½ç½®ï¼æä¸é¨ã«åºå®ï¼æ¬¡ã«ä¸ããæ»ã£ãã¨ãå é âæ«å°¾ã®å é¨ã¹ã¯ãã¼ã«ã§ãã¼ã¸ãæ¢ã¾ããªãï¼ 10060 const maxOnLeave = 10061 galleryGrid.scrollHeight - galleryGrid.clientHeight; 10062 if (maxOnLeave > 0) { 10063 galleryGrid.scrollTop = maxOnLeave; 10064 targetScrollTop = maxOnLeave; 10065 } else { 10066 galleryGrid.scrollTop = 0; 10067 targetScrollTop = 0; 10068 } 10069 galleryGrid.setAttribute("data-lenis-prevent", ""); // data-lenis-preventã復å 10070 }, 10071 onEnterBack: () => { 10072 galleryScrollActive = true; 10073 hasSkippedToNext = false; 10074 galleryGrid.setAttribute("data-lenis-prevent", ""); 10075 lenis.scrollTo(lenis.scroll, { immediate: true });
10076 const snapGridToBottom = () => { 10077 const maxScroll = 10078 galleryGrid.scrollHeight - galleryGrid.clientHeight; 10079 if (maxScroll > 0) { 10080 galleryGrid.scrollTop = maxScroll; 10081 targetScrollTop = maxScroll; 10082 } else { 10083 targetScrollTop = 0; 10084 } 10085 }; 10086 snapGridToBottom(); 10087 // ã¬ã¤ã¢ã¦ã確å®å¾ã«åé©ç¨ï¼åãã¬ã¼ã ã ã 0 ã®ã¾ã¾ã«ãªãã®ãé²ãï¼ 10088 requestAnimationFrame(snapGridToBottom); 10089 if (bypassGalleryWheelUntilPhase4LeaveBack && lenis) { 10090 galleryGrid.removeAttribute("data-lenis-prevent"); 10091 } 10092 }, 10093 onLeaveBack: () => { 10094 galleryScrollActive = false; 10095 targetScrollTop = 0; 10096 hasSkippedToNext = false; 10097 galleryGrid.scrollTop = 0; 10098 galleryGrid.setAttribute("data-lenis-prevent", ""); // data-lenis-preventã復å 10099 }, 10100 markers: false, 10101 id: "brand-list-gallery-scroll-control", 10102 }); 10103 10104 // ãã¼ã¸å ¨ä½ã®wheelã¤ãã³ãããã£ããã£ï¼ã«ã¼ã½ã«ããã´ãªã¹ãå¤ã§ãåä½ï¼ 10105 window.addEventListener( 10106 "wheel", 10107 (e) => { 10108 // IR ããæ»ã Phase4 ã§æä¸é¨ããã¯ä¸ã¯ãã¼ã¸ã¹ã¯ãã¼ã«ï¼Lenisï¼ã«ä»»ãã 10109 if (phase4LockGridBottomAfterIrReentry) return; 10110 // Phase4 å ¥ãåã®ã¾ã¼ã³ã§ã¯ galleryScrollActive ã®ã¿ true ã§æ¨ªåããããã®ãé²ã 10111 if (bypassGalleryWheelUntilPhase4LeaveBack) return; 10112 if (!galleryScrollActive) return; 10113 10114 // galleryScrollActiveä¸ã¯å¸¸ã«ãã¤ãã£ãã¹ã¯ãã¼ã«ãé²ã 10115 e.preventDefault(); 10116 e.stopPropagation(); 10117 10118 const hasOverflow = 10119 galleryGrid.scrollHeight > galleryGrid.clientHeight + 1; 10120 const maxScroll = 10121 galleryGrid.scrollHeight - galleryGrid.clientHeight; 10122 10123 // 端ã®å¤å®ã¯ã¢ãã¡ã¼ã·ã§ã³ç®æ¨å¤ã§è¡ãï¼æ £æ§ä¸ãæ£ç¢ºã«åä½ãããããï¼ 10124 const atBottom = !hasOverflow || targetScrollTop >= maxScroll - 1; 10125 const atTop = targetScrollTop <= 0; 10126 10127 if (e.deltaY > 0 && atBottom) { 10128 if (!hasSkippedToNext) { 10129 hasSkippedToNext = true; 10130 galleryScrollActive = false; 10131 galleryGrid.removeAttribute("data-lenis-prevent"); 10132 const nextSection = document.querySelector( 10133 ".main-content-block", 10134 ); 10135 if (nextSection) { 10136 lenis.scrollTo(nextSection, { duration: 1.2, offset: 0 }); 10137 } 10138 } 10139 return; 10140 } 10141 10142 if (e.deltaY < 0 && atTop) { 10143 galleryScrollActive = false; 10144 lenis.scrollTo(window.scrollY + e.deltaY * 2, { 10145 duration: 0.8, 10146 }); 10147 return; 10148 } 10149 10150 if (!hasOverflow) return; 10151 10152 // ãã´ãªã¹ãã«ã¾ã ã¹ã¯ãã¼ã«ä½å°ããã â GSAPã§Lenisã«è¿ãåãã§ã¹ã¯ãã¼ã« 10153 targetScrollTop = Math.max( 10154 0, 10155 Math.min(maxScroll, targetScrollTop + e.deltaY), 10156 ); 10157 10158 if (galleryScrollTween) galleryScrollTween.kill(); 10159 galleryScrollTween = gsap.to(galleryGrid, { 10160 scrollTop: targetScrollTop, 10161 duration: 1.0, 10162 ease: "power3.out", 10163 overwrite: true, 10164 }); 10165 }, 10166 { passive: false, capture: true }, 10167 ); 10168 } 10169 10170 if (galleryGrid && phase4) { 10171 const phase4HeightVh = () => { 10172 const vh = window.innerHeight; 10173 const gh = galleryGrid.scrollHeight; 10174 if (gh <= vh) return 20; 10175 return Math.min(200, Math.max(50, (gh - vh) * 0.8)); 10176 }; 10177 10178 phase4.style.height = `${phase4HeightVh()}vh`; 10179 let phase4GalleryScrollStart = 0; 10180 10181 const clampGalleryScrollStart = () => { 10182 const maxScroll = 10183 galleryGrid.scrollHeight - galleryGrid.clientHeight; 10184 if (maxScroll <= 0) return 0; 10185 return Math.min(Math.max(0, galleryGrid.scrollTop), maxScroll); 10186 }; 10187 10188 ScrollTrigger.create({ 10189 trigger: phase4, 10190 start: "top center", 10191 end: "bottom center", 10192 scrub: true, 10193 markers: false, 10194 id: "phase4-gallery-scroll", 10195 onUpdate: (self) => { 10196 const maxScroll = 10197 galleryGrid.scrollHeight - galleryGrid.clientHeight; 10198 if (maxScroll <= 0) return; 10199 if (phase4LockGridBottomAfterIrReentry) { 10200 galleryGrid.scrollTop = maxScroll; 10201 return; 10202 } 10203 galleryGrid.scrollTop = 10204 phase4GalleryScrollStart + 10205 (maxScroll - phase4GalleryScrollStart) * self.progress; 10206 }, 10207 onEnter: () => { 10208 galleryGrid.style.overflowY = "hidden"; 10209 phase4LockGridBottomAfterIrReentry = false; 10210 if (lenis) galleryGrid.setAttribute("data-lenis-prevent", ""); 10211 phase4GalleryScrollStart = clampGalleryScrollStart(); 10212 }, 10213 onLeave: () => { 10214 galleryGrid.style.overflowY = "auto"; 10215 phase4LockGridBottomAfterIrReentry = false; 10216 if (lenis) galleryGrid.setAttribute("data-lenis-prevent", ""); 10217 }, 10218 onLeaveBack: () => { 10219 galleryGrid.style.overflowY = "hidden"; 10220 phase4LockGridBottomAfterIrReentry = false; 10221 bypassGalleryWheelUntilPhase4LeaveBack = false; 10222 if (lenis) galleryGrid.setAttribute("data-lenis-prevent", ""); 10223 }, 10224 onEnterBack: () => { 10225 galleryGrid.style.overflowY = "hidden"; 10226 const maxScroll = 10227 galleryGrid.scrollHeight - galleryGrid.clientHeight; 10228 phase4GalleryScrollStart = maxScroll > 0 ? maxScroll : 0; 10229 if (maxScroll > 0) { 10230 galleryGrid.scrollTop = maxScroll; 10231 phase4LockGridBottomAfterIrReentry = true; 10232 bypassGalleryWheelUntilPhase4LeaveBack = false; 10233 requestAnimationFrame(() => { 10234 requestAnimationFrame(() => { 10235 if (phase4LockGridBottomAfterIrReentry && lenis) { 10236 galleryGrid.removeAttribute("data-lenis-prevent"); 10237 } 10238 }); 10239 }); 10240 } else { 10241 phase4LockGridBottomAfterIrReentry = false; 10242 } 10243 }, 10244 }); 10245 10246 window.addEventListener("resize", () => { 10247 phase4.style.height = `${phase4HeightVh()}vh`; 10248 ScrollTrigger.refresh();
10249 }); 10250 } 10251 } 10252 10253 // IRã»ã¯ã·ã§ã³ã¯é常ã®DOMé ç½®ï¼Phase 4å®äºå¾ã«æåã¹ã¯ãã¼ã«ã§è¡¨ç¤ºï¼ 10254 const mainContentBlock = document.querySelector(".main-content-block"); 10255 if (mainContentBlock) { 10256 gsap.set(mainContentBlock, { clearProps: "all" }); 10257 } 10258 10259 // Phase 5: diamondClipPath ã®ã¿ãã§ã¼ãï¼CTA ãä¸ã«è¢«ããï¼ 10260 gsap.to(diamondClipPath, { 10261 opacity: 0, 10262 ease: "none", 10263 duration: 1, 10264 scrollTrigger: { 10265 trigger: phase5, 10266 start: "top center", 10267 end: "bottom center", 10268 scrub: true, 10269 markers: false, 10270 id: "brand-list-phase5-fadeout", 10271 }, 10272 }); 10273 10274 // ã¦ã£ã³ãã¦ãªãµã¤ãºæã«åè¨ç® 10275 addDebouncedResizeListener(() => { 10276 applyBrandListImageSliderScale(imageSlider); 10277 scheduleScrollTriggerRefresh(); 10278 // Lenisããªãµã¤ãºæã«æ´æ° 10279 if (lenis) { 10280 lenis.resize(); 10281 } 10282 }, 250); 10283 10284 const hideBrandListFixedLayers = () => { 10285 bypassGalleryWheelUntilPhase4LeaveBack = false; 10286 document.documentElement.classList.remove("is-brand-list-active"); 10287 brandListSection.classList.remove("brand-list--gallery-visible"); 10288 gsap.set(diamondMaskContainer, { clearProps: "backgroundColor" }); 10289 gsap.set([diamondClipPath, diamondMaskContainer, imageLayer], { 10290 opacity: 0, 10291 pointerEvents: "none", 10292 }); 10293 gsap.set(maskSymbol, { autoAlpha: 0, pointerEvents: "none" }); 10294 if (gallery) { 10295 const gg = gallery.querySelector(".brand-list__gallery-grid"); 10296 if (gg) { 10297 gg.removeAttribute("data-lenis-prevent"); 10298 } 10299 gsap.set(gallery, { 10300 visibility: "hidden", 10301 opacity: 0, 10302 pointerEvents: "none", 10303 }); 10304 } 10305 }; 10306 10307 ScrollTrigger.create({ 10308 trigger: brandListSection, 10309 start: "top top", 10310 end: "bottom top", 10311 onEnter: () => { 10312 bypassGalleryWheelUntilPhase4LeaveBack = false; 10313 document.documentElement.classList.add("is-brand-list-active"); 10314 gsap.set([diamondClipPath, diamondMaskContainer, imageLayer], { 10315 visibility: "visible", 10316 pointerEvents: "none", 10317 }); 10318 if (gallery) { 10319 gsap.set(gallery, { visibility: "visible", pointerEvents: "none" }); 10320 scheduleScrollTriggerRefresh(); 10321 } 10322 }, 10323 onLeave: hideBrandListFixedLayers, 10324 onLeaveBack: hideBrandListFixedLayers, 10325 onEnterBack: () => { 10326 document.documentElement.classList.add("is-brand-list-active"); 10327 // ä¸ããåå ¥å ´ï¼IR å´ããï¼ï¼å é¨ã¹ã¯ãã¼ã«ãããªã Phase4 ã«å ¥ãã¾ã§ãã¤ã¼ã«æ¨ªåãããªã 10328 if (lenis && gallery) { 10329 const gg = gallery.querySelector(".brand-list__gallery-grid"); 10330 if (gg) { 10331 const mx = gg.scrollHeight - gg.clientHeight; 10332 bypassGalleryWheelUntilPhase4LeaveBack = mx > 1; 10333 if (bypassGalleryWheelUntilPhase4LeaveBack) { 10334 gg.removeAttribute("data-lenis-prevent"); 10335 requestAnimationFrame(() => { 10336 requestAnimationFrame(() => { 10337 if (bypassGalleryWheelUntilPhase4LeaveBack && lenis) { 10338 gg.removeAttribute("data-lenis-prevent"); 10339 } 10340 }); 10341 }); 10342 } 10343 } 10344 } 10345 // maskSymbolã®opacityã¯Phase1 scrubã«å®å ¨ã«å§ãã 10346 gsap.set([diamondClipPath, diamondMaskContainer, imageLayer], { 10347 opacity: 1, 10348 pointerEvents: "none", 10349 }); 10350 gsap.set(maskSymbol, { 10351 visibility: "visible", 10352 pointerEvents: "none", 10353 }); 10354 if (gallery) { 10355 gsap.set(gallery, { visibility: "visible", pointerEvents: "none" }); 10356 scheduleScrollTriggerRefresh();
10357 } 10358 }, 10359 markers: false, 10360 id: "brand-list-leave-hide", 10361 }); 10362 } else { 10363 // GSAPãã¾ã èªã¿è¾¼ã¾ãã¦ããªãå ´åãå°ãå¾ ã£ã¦ããåè©¦è¡ 10364 setTimeout(checkGSAP, 50); 10365 } 10366 }; 10367 10368 checkGSAP(); 10369} 10370 10371// ======================================== 10372// Brand Listã»ã¯ã·ã§ã³ã®ã¢ãã¡ã¼ã·ã§ã³ï¼SPçï¼ 10373// ======================================== 10374 10375/** SP: çª opacity ãã§ã¼ãï¼_brand-list.scss .brand-list__image-layer 0.6s ã¨åæï¼ */ 10376const SP_MASK_FADE_IN_MS = 600; 10377 10378function initBrandListAnimationSP() { 10379 const brandListSection = document.querySelector(".brand-list"); 10380 const imageLayer = document.querySelector(".brand-list__image-layer"); 10381 const imageSlider = document.querySelector(".brand-list__image-slider"); 10382 if (!brandListSection) return; 10383 10384 // SPçã®åºæ¬è¨å® 10385 brandListSection.classList.add("brand-list--sp-static"); 10386 brandListSection.classList.remove("brand-list--sp-logo-diamond"); 10387 brandListSection.classList.remove("brand-list--sp-bg-fixed"); 10388 10389 if (imageSlider) { 10390 applyBrandListImageSliderScale(imageSlider); 10391 } 10392 10393 let hasActivated = false; 10394 let hasBackgroundActivated = false; 10395 let diamondTimer = null; 10396 10397 const updateLogoVisibility = () => { 10398 if (window.innerWidth >= 768) { 10399 // PCçã«åãæ¿ãã£ãå ´åã¯SPçã®ã¯ã©ã¹ãåé¤ 10400 brandListSection.classList.remove("brand-list--sp-logo-diamond"); 10401 brandListSection.classList.remove("brand-list--sp-bg-fixed"); 10402 hasActivated = false; 10403 hasBackgroundActivated = false; 10404 if (diamondTimer) { 10405 clearTimeout(diamondTimer); 10406 diamondTimer = null; 10407 } 10408 return; 10409 } 10410 10411 const rect = brandListSection.getBoundingClientRect(); 10412 const viewportHeight = window.innerHeight || document.documentElement.clientHeight; 10413 const visibleTop = Math.max(rect.top, 0); 10414 const visibleBottom = Math.min(rect.bottom, viewportHeight); 10415 const visibleHeight = Math.max(0, visibleBottom - visibleTop); 10416 const visibleRatio = rect.height > 0 ? visibleHeight / rect.height : 0; 10417 10418 if (!hasActivated && visibleRatio >= 0.3) { 10419 hasActivated = true; 10420 brandListSection.classList.add("brand-list--sp-logo-diamond"); 10421 10422 diamondTimer = setTimeout(() => { 10423 diamondTimer = null; 10424 if (!hasBackgroundActivated && imageLayer && imageSlider) { 10425 // ãã§ã¼ãå®äºå¾ã¯ width/height ã®ã¿ï¼opacity 㯠CSS ã§ 1 æ¸ã¿ï¼ 10426 imageLayer.style.transition = 10427 "width 2s cubic-bezier(0.65, 0, 0.35, 1), height 2s cubic-bezier(0.65, 0, 0.35, 1)"; 10428 imageLayer.style.position = "absolute"; 10429 imageLayer.style.top = "25vh"; 10430 imageLayer.style.left = "50%"; 10431 imageLayer.style.transform = "translate(-50%, -50%)"; 10432 10433 imageLayer.style.width = "300vw"; 10434 imageLayer.style.height = "300vh"; 10435 10436 imageSlider.style.position = "absolute"; 10437 imageSlider.style.top = "50%"; 10438 imageSlider.style.left = "50%"; 10439 applyBrandListImageSliderScale(imageSlider); 10440 10441 hasBackgroundActivated = true; 10442 } 10443 }, SP_MASK_FADE_IN_MS); 10444 } 10445 }; 10446 10447 // ã¹ã¯ãã¼ã«ç£è¦ 10448 updateLogoVisibility(); 10449 window.addEventListener("scroll", updateLogoVisibility, { passive: true }); 10450 10451 // ãªãµã¤ãºå¯¾å¿ 10452 addDebouncedResizeListener(() => { 10453 if (window.innerWidth >= 768) { 10454 brandListSection.classList.remove("brand-list--sp-static"); 10455 brandListSection.classList.remove("brand-list--sp-logo-diamond"); 10456 brandListSection.classList.remove("brand-list--sp-bg-fixed"); 10457 scheduleScrollTriggerRefresh(); 10458 return; 10459 } 10460 brandListSection.classList.add("brand-list--sp-static"); 10461 if (imageSlider) { 10462 applyBrandListImageSliderScale(imageSlider); 10463 } 10464 updateLogoVisibility(); 10465 }, 250); 10466} 10467 10468function initSustainabilityKvHeaderAnimation(onInview) { 10469 const header = document.querySelector(".sustainability__kv-header"); 10470 const title = document.querySelector( 10471 ".sustainability__kv-header .sustainability__kv-title", 10472 );
10473 const subtitle = document.querySelector( 10474 ".sustainability__kv-header .sustainability__kv-subtitle", 10475 ); 10476 if (!header || !title) return; 10477 10478 const titleText = title.textContent.trim().replace(/\s+/g, " "); 10479 title.textContent = ""; 10480 titleText.split("").forEach((char, index) => { 10481 const span = document.createElement("span"); 10482 span.className = char === " " ? "char space" : "char"; 10483 span.textContent = char; 10484 span.style.setProperty("--char-delay", `${index * 0.05}s`); 10485 title.appendChild(span); 10486 }); 10487 10488 if (subtitle) { 10489 const lineText = subtitle.textContent.trim(); 10490 if (lineText) { 10491 subtitle.textContent = ""; 10492 const wrapper = document.createElement("span"); 10493 wrapper.className = "sustainability__kv-subtitle-line-wrapper"; 10494 wrapper.style.setProperty("--line-delay", "0.25s"); 10495 const line = document.createElement("span"); 10496 line.className = "sustainability__kv-subtitle-line"; 10497 line.textContent = lineText; 10498 wrapper.appendChild(line); 10499 subtitle.appendChild(wrapper); 10500 } 10501 } 10502 10503 let headerRevealScheduled = false; 10504 const applyInview = () => { 10505 if (headerRevealScheduled) return; 10506 headerRevealScheduled = true; 10507 title.classList.add("is-inview"); 10508 if (subtitle) subtitle.classList.add("is-inview"); 10509 if (typeof onInview === "function") { 10510 onInview(); 10511 } 10512 }; 10513 10514 const observer = new IntersectionObserver( 10515 (entries) => { 10516 entries.forEach((entry) => { 10517 if (entry.isIntersecting) { 10518 applyInview(); 10519 observer.disconnect(); // ä¸åº¦çºç«ãããè§£é¤ 10520 } 10521 }); 10522 }, 10523 { rootMargin: "0px", threshold: 0.2 } 10524 ); 10525 observer.observe(header); 10526 10527 // â Safari対ç: æ¢ã«ãã¥ã¼ãã¼ãå ã«ããå ´åã®å³æçºç« 10528 const check = () => { 10529 const rect = header.getBoundingClientRect(); 10530 if (rect.top < window.innerHeight * 0.9 && rect.bottom > 0) { 10531 applyInview(); 10532 } 10533 }; 10534 // DOMContentLoadedç´å¾ + loadå¾ã®2åãã§ã㯠10535 check(); 10536 window.addEventListener("load", () => { 10537 check(); 10538 // Safariã¯ãã©ã³ããã¼ãå¾ã«ã¬ã¤ã¢ã¦ããå¤ãããã¨ãããã®ã§å°ãé å»¶ã 10539 setTimeout(check, 300); 10540 }, { once: true }); 10541} 10542 10543function initSustainabilityKv() { 10544 if (window.innerWidth < 768) { 10545 initSustainabilityKvHeaderAnimation(); 10546 return; 10547 } 10548 10549 const wrapper = document.querySelector(".sustainability__kv-image-wrapper"); 10550 const img = document.querySelector( 10551 ".sustainability__kv-image-wrapper .sustainability__kv-image", 10552 ); 10553 if (!wrapper || !img) { 10554 initSustainabilityKvHeaderAnimation(); 10555 return; 10556 } 10557 10558 const descriptionEl = document.querySelector(".sustainability__kv-description"); 10559 const descriptionParagraphs = document.querySelectorAll( 10560 ".sustainability__kv-description p", 10561 ); 10562 10563 if (typeof gsap === "undefined") { 10564 initSustainabilityKvHeaderAnimation(); 10565 return; 10566 } 10567 10568 gsap.set(img, { clipPath: "inset(0 0 100% 0)" }); 10569 if (descriptionEl && descriptionParagraphs.length > 0) { 10570 gsap.set(descriptionParagraphs, { clipPath: "inset(0 0 100% 0)" }); 10571 } 10572 10573 const IMAGE_REVEAL_DURATION = 2; 10574 10575 const revealDescription = () => { 10576 if (!descriptionEl || descriptionParagraphs.length === 0) return; 10577 gsap.to(descriptionParagraphs, { 10578 clipPath: "inset(0 0 0% 0)", 10579 duration: 1.2, 10580 ease: "power4.out", 10581 stagger: 0.15, 10582 }); 10583 }; 10584 10585 // ç»é¢ã«å ¥ã£ãããããã¼ã¨ç»åãåæéå§ â ç»åãååã¾ã§åºãã説ææåºç¾ 10586 const onInview = () => { 10587 const tl = gsap.timeline(); 10588 tl.to(img, { 10589 clipPath: "inset(0 0 0% 0)", 10590 duration: IMAGE_REVEAL_DURATION, 10591 ease: "power4.out", 10592 }); 10593 tl.add(revealDescription, IMAGE_REVEAL_DURATION * 0.5); 10594 }; 10595 10596 initSustainabilityKvHeaderAnimation(onInview); 10597} 10598 10599function initSustainabilityAccordion() { 10600 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") { 10601 return; 10602 } 10603 10604 if (window.innerWidth <= 767) { 10605 return; 10606 } 10607 10608 gsap.registerPlugin(ScrollTrigger); 10609 10610 const container = document.querySelector(".sustainability__scroll-accordion"); 10611 const items = document.querySelectorAll( 10612 ".sustainability__scroll-accordion-item", 10613 );
10614 const bodies = document.querySelectorAll( 10615 ".sustainability__scroll-accordion-item .sustainability__scroll-accordion-body", 10616 ); 10617 const images = document.querySelectorAll( 10618 ".sustainability__scroll-accordion-item .sustainability__scroll-accordion-image", 10619 ); 10620 const textBlocks = document.querySelectorAll( 10621 ".sustainability__scroll-accordion-item .sustainability__scroll-accordion-text", 10622 ); 10623 const contentBlocks = document.querySelectorAll( 10624 ".sustainability__scroll-accordion-item .sustainability__scroll-accordion-content", 10625 ); 10626 10627 if ( 10628 !container || 10629 items.length === 0 || 10630 bodies.length === 0 || 10631 images.length === 0 10632 ) { 10633 return; 10634 } 10635 10636 let tl = null; 10637 10638 const COLLAPSED_ITEM_HEIGHT_PC = 144; 10639 const MID_ITEM_HEIGHT_PC = 373; 10640 const EXPANDED_ITEM_HEIGHT_PC = 413; 10641 const COLLAPSED_IMAGE_HEIGHT_PC = 104; 10642 const EXPANDED_IMAGE_HEIGHT_PC = 333; 10643 const COLLAPSED_IMAGE_HEIGHT_SP = 60; 10644 const EXPANDED_IMAGE_HEIGHT_SP = 120; 10645 const COLLAPSED_PADDING_TOP_PC = 40; 10646 const COLLAPSED_PADDING_BOTTOM_PC = 0; 10647 const EXPANDED_PADDING_TOP_PC = 40; 10648 const EXPANDED_PADDING_BOTTOM_PC = 40; 10649 10650 // SPå¤å®ãä¸ç®æã«éç´ï¼ãªãµã¤ãºæã®ä¸æ´åãé²ãï¼ 10651 const isPc = () => window.innerWidth > 767; 10652 const getRootScale = () => { 10653 const rootFontSize = parseFloat( 10654 window.getComputedStyle(document.documentElement).fontSize, 10655 ); 10656 if (!Number.isFinite(rootFontSize) || rootFontSize <= 0) return 1; 10657 return rootFontSize / 16; 10658 }; 10659 10660 // å¼·å¶çã«ã¬ã¤ã¢ã¦ãã確å®ããã¦ããscrollHeightãèªã 10661 const forceReflow = (el) => el.getBoundingClientRect(); 10662 10663 const getHeights = () => { 10664 const bodyHeights = []; 10665 const titleHeights = []; 10666 10667 items.forEach((item, index) => { 10668 const titleEl = item.querySelector( 10669 ".sustainability__scroll-accordion-title", 10670 ); 10671 const bodyEl = bodies[index]; 10672 const imageEl = images[index]; 10673 const textEl = textBlocks[index]; 10674 const contentEl = contentBlocks[index]; 10675 10676 if (titleEl) { 10677 titleHeights.push(titleEl.offsetHeight); 10678 } 10679 10680 if (bodyEl) { 10681 // ãSafari対çãheight: auto ã«ããå¾ãgetBoundingClientRect()ã§ 10682 // ã¬ã¤ã¢ã¦ããå¼·å¶ç¢ºå®ãã¦ããscrollHeightãèªã 10683 gsap.set(bodyEl, { height: "auto", overflow: "visible" }); 10684 forceReflow(bodyEl); 10685 bodyHeights.push(bodyEl.scrollHeight); 10686 } 10687 10688 if (imageEl) { 10689 gsap.set(imageEl, { height: "auto", overflow: "hidden" }); 10690 forceReflow(imageEl); 10691 } 10692 10693 if (textEl) { 10694 gsap.set(textEl, { clearProps: "height,overflow" }); 10695 forceReflow(textEl); 10696 } 10697 10698 if (contentEl) { 10699 gsap.set(contentEl, { clearProps: "height,overflow" }); 10700 forceReflow(contentEl); 10701 } 10702 }); 10703 10704 return { 10705 bodyHeights, 10706 titleHeights, 10707 }; 10708 }; 10709 10710 const setCollapsedState = () => { 10711 const rootScale = getRootScale(); 10712 const collapsedImageHeight = isPc() 10713 ? COLLAPSED_IMAGE_HEIGHT_PC * rootScale 10714 : COLLAPSED_IMAGE_HEIGHT_SP; 10715 const collapsedPaddingTop = isPc() ? COLLAPSED_PADDING_TOP_PC * rootScale : 20; 10716 const collapsedPaddingBottom = isPc() 10717 ? COLLAPSED_PADDING_BOTTOM_PC * rootScale 10718 : 20; 10719 10720 contentBlocks.forEach((el) => { 10721 gsap.set(el, { clearProps: "height,overflow" }); 10722 }); 10723 10724 textBlocks.forEach((el) => { 10725 gsap.set(el, { clearProps: "height,overflow" }); 10726 }); 10727 10728 bodies.forEach((el) => { 10729 gsap.set(el, { height: 0, overflow: "visible" }); 10730 }); 10731 10732 images.forEach((el) => { 10733 gsap.set(el, { 10734 height: collapsedImageHeight, 10735 overflow: "hidden", 10736 }); 10737 const photoEl = el.querySelector( 10738 ".sustainability__scroll-accordion-image-photo", 10739 ); 10740 if (photoEl) { 10741 gsap.set(photoEl, { borderRadius: 0 }); 10742 } 10743 }); 10744 10745 items.forEach((item, index) => { 10746 const itemHeight = isPc() 10747 ? COLLAPSED_ITEM_HEIGHT_PC * rootScale 10748 : (() => { 10749 const numEl = item.querySelector( 10750 ".sustainability__scroll-accordion-num", 10751 ); 10752 const titleEl = item.querySelector( 10753 ".sustainability__scroll-accordion-title", 10754 ); 10755 const titleHeight = titleEl ? titleEl.offsetHeight : 0; 10756 const numHeight = numEl ? numEl.offsetHeight : 0; 10757 const imageHeight = collapsedImageHeight; 10758 const rowHeight = Math.max(titleHeight, numHeight, imageHeight); 10759 return rowHeight + 40; 10760 })(); 10761 gsap.set(item, { 10762 height: itemHeight, 10763 paddingTop: collapsedPaddingTop, 10764 paddingBottom: collapsedPaddingBottom, 10765 }); 10766 });
10767 }; 10768 10769 const createTimeline = () => { 10770 const rootScale = getRootScale(); 10771 if (tl) { 10772 tl.kill(); 10773 tl.scrollTrigger?.kill(); 10774 } 10775 10776 // getHeights â setCollapsedState ã®é ãç¶æ 10777 const heights = getHeights(); 10778 setCollapsedState(); 10779 10780 const { bodyHeights } = heights; 10781 10782 const expandedImageHeight = isPc() 10783 ? EXPANDED_IMAGE_HEIGHT_PC * rootScale 10784 : EXPANDED_IMAGE_HEIGHT_SP; 10785 const midItemHeight = isPc() ? MID_ITEM_HEIGHT_PC * rootScale : 260; 10786 const expandedItemHeight = isPc() ? EXPANDED_ITEM_HEIGHT_PC * rootScale : 260; 10787 const expandedPaddingTop = isPc() ? EXPANDED_PADDING_TOP_PC * rootScale : 20; 10788 const expandedPaddingBottom = isPc() 10789 ? EXPANDED_PADDING_BOTTOM_PC * rootScale 10790 : 20; 10791 10792 // 2æç®ä»¥éãæ©ãè¦ããããªããããéå§ééãå°ãåºãã 10793 const ITEM_DURATION = 1; 10794 const ITEM_STAGGER = 1.45; 10795 10796 // ãã©ã¦ã¶å·®ãåºã«ãããããå±ééãã¼ã¹ã§ã¹ã¯ãã¼ã«è·é¢ã決å®ãã 10797 const expandedDeltaPerItem = Math.max( 10798 expandedItemHeight - COLLAPSED_ITEM_HEIGHT_PC, 10799 1, 10800 ); 10801 const baseScrollHeight = items.length * expandedDeltaPerItem; 10802 // 4æç®ä»è¿ã§ä½ç½ãåºãããªããããstaggerå½±é¿ãå§ç¸®ãã¦åæ ãã 10803 const staggerImpact = 1 + (ITEM_STAGGER - ITEM_DURATION) * 0.45; 10804 const measuredScrollHeight = baseScrollHeight * staggerImpact; 10805 const viewportFloor = window.innerHeight * 1.05; 10806 const totalScrollHeight = Math.max(measuredScrollHeight, viewportFloor); 10807 10808 // ããã¸ãã¯ä¿®æ£ãåã¢ã¤ãã ã®ã¢ãã¡ã¼ã·ã§ã³ duration ã¨éå§ä½ç½®ã 10809 // ã¢ã¤ãã æ°ã§åçåå²ããé çªã«å±éãããããã«ãã 10810 10811 tl = gsap.timeline({ 10812 defaults: { ease: "none" }, 10813 scrollTrigger: { 10814 trigger: container, 10815 // éå§ãæ©ããããããã»ã¯ã·ã§ã³ãããè¦ãã¦ããå±éãå§ãã 10816 start: "top 68%", 10817 end: `+=${totalScrollHeight}`, 10818 scrub: 0.45, 10819 invalidateOnRefresh: true, 10820 markers: false, 10821 }, 10822 }); 10823 10824 items.forEach((item, index) => { 10825 const bodyEl = bodies[index]; 10826 const imageEl = images[index]; 10827 const textEl = textBlocks[index]; 10828 const contentEl = contentBlocks[index]; 10829 const bodyHeight = bodyHeights[index] || 0; 10830 10831 // 2æç®ä»¥éã®éå§ããããã«é ãããè¦ãç®ã®å±éã¿ã¤ãã³ã°ãæ´ãã 10832 const startPosition = index * ITEM_STAGGER; 10833 10834 if (bodyEl && bodyHeight > 0) { 10835 tl.to( 10836 bodyEl, 10837 { 10838 height: bodyHeight, 10839 duration: ITEM_DURATION, 10840 immediateRender: false, 10841 }, 10842 startPosition, 10843 ); 10844 } 10845 10846 if (textEl) { 10847 gsap.set(textEl, { clearProps: "height,overflow" }); 10848 } 10849 10850 if (imageEl) { 10851 const photoEl = imageEl.querySelector( 10852 ".sustainability__scroll-accordion-image-photo", 10853 ); 10854 tl.to( 10855 imageEl, 10856 { 10857 height: expandedImageHeight, 10858 onComplete: () => gsap.set(imageEl, { overflow: "hidden" }), 10859 duration: ITEM_DURATION, 10860 immediateRender: false, 10861 }, 10862 startPosition, 10863 ); 10864 if (photoEl) { 10865 tl.to( 10866 photoEl, 10867 { 10868 borderRadius: 5 * rootScale, 10869 duration: ITEM_DURATION, 10870 immediateRender: false, 10871 }, 10872 startPosition, 10873 ); 10874 } 10875 } 10876 10877 if (contentEl) { 10878 gsap.set(contentEl, { clearProps: "height,overflow" }); 10879 } 10880 10881 if (item) { 10882 tl.to( 10883 item, 10884 { 10885 height: midItemHeight, 10886 paddingTop: expandedPaddingTop, 10887 paddingBottom: 0, 10888 duration: ITEM_DURATION, 10889 immediateRender: false, 10890 }, 10891 startPosition, 10892 ); 10893 tl.to( 10894 item, 10895 { 10896 height: expandedItemHeight, 10897 paddingBottom: expandedPaddingBottom, 10898 duration: 0.25, 10899 immediateRender: false, 10900 }, 10901 startPosition + ITEM_DURATION, 10902 ); 10903 } 10904 });
10905 }; 10906 10907 createTimeline(); 10908 10909 // loadã¤ãã³ãå¾ã«refreshãã¦ãã©ã³ãã»ç»åãµã¤ãºç¢ºå®å¾ã® 10910 // æ£ç¢ºãªã¬ã¤ã¢ã¦ãã§ScrollTriggerãåè¨ç®ãã 10911 const isAroundAccordion = () => { 10912 const rect = container.getBoundingClientRect(); 10913 // ã»ã¯ã·ã§ã³ã大ããééå¾ã¯åçæããªãï¼é å»¶çºç«ã«ããå¼ã£ããã鲿¢ï¼ 10914 return rect.bottom > -window.innerHeight * 0.3; 10915 }; 10916 10917 const rebuildAfterLayoutSettles = () => { 10918 if (!isAroundAccordion()) { 10919 scheduleScrollTriggerRefresh(); 10920 return; 10921 } 10922 createTimeline(); 10923 scheduleScrollTriggerRefresh(); 10924 }; 10925 10926 if (document.readyState === "complete") { 10927 rebuildAfterLayoutSettles(); 10928 } else { 10929 window.addEventListener( 10930 "load", 10931 () => { 10932 rebuildAfterLayoutSettles(); 10933 10934 // ãã©ã³ããã¼ãå¾ã¨é å»¶1åã§åº§æ¨ã®æçµç¢ºå®ã«è¿½å¾ãã 10935 if (document.fonts?.ready) { 10936 document.fonts.ready.then(rebuildAfterLayoutSettles); 10937 } 10938 10939 setTimeout(rebuildAfterLayoutSettles, 350); 10940 }, 10941 { once: true }, 10942 ); 10943 } 10944 10945 // ä¸å±¤ã»ã¯ã·ã§ã³ã§ããªã¬ã¼åº§æ¨ãé ãã¦ç¢ºå®ããã±ã¼ã¹ã«åãã 10946 // ã»ã¯ã·ã§ã³æ¥è¿æã«1åã ãåè¨ç®ãã 10947 if ("IntersectionObserver" in window) { 10948 const nearViewportObserver = new IntersectionObserver( 10949 (entries, observer) => { 10950 const hasVisible = entries.some((entry) => entry.isIntersecting); 10951 if (!hasVisible) return; 10952 rebuildAfterLayoutSettles(); 10953 observer.disconnect(); 10954 }, 10955 { 10956 root: null, 10957 rootMargin: "0px 0px 0px 0px", 10958 threshold: 0, 10959 }, 10960 ); 10961 nearViewportObserver.observe(container); 10962 } 10963 10964 addDebouncedResizeListener(() => { 10965 // SPå¹ ã«ãªã£ãå ´åã¯ã¢ãã¡ã¼ã·ã§ã³ãç ´æ£ãã¦çµäº 10966 if (window.innerWidth <= 767) { 10967 if (tl) { 10968 tl.kill(); 10969 tl.scrollTrigger?.kill(); 10970 } 10971 return; 10972 } 10973 createTimeline(); 10974 scheduleScrollTriggerRefresh(); 10975 }, 250); 10976} 10977 10978/** 10979 * Aboutãã¼ã¸ ãã¸ã§ã³ã»ã¯ã·ã§ã³ç¨ã¢ãã¡ã¼ã·ã§ã³ 10980 * - èæ¯ã»æåè²ã®ã¹ã¯ãã¼ã«é£åï¼éâç½ï¼ 10981 * - å·¦å³ç»åã®ç»å ´ã»ãã©ã©ãã¯ã¹ 10982 * - ã³ãã¼ã¹ã¯ãã¼ã«ï¼ç¸¦ã¹ã¯ãã¼ã«ã«é£åããæ¨ªç§»åï¼ 10983 * åè: GSAP ScrollTrigger + Lenis 10984 */ 10985 10986 10987function initAboutVision() { 10988 const section = document.querySelector(".about-vision"); 10989 if (!section) return; 10990 10991 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") { 10992 return; 10993 } 10994 10995 gsap.registerPlugin(ScrollTrigger); 10996 10997 // 1. è¦ç´ åå¾ 10998 const body = document.querySelector("body"); 10999 const headingEn = section.querySelector(".about-vision__heading-en"); 11000 section.querySelector(".about-vision__catch"); 11001 const leftImages = section.querySelectorAll( 11002 ".about-vision__photo-left .about-vision__img", 11003 ); 11004 const rightImages = section.querySelectorAll( 11005 ".about-vision__photo-right .about-vision__img", 11006 ); 11007 section.querySelector(".about-vision__copyscroll"); 11008 const innerEl = section.querySelector(".about-vision__inner"); 11009 const textSelectors = 11010 ".about-vision__heading-en, .about-vision__catch, .about-vision__body, .about-vision__body p"; 11011 const SectionHeadingTitleEn = document.querySelector(".section-heading__title-en-text"); 11012 const SectionHeadingTitleJa = document.querySelector(".section-heading__title-ja-text"); 11013 const groupCompanyListItemCompanyName = document.querySelectorAll(".group-company-list-item__company-name"); 11014 const groupCompanyListItemDescriptionLine = document.querySelectorAll(".group-company-list-item__description-line"); 11015 const groupCompanyListItemDate = document.querySelectorAll(".group-company-list-item__date"); 11016 const groupCompanyListItemDateNumber = document.querySelectorAll(".group-company-list-item__date-number"); 11017 const groupCompanyListItemWebsiteLink = document.querySelectorAll(".group-company-list-item__website-link"); 11018 11019 if (!headingEn) return; 11020 11021 // 3. åæç¶æ ï¼ç»åã¯æåã¯é表示ã»ã¹ã¯ãã¼ã«ã§ç»å ´ï¼ 11022 const bgGradientEl = section.querySelector(".about-vision__bg-gradient"); 11023 const bgDuration = 1; 11024 gsap.set(body, { background: "#fff" }); 11025 gsap.set(leftImages, { opacity: 0 }); 11026 gsap.set(rightImages, { opacity: 0 }); 11027 11028 function applyEnterState() { 11029 // æ¢åã® about-vision__bg-gradient ã® opacity ã§ãã§ã¼ãï¼ã°ã©ãâç½ã®ç´æ¥è£éãé¿ããï¼ 11030 if (bgGradientEl) { 11031 gsap.to(bgGradientEl, { 11032 opacity: 1, 11033 duration: bgDuration, 11034 ease: "none", 11035 }); 11036 } 11037 gsap.to(textSelectors, { color: "#fff", ease: "none" }); 11038 if (SectionHeadingTitleEn) gsap.to(SectionHeadingTitleEn, { color: "#fff", ease: "none" }); 11039 if (SectionHeadingTitleJa) gsap.to(SectionHeadingTitleJa, { color: "#fff", ease: "none" }); 11040 groupCompanyListItemCompanyName.forEach((item) => { 11041 gsap.to(item, { color: "#fff", ease: "none" }); 11042 });
11043 groupCompanyListItemDescriptionLine.forEach((item) => { 11044 gsap.to(item, { color: "#fff", ease: "none" }); 11045 }); 11046 groupCompanyListItemDate.forEach((item) => { 11047 gsap.to(item, { color: "#fff", ease: "none" }); 11048 }); 11049 groupCompanyListItemDateNumber.forEach((item) => { 11050 gsap.to(item, { color: "#fff", ease: "none" }); 11051 }); 11052 groupCompanyListItemWebsiteLink.forEach((item) => { 11053 gsap.to(item, { color: "#fff", ease: "none" }); 11054 }); 11055 leftImages.forEach((img, index) => { 11056 gsap.to(img, { opacity: 1, ease: "none", delay: index * 0.05 }); 11057 }); 11058 rightImages.forEach((img, index) => { 11059 gsap.to(img, { opacity: 1, ease: "none", delay: index * 0.05 }); 11060 }); 11061 } 11062 11063 function applyLeaveState() { 11064 if (bgGradientEl) { 11065 gsap.to(bgGradientEl, { 11066 opacity: 0, 11067 duration: bgDuration, 11068 ease: "none", 11069 }); 11070 } 11071 gsap.to(".about-vision__heading-en", { color: "#666", ease: "none" }); 11072 gsap.to(".about-vision__catch", { color: "#1a1a1a", ease: "none" }); 11073 gsap.to(".about-vision__body, .about-vision__body p", { color: "#333", ease: "none" }); 11074 if (SectionHeadingTitleEn) gsap.to(SectionHeadingTitleEn, { color: "#000", ease: "none" }); 11075 if (SectionHeadingTitleJa) gsap.to(SectionHeadingTitleJa, { color: "#000", ease: "none" }); 11076 groupCompanyListItemCompanyName.forEach((item) => { 11077 gsap.to(item, { color: "#000", ease: "none" }); 11078 }); 11079 groupCompanyListItemDescriptionLine.forEach((item) => { 11080 gsap.to(item, { color: "#000", ease: "none" }); 11081 }); 11082 groupCompanyListItemDate.forEach((item) => { 11083 gsap.to(item, { color: "#000", ease: "none" }); 11084 }); 11085 groupCompanyListItemDateNumber.forEach((item) => { 11086 gsap.to(item, { color: "#000", ease: "none" }); 11087 }); 11088 groupCompanyListItemWebsiteLink.forEach((item) => { 11089 gsap.to(item, { color: "#1a1a1a", ease: "none" }); 11090 }); 11091 leftImages.forEach((img, index) => { 11092 gsap.to(img, { opacity: 0, ease: "none", delay: index * 0.03 }); 11093 }); 11094 rightImages.forEach((img, index) => { 11095 gsap.to(img, { opacity: 0, ease: "none", delay: index * 0.03 }); 11096 }); 11097 } 11098 11099 // ã¨ãªã¢ã«å ¥ã£ãæï¼onEnterï¼ã»æ»ã£ã¦å ¥ã£ãæï¼onEnterBackï¼â é / æããæï¼onLeaveï¼ã»ä¸ã«æ»ã£ã¦åºãæï¼onLeaveBackï¼â ç½ 11100 ScrollTrigger.create({ 11101 trigger: section, 11102 start: "top 20%", 11103 end: window.innerWidth < 768 ? "bottom 0+=200" : "bottom 0", 11104 onEnter: applyEnterState, 11105 onEnterBack: applyEnterState, 11106 onLeave: applyLeaveState, 11107 onLeaveBack: applyLeaveState, 11108 }); 11109 11110 // 6. å·¦å³ç»åã®ãã©ã©ãã¯ã¹ï¼ã¹ã¯ãã¼ã«ã«å¿ãã¦ç°ãªãé度ã§ç§»åï¼ 11111 if (innerEl && leftImages.length > 0) { 11112 leftImages.forEach((img, index) => { 11113 gsap.fromTo(img, { 11114 y: 300, 11115 }, { 11116 y: -200, 11117 ease: "none", 11118 scrollTrigger: { 11119 trigger: section, 11120 start: "top bottom", 11121 end: "bottom top", 11122 scrub: 1.5, 11123 }, 11124 }); 11125 }); 11126 } 11127 if (innerEl && rightImages.length > 0) { 11128 rightImages.forEach((img, index) => { 11129 gsap.fromTo(img, { 11130 y: -100, 11131 }, { 11132 y: 100, 11133 ease: "none", 11134 scrollTrigger: { 11135 trigger: section, 11136 start: "top bottom", 11137 end: "bottom top", 11138 scrub: 1.5, 11139 }, 11140 }); 11141 }); 11142 } 11143 11144 // 7. Brand Vision ããã¹ãã䏿åãã¤ã¢ãã¡ã¼ã·ã§ã³ 11145 const textLabel = section.querySelector('.about-vision__heading-en'); 11146 if (textLabel) { 11147 const text = textLabel.textContent.trim(); 11148 textLabel.innerHTML = ''; 11149 const chars = []; 11150 11151 text.split('').forEach((char) => { 11152 if (char === ' ') { 11153 const space = document.createElement('span'); 11154 space.className = 'char-wrapper space'; 11155 space.innerHTML = ' '; 11156 textLabel.appendChild(space); 11157 } else { 11158 const wrapper = document.createElement('span'); 11159 wrapper.className = 'char-wrapper'; 11160 const inner = document.createElement('span'); 11161 inner.className = 'char-inner'; 11162 inner.textContent = char; 11163 wrapper.appendChild(inner); 11164 textLabel.appendChild(wrapper); 11165 chars.push(inner); 11166 } 11167 });
11168 11169 gsap.from(chars, { 11170 y: '100%', 11171 duration: 0.6, 11172 ease: 'power3.out', 11173 stagger: 0.05, 11174 scrollTrigger: { 11175 trigger: textLabel, 11176 start: 'top 75%', 11177 toggleActions: 'play none none none', 11178 }, 11179 }); 11180 } 11181 11182 // 8. ãã£ããã³ãã¼ã1è¡ãã¤ã¢ãã¡ã¼ã·ã§ã³(è¡å ¨ä½ãåæã«ä¸ãã) 11183 const catchcopy = section.querySelector('.about-vision__catch'); 11184 if (catchcopy) { 11185 const lines = catchcopy.innerHTML.split('<br>'); 11186 catchcopy.innerHTML = ''; 11187 11188 lines.forEach((lineText) => { 11189 const wrapper = document.createElement('span'); 11190 wrapper.className = 'catchcopy-line-wrapper'; 11191 const inner = document.createElement('span'); 11192 inner.className = 'catchcopy-line'; 11193 inner.innerHTML = lineText; 11194 wrapper.appendChild(inner); 11195 catchcopy.appendChild(wrapper); 11196 }); 11197 11198 const lineInners = catchcopy.querySelectorAll('.catchcopy-line'); 11199 11200 gsap.from(lineInners, { 11201 y: '100%', 11202 duration: 0.9, 11203 ease: 'power3.out', 11204 stagger: 0.25, 11205 scrollTrigger: { 11206 trigger: catchcopy, 11207 start: 'top 70%', 11208 toggleActions: 'play none none none', 11209 }, 11210 }); 11211 } 11212 11213 // 9. ããã¹ãã®åè¡ãã¢ãã¡ã¼ã·ã§ã³(ã°ã©ãã¼ã·ã§ã³ãã¹ã¯) â _vision.js ã¨åãæ¹å¼ 11214 const visionBody = section.querySelector('.about-vision__body'); 11215 if (visionBody) { 11216 const allPs = visionBody.querySelectorAll('p'); 11217 let activeP = null; 11218 allPs.forEach((p) => { 11219 if (getComputedStyle(p).display !== 'none') { 11220 activeP = p; 11221 } 11222 }); 11223 11224 if (activeP) { 11225 const lineWrappers = []; 11226 const lines = []; 11227 let current = ''; 11228 for (const node of activeP.childNodes) { 11229 if (node.nodeType === Node.TEXT_NODE) { 11230 current += node.textContent; 11231 } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BR') { 11232 lines.push(current.trim()); 11233 current = ''; 11234 } 11235 } 11236 if (current.length) lines.push(current.trim()); 11237 11238 activeP.innerHTML = ''; 11239 11240 lines.forEach((lineText) => { 11241 const wrapper = document.createElement('span'); 11242 wrapper.className = 'text-line-wrapper'; 11243 const line = document.createElement('span'); 11244 line.className = 'text-line'; 11245 line.textContent = lineText || '\u00A0'; 11246 wrapper.appendChild(line); 11247 activeP.appendChild(wrapper); 11248 lineWrappers.push({ wrapper, line }); 11249 }); 11250 11251 const isSp = window.innerWidth <= 767; 11252 const textFadeStart = isSp ? "top 90%" : "top 85%"; 11253 const textFadeEnd = isSp ? "top 70%" : "top 60%"; 11254 11255 lineWrappers.forEach(({ line }, index) => { 11256 gsap.to(line, { 11257 ease: 'none', 11258 scrollTrigger: { 11259 trigger: line, 11260 start: textFadeStart, 11261 end: textFadeEnd, 11262 scrub: true, 11263 id: `about-vision-text-line-${index}`, 11264 onUpdate(self) { 11265 const progress = self.progress * 100; 11266 const fadeWidth = 40; 11267 const front = (progress * (100 + fadeWidth * 2)) / 100 - fadeWidth; 11268 11269 let maskValue; 11270 if (front <= 0) { 11271 maskValue = 'linear-gradient(to right, transparent 0%, transparent 100%)'; 11272 } else if (front >= 100 + fadeWidth) { 11273 maskValue = 'linear-gradient(to right, black 0%, black 100%)'; 11274 } else { 11275 const solidEnd = Math.max(0, front - fadeWidth); 11276 const transEnd = Math.min(100, front); 11277 maskValue = `linear-gradient(to right, black 0%, black ${solidEnd}%, transparent ${transEnd}%, transparent 100%)`; 11278 } 11279 11280 line.style.webkitMaskImage = maskValue; 11281 line.style.maskImage = maskValue; 11282 }, 11283 }, 11284 }); 11285 }); 11286 } 11287 } 11288 11289 // 10. ãªãµã¤ãºã»ãã¼ãæã« ScrollTrigger ãåè¨ç® 11290 window.addEventListener("load", () =>
11290 scheduleScrollTriggerRefresh()); 11291 addDebouncedResizeListener(() => { 11292 scheduleScrollTriggerRefresh(); 11293 }, 120); 11294} 11295 11296/** 11297 * Companyãã¼ã¸ OUR vision ã»ã¯ã·ã§ã³ 11298 * 11299 * ãã¿ã¤ãã«ã1æåãã¤ã¹ã©ã¤ãã¢ããï¼y: 100% â 0ï¼ 11300 * ãæ¬æã<br> ã«åºã¥ãè¡åå²ã®ã¿ããã§ã¼ã广ã¯ãªãã»ååããå ¨æè¡¨ç¤ºã 11301 * 11302 * GSAPã»ScrollTrigger ã¯ã¿ã¤ãã«ã¢ãã¡ç¨ã« Layout ã§ CDN èªã¿è¾¼ã¿æ¸ã¿åæ 11303 */ 11304 11305 11306/** 11307 * <br> ã§åºåãããè¡ãé åã§è¿ãã 11308 * pc-only ã® br ã¯æ¹è¡æ±ãããªãï¼SPã§æ¹è¡ããããªãããï¼ã 11309 * sp-only ã® br ã¯æ¹è¡ããããã®ç´å¾ã®è¡ã¯ spBreakBefore ã«å«ã SP ã§æ¹è¡è¡¨ç¤ºããã 11310 */ 11311function parseTextLines(container) { 11312 const lines = []; 11313 const spBreakBefore = []; 11314 let current = ""; 11315 let nextIsAfterSpOnly = false; 11316 11317 for (const node of container.childNodes) { 11318 if (node.nodeType === Node.TEXT_NODE) { 11319 current += node.textContent || ""; 11320 } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "BR") { 11321 const isPcOnly = node.classList && node.classList.contains("pc-only"); 11322 const isSpOnly = node.classList && node.classList.contains("sp-only"); 11323 if (isPcOnly) { 11324 current = current.replace(/[ \t\r\n]+$/g, ""); 11325 } else if (isSpOnly) { 11326 lines.push(current.trim()); 11327 current = ""; 11328 nextIsAfterSpOnly = true; 11329 } else { 11330 lines.push(current.trim()); 11331 current = ""; 11332 } 11333 } 11334 } 11335 11336 if (current.trim().length) { 11337 if (nextIsAfterSpOnly) spBreakBefore.push(lines.length); 11338 lines.push(current.trim()); 11339 } 11340 return { lines, spBreakBefore }; 11341} 11342 11343function initOurVisionTitleAnimation() { 11344 const titleEl = document.querySelector( 11345 ".our-vision__title[data-anim-title]", 11346 ); 11347 if (!titleEl) return; 11348 11349 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") { 11350 return; 11351 } 11352 11353 gsap.registerPlugin(ScrollTrigger); 11354 11355 const text = 11356 titleEl.dataset.text || 11357 (titleEl.textContent || "").replace(/\s+/g, " ").trim(); 11358 if (!text) return; 11359 11360 titleEl.innerHTML = ""; 11361 const chars = []; 11362 11363 for (let i = 0; i < text.length; i++) { 11364 const ch = text[i]; 11365 /* SPã®ã¿ï¼ãããã®ç´å¾ï¼ãåãã®åï¼ã§æ¹è¡ */ 11366 if (ch === "å" && text[i - 1] === "ã") { 11367 const br = document.createElement("br"); 11368 br.className = "sp-only"; 11369 titleEl.appendChild(br); 11370 } 11371 if (ch === " " || ch === "\u3000") { 11372 const wrap = document.createElement("span"); 11373 wrap.className = 11374 "our-vision__title-char-wrap our-vision__title-char-wrap--space"; 11375 wrap.innerHTML = "\u00A0"; 11376 titleEl.appendChild(wrap); 11377 } else { 11378 const wrap = document.createElement("span"); 11379 wrap.className = "our-vision__title-char-wrap"; 11380 const inner = document.createElement("span"); 11381 inner.className = "our-vision__title-char-inner"; 11382 inner.textContent = ch; 11383 wrap.appendChild(inner); 11384 titleEl.appendChild(wrap); 11385 chars.push(inner); 11386 } 11387 } 11388 11389 gsap.from(chars, { 11390 y: "100%", 11391 duration: 0.6, 11392 ease: "power3.out", 11393 stagger: 0.05, 11394 scrollTrigger: { 11395 trigger: titleEl, 11396 start: "top 90%", 11397 toggleActions: "play none none none", 11398 }, 11399 }); 11400} 11401 11402/** 11403 * æ¬æï¼<br> ã«åºã¥ãè¡åå²ã®ã¿ï¼ãã§ã¼ã广ãªãã»ååããå ¨æè¡¨ç¤ºï¼ 11404 */ 11405function initOurVisionTextMaskAnimation() { 11406 const textWrap = document.querySelector(".our-vision__text-wrap"); 11407 if (!textWrap) return; 11408 11409 const paragraphs = textWrap.querySelectorAll(".our-vision__text"); 11410 if (paragraphs.length === 0) return; 11411 11412 const lineWrappers = []; 11413 paragraphs.forEach((p, pIndex) => { 11414 const { lines, spBreakBefore } = parseTextLines(p); 11415 lines.forEach((lineText, lineIndex) => { 11416 const wrapper = document.createElement("span"); 11417 wrapper.className = "our-vision__text-line-wrapper"; 11418 if (pIndex > 0 && lineIndex === 0) { 11419 wrapper.classList.add("our-vision__text-line-wrapper--new-para"); 11420 } 11421 /* SPã®ã¿æ¹è¡ï¼1段è½ç®2è¡ç®ï¼ã°ã«ã¼ãå社ã¯ãã®å¾ï¼ãã¾ã㯠sp-only ã®ç´å¾ï¼åãæãããªã©ï¼ */ 11422 if ((pIndex === 0 && lineIndex === 1) || spBreakBefore.includes(lineIndex)) { 11423 wrapper.classList.add("our-vision__text-line-wrapper--sp-break"); 11424 } 11425 const line = document.createElement("span"); 11426 line.className = "our-vision__text-line"; 11427 line.textContent = lineText; 11428 wrapper.appendChild(line); 11429 lineWrappers.push({ wrapper, line }); 11430 });
11431 }); 11432 11433 const fragment = document.createDocumentFragment(); 11434 lineWrappers.forEach(({ wrapper }) => fragment.appendChild(wrapper)); 11435 textWrap.innerHTML = ""; 11436 textWrap.appendChild(fragment); 11437 11438 /* ãã§ã¼ã广ã¯ãªãï¼ååããå ¨æè¡¨ç¤ºï¼è¡åå²ã®ã¿æ®ãã¦ãã¹ã¯ã¢ãã¡ã¯è¡ããªãï¼ */ 11439 scheduleScrollTriggerRefresh(); 11440} 11441 11442/** 11443 * åçã¨ãªã¢ï¼å¤§ç»åã»å°ç»åãå¥ã¹ã¯ã¤ãã¼ã§åæã 11444 * setInterval ã§ä¸¡æ¹ãåæã« slideNext ããã¤ãã³ãèµ·å ã®ã«ã¼ã忢ãé²ãã 11445 * loop è¦å対ç: loopedSlides/loopAdditionalSlides ã¯æå®ãããslidesPerView:1 / slidesPerGroup:1 ãæç¤ºã 11446 * 大ç»åï¼4ç§è¡¨ç¤ºã1â1.15åãºã¼ã (7s)ã2ç§ãã§ã¼ãã 11447 * å°ç»åï¼4æã«ã¼ããåãæ¿ãæ1.5ç§ã§å³ãããã©ã©ãã¯ã¹ã 11448 */ 11449function initOurVisionSwiper() { 11450 const mainEl = document.querySelector(".our-vision__photo-main-swiper"); 11451 const insetEl = document.querySelector(".our-vision__photo-inset-swiper"); 11452 if (!mainEl || !insetEl || typeof window.Swiper === "undefined") return; 11453 11454 const Swiper = window.Swiper; 11455 const AUTOPLAY_DELAY = 5000; 11456 const MAIN_SPEED = 2000; 11457 const INSET_SPEED = 1500; 11458 11459 const insetSwiper = new Swiper(insetEl, { 11460 loop: true, 11461 slidesPerView: 1, 11462 slidesPerGroup: 1, 11463 loopPreventsSliding: false, 11464 parallax: true, 11465 speed: INSET_SPEED, 11466 allowTouchMove: false, 11467 watchSlidesProgress: true, 11468 }); 11469 11470 const mainSwiper = new Swiper(mainEl, { 11471 loop: true, 11472 slidesPerView: 1, 11473 slidesPerGroup: 1, 11474 effect: "fade", 11475 fadeEffect: { crossFade: true }, 11476 speed: MAIN_SPEED, 11477 allowTouchMove: false, 11478 }); 11479 11480 // åæä½ç½®ãæãã 11481 insetSwiper.slideToLoop(mainSwiper.realIndex, 0, false); 11482 11483 // åä¸ã¿ã¤ãã³ã°ã§ä¸¡æ¹ã次ã¸ãâ ã«ã¼ããæ¢ã¾ãã«ãã 11484 setInterval(() => { 11485 mainSwiper.slideNext(MAIN_SPEED); 11486 insetSwiper.slideNext(INSET_SPEED); 11487 }, AUTOPLAY_DELAY); 11488} 11489 11490function initOurVisionAnim() { 11491 initOurVisionTitleAnimation(); 11492 initOurVisionTextMaskAnimation(); 11493 initOurVisionSwiper(); 11494} 11495 11496/** 11497 * SectionHeading å ±éã¢ãã¡ã¼ã·ã§ã³ 11498 * 11499 * ãè±å½¢ãæåãã表示ï¼ã¢ãã¡ãªãï¼ 11500 * ãè±èªã1æåãã¤ä¸ããä¸ã¸ã¹ã©ã¤ãï¼y: 100% â 0ï¼ã表示å¾ã« is-inview ä»ä¸ 11501 * ãæ¥æ¬èªããããã¯ãã¨ä¸ããä¸ã¸ã¹ã©ã¤ãã表示å¾ã« is-inview ä»ä¸ 11502 * 11503 * GSAPã»ScrollTrigger 㯠Layout ã§ CDN èªã¿è¾¼ã¿æ¸ã¿åæ 11504 */ 11505 11506 11507function initSectionHeadingAnimation() { 11508 const headings = document.querySelectorAll( 11509 ".section-heading[data-section-heading-anim]", 11510 ); 11511 if (headings.length === 0) return; 11512 11513 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") { 11514 return; 11515 } 11516 gsap.registerPlugin(ScrollTrigger); 11517
11518 headings.forEach((heading) => { 11519 const titleEnEl = heading.querySelector(".section-heading__title-en-text"); 11520 const titleJaEl = heading.querySelector(".section-heading__title-ja"); 11521 11522 if (!titleEnEl || !titleJaEl) return; 11523 11524 const text = 11525 titleEnEl.dataset.titleEn || 11526 (titleEnEl.textContent || "").replace(/\s+/g, " ").trim(); 11527 if (!text) return; 11528 11529 // è±èªï¼1æåã㤠span ã«åè§£ 11530 titleEnEl.innerHTML = ""; 11531 const chars = []; 11532 11533 for (let i = 0; i < text.length; i++) { 11534 const ch = text[i]; 11535 if (ch === " " || ch === "\u3000") { 11536 const wrap = document.createElement("span"); 11537 wrap.className = 11538 "section-heading__title-char-wrap section-heading__title-char-wrap--space"; 11539 wrap.innerHTML = "\u00A0"; 11540 titleEnEl.appendChild(wrap); 11541 } else { 11542 const wrap = document.createElement("span"); 11543 wrap.className = "section-heading__title-char-wrap"; 11544 const inner = document.createElement("span"); 11545 inner.className = "section-heading__title-char-inner"; 11546 inner.textContent = ch; 11547 wrap.appendChild(inner); 11548 titleEnEl.appendChild(wrap); 11549 chars.push(inner); 11550 } 11551 } 11552 11553 // è±èªï¼1æåãã¤ã¹ã©ã¤ãã¢ãããå®äºæã« is-inview 11554 gsap.from(chars, { 11555 y: "100%", 11556 duration: 0.5, 11557 ease: "power3.out", 11558 stagger: 0.04, 11559 scrollTrigger: { 11560 trigger: heading.querySelector(".section-heading__title-en"), 11561 start: "top 90%", 11562 toggleActions: "play none none none", 11563 onEnter: () => { 11564 heading 11565 .querySelector(".section-heading__title-en") 11566 ?.classList.add("is-inview"); 11567 }, 11568 }, 11569 }); 11570 11571 // æ¥æ¬èªï¼<br> ãããå ´åã¯1è¡ãã¤ããªããã°ãããã¯ãã¨ã¹ã©ã¤ã 11572 const titleJaTextEl = heading.querySelector( 11573 ".section-heading__title-ja-text", 11574 ); 11575 const hasBr = titleJaTextEl?.querySelector("br"); 11576 11577 if (hasBr && titleJaTextEl) { 11578 // ç»é¢å¹ ã«å¿ãã¦ã¢ã¯ãã£ã㪠<br> ã ããåå²ç¹ã«å¤æãéã¢ã¯ãã£ãã¯åé¤ 11579 const SP_BREAKPOINT = 767; 11580 const isSp = window.innerWidth <= SP_BREAKPOINT; 11581 const DELIM = "\x00"; 11582 11583 const processedHtml = titleJaTextEl.innerHTML.replace( 11584 /<br([^>]*)>/gi, 11585 (_match, attrs) => { 11586 if (/\bsp-only\b/.test(attrs) && !isSp) return ""; // PCæã¯ sp-only ãç¡è¦ 11587 if (/\bpc-only\b/.test(attrs) && isSp) return ""; // SPæã¯ pc-only ãç¡è¦ 11588 return DELIM; 11589 }, 11590 ); 11591 11592 const parts = processedHtml.split(DELIM).filter((p) => p.trim()); 11593 11594 if (parts.length > 1) { 11595 // è¤æ°è¡ï¼è¡ãã¨ã«ã¹ã©ã¤ãã¢ãã 11596 titleJaTextEl.innerHTML = ""; 11597 const lineInners = []; 11598 11599 parts.forEach((part) => { 11600 const wrap = document.createElement("span"); 11601 wrap.className = "section-heading__title-ja-line-wrap"; 11602 const inner = document.createElement("span"); 11603 inner.className = "section-heading__title-ja-line-inner"; 11604 inner.innerHTML = part; 11605 wrap.appendChild(inner); 11606 titleJaTextEl.appendChild(wrap); 11607 lineInners.push(inner); 11608 }); 11609 11610 gsap.from(lineInners, { 11611 y: "100%", 11612 duration: 0.5, 11613 delay: 0.35, 11614 ease: "power3.out", 11615 stagger: 0.1, 11616 scrollTrigger: { 11617 trigger: titleJaEl, 11618 start: "top 92%", 11619 toggleActions: "play none none none", 11620 onEnter: () => { 11621 titleJaEl.classList.add("is-inview"); 11622 }, 11623 }, 11624 }); 11625 } else { 11626 // ç¾å¨ã®ç»é¢å¹ ã§ã¯å®è³ª1è¡ï¼ãããã¯ãã¨ã¹ã©ã¤ã 11627 titleJaTextEl.innerHTML = processedHtml; // éã¢ã¯ãã£ã br ãé¤å»ããç¶æ ã«æ´æ° 11628 gsap.from(titleJaEl, { 11629 y: "100%", 11630 duration: 0.5, 11631 delay: 0.35, 11632 ease: "power3.out", 11633 scrollTrigger: { 11634 trigger: titleJaEl, 11635 start: "top 92%", 11636 toggleActions: "play none none none", 11637 onEnter: () => { 11638 titleJaEl.classList.add("is-inview"); 11639 }, 11640 }, 11641 }); 11642 } 11643 } else { 11644 // <br> ãªãï¼ãããã¯ãã¨ä¸ããä¸ã¸ã¹ã©ã¤ããå®äºæã« is-inview 11645 gsap.from(titleJaEl, { 11646 y: "100%", 11647 duration: 0.5, 11648 delay: 0.35, 11649 ease: "power3.out", 11650 scrollTrigger: { 11651 trigger: titleJaEl, 11652 start: "top 92%", 11653 toggleActions: "play none none none", 11654 onEnter: () => { 11655 titleJaEl.classList.add("is-inview"); 11656 }, 11657 }, 11658 }); 11659 } 11660 }); 11661 11662 scheduleScrollTriggerRefresh();
11663} 11664 11665/** 11666 * æå®ã³ã³ããå ã® SectionHeading ã¢ãã¡ã¼ã·ã§ã³ãåçããã 11667 * ã¿ãåãæ¿ããªã©ãScrollTrigger ã使ããã«å³æçºç«ãããå ´åã«ä½¿ç¨ã 11668 */ 11669function replaySectionHeadingAnimation(container) { 11670 if (typeof gsap === "undefined") return; 11671 11672 const headings = container.querySelectorAll( 11673 ".section-heading[data-section-heading-anim]", 11674 ); 11675 if (headings.length === 0) return; 11676 11677 headings.forEach((heading) => { 11678 const titleEnEl = heading.querySelector(".section-heading__title-en"); 11679 const titleJaEl = heading.querySelector(".section-heading__title-ja"); 11680 11681 // is-inview ãªã»ãã 11682 titleEnEl?.classList.remove("is-inview"); 11683 titleJaEl?.classList.remove("is-inview"); 11684 11685 // è±èªï¼æ¢ã« char-inner ã«å岿¸ã¿ã®è¦ç´ ããªã»ãããã¦åã¢ãã¡ã¼ã·ã§ã³ 11686 const charInners = heading.querySelectorAll( 11687 ".section-heading__title-char-inner", 11688 ); 11689 if (charInners.length > 0) { 11690 gsap.killTweensOf(charInners
11690); 11691 gsap.set(charInners, { y: "100%" }); 11692 gsap.to(charInners, { 11693 y: "0%", 11694 duration: 0.5, 11695 ease: "power3.out", 11696 stagger: 0.04, 11697 onComplete: () => titleEnEl?.classList.add("is-inview"), 11698 }); 11699 } 11700 11701 // æ¥æ¬èªï¼è¡å岿¸ã¿ã®å ´å㯠line-innerãããã§ãªããã° titleJa ãããã¯ãã¢ãã¡ã¼ã·ã§ã³ 11702 const lineInners = heading.querySelectorAll( 11703 ".section-heading__title-ja-line-inner", 11704 ); 11705 if (lineInners.length > 0) { 11706 gsap.killTweensOf(lineInners); 11707 gsap.set(lineInners, { y: "100%" }); 11708 gsap.to(lineInners, { 11709 y: "0%", 11710 duration: 0.5, 11711 delay: 0.35, 11712 ease: "power3.out", 11713 stagger: 0.1, 11714 onComplete: () => titleJaEl?.classList.add("is-inview"), 11715 }); 11716 } else if (titleJaEl) { 11717 gsap.killTweensOf(titleJaEl); 11718 gsap.set(titleJaEl, { y: "100%" }); 11719 gsap.to(titleJaEl, { 11720 y: "0%", 11721 duration: 0.5, 11722 delay: 0.35, 11723 ease: "power3.out", 11724 onComplete: () => titleJaEl.classList.add("is-inview"), 11725 }); 11726 } 11727 }); 11728} 11729 11730/** 11731 * .group-company-list-item__date å ã®ãå¹´ããæããæ¥ãã«ä»ãæ°åã®ã¿ 11732 * span.group-company-list-item__date-number ã§å²ãï¼å¹´ã»æã»æ¥ã®æåã¯å²ã¾ãªãï¼ 11733 */ 11734function initGroupCompanyDate() { 11735 const datePattern = /(\d+)(å¹´|æ|æ¥)/g; 11736 const elements = document.querySelectorAll(".group-company-list-item__date"); 11737 11738 elements.forEach((el) => { 11739 const text = el.textContent; 11740 el.innerHTML = text.replace( 11741 datePattern, 11742 '<span class="group-company-list-item__date-number">$1</span>$2', 11743 ); 11744 }); 11745} 11746 11747document.addEventListener("DOMContentLoaded", () => { 11748 const select = document.querySelector(".filter-controls__select"); 11749 if (!select) return; 11750 if (select.options.length > 1) return; 11751 const currentYear = new Date().getFullYear(); 11752 11753 for (let year = currentYear; year >= 2002; year--) { 11754 const option = document.createElement("option"); 11755 option.value = year; 11756 option.textContent = year; 11757 select.appendChild(option); 11758 } 11759}); 11760 11761/** 11762 * Companyãã¼ã¸ STORY ã»ã¯ã·ã§ã³ 11763 * 11764 * ãã¿ã¤ãã«ãæ¹è¡(<br>)ã§è¡ãåãã1è¡ãã¤ã¹ã©ã¤ãã¢ãã(CodePen pvbNyRo ã®ãã£ããã³ãã¼æ¹å¼) 11765 * ãæ¬æã1è¡ãã¤ã¹ã¯ãã¼ã«é£åã®ã°ã©ãã¼ã·ã§ã³ãã¹ã¯ã§å·¦âå³ã«è¡¨ç¤º(start: top 90%, end: top 65%, scrub) 11766 * 11767 * GSAPã»ScrollTrigger 㯠Layout ã§ CDN èªã¿è¾¼ã¿æ¸ã¿åæ 11768 * ã¦ã£ã³ãã¦ãªãµã¤ãºæã«ååæåå¯¾å¿ 11769 */ 11770 11771let storyState = { 11772 mainSwiper: null, 11773 insetSwiper: null, 11774 autoplayInterval: null, 11775 originalTitleHTML: null}; 11776 11777/** 11778 * ã¿ã¤ãã«è¦ç´ ãã <br> ã§åºåãããè¡ã®ããã¹ããé åã§è¿ãã 11779 * ç¾å¨ã®ç»é¢ãµã¤ãºã«å¿ãã¦ã表示ãããªãbrãç¡è¦ããã 11780 * PC表示æ: pc-onlyã¨ã¯ã©ã¹ãªãbrã§æ¹è¡ãsp-onlyã¯ç¡è¦ 11781 * SP表示æ: sp-onlyã¨ã¯ã©ã¹ãªãbrã§æ¹è¡ãpc-onlyã¯ç¡è¦ 11782 */ 11783function parseTitleLines(titleEl) { 11784 const lines = []; 11785 let current = ""; 11786 const isMobile = window.innerWidth <= 767; 11787 11788 for (const node of titleEl.childNodes) { 11789 if (node.nodeType === Node.TEXT_NODE) { 11790 current += node.textContent || ""; 11791 } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "BR") { 11792 const isPcOnly = node.classList && node.classList.contains("pc-only"); 11793 const isSpOnly = node.classList && node.classList.contains("sp-only"); 11794 11795 // ã¯ã©ã¹ãªãbrã¯å¸¸ã«æ¹è¡ 11796 // PC表示æ: pc-onlyã¯æ¹è¡ãsp-onlyã¯ç¡è¦ 11797 // SP表示æ: sp-onlyã¯æ¹è¡ãpc-onlyã¯ç¡è¦ 11798 const shouldBreak = (!isPcOnly && !isSpOnly) || (isMobile ? isSpOnly : isPcOnly); 11799 11800 if (shouldBreak) { 11801 lines.push(current.trim()); 11802 current = ""; 11803 } 11804 } 11805 } 11806 if (current.trim().length) lines.push(current.trim()); 11807 return lines; 11808} 11809 11810/** 11811 * ã¿ã¤ãã«ãæ¹è¡ã§åºåã£ã¦1è¡ãã¤ã¹ã©ã¤ãã¢ããï¼CodePen pvbNyRo ã® catchcopy æ¹å¼ï¼ 11812 */ 11813function initStoryTitleAnimation() { 11814 const titleEl = document.querySelector( 11815 ".story__title[data-anim-title]", 11816 ); 11817 if (!titleEl) return; 11818 11819 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") { 11820 return; 11821 } 11822 11823 gsap.registerPlugin(ScrollTrigger); 11824 11825 if (!storyState.originalTitleHTML) { 11826 storyState.originalTitleHTML = titleEl.innerHTML; 11827 } 11828 11829 const lines = parseTitleLines(titleEl); 11830 if (lines.length === 0) return; 11831 11832 titleEl.innerHTML = ""; 11833
11834 lines.forEach((lineText) => { 11835 const wrapper = document.createElement("span"); 11836 wrapper.className = "story__title-line-wrapper"; 11837 const inner = document.createElement("span"); 11838 inner.className = "story__title-line"; 11839 inner.textContent = lineText; 11840 wrapper.appendChild(inner); 11841 titleEl.appendChild(wrapper); 11842 }); 11843 11844 const lineInners = titleEl.querySelectorAll(".story__title-line"); 11845 11846 gsap.from(lineInners, { 11847 y: "100%", 11848 duration: 0.9, 11849 ease: "power3.out", 11850 stagger: 0.15, 11851 scrollTrigger: { 11852 trigger: titleEl, 11853 start: "top 90%", 11854 toggleActions: "play none none none", 11855 }, 11856 }); 11857} 11858 11859/** 11860 * åçã¨ãªã¢:大ç»åã»å°ç»åãå¥ã¹ã¯ã¤ãã¼ã§åæã 11861 * setInterval ã§ä¸¡æ¹ãåæã« slideNext ããã¤ãã³ãèµ·å ã®ã«ã¼ã忢ãé²ãã 11862 * loop è¦å対ç: loopedSlides/loopAdditionalSlides ã¯æå®ãããslidesPerView:1 / slidesPerGroup:1 ãæç¤ºã 11863 * 大ç»å:5ç§è¡¨ç¤ºã1â1.15åãºã¼ã (7s)ã2ç§ãã§ã¼ãã 11864 * å°ç»å:3æã«ã¼ããåãæ¿ãæ1.5ç§ã§å³ãããã©ã©ãã¯ã¹ã 11865 */ 11866function initStorySwiper() { 11867 const mainEl = document.querySelector(".story__photo-main-swiper"); 11868 const insetEl = document.querySelector(".story__photo-inset-swiper"); 11869 if (!mainEl || !insetEl || typeof window.Swiper === "undefined") return; 11870 11871 const Swiper = window.Swiper; 11872 const AUTOPLAY_DELAY = 5000; 11873 const MAIN_SPEED = 2000; 11874 const INSET_SPEED = 1500; 11875 11876 const insetSwiper = new Swiper(insetEl, { 11877 loop: true, 11878 slidesPerView: 1, 11879 slidesPerGroup: 1, 11880 loopPreventsSliding: false, 11881 parallax: true, 11882 speed: INSET_SPEED, 11883 allowTouchMove: false, 11884 watchSlidesProgress: true, 11885 }); 11886 11887 const mainSwiper = new Swiper(mainEl, { 11888 loop: true, 11889 slidesPerView: 1, 11890 slidesPerGroup: 1, 11891 effect: "fade", 11892 fadeEffect: { crossFade: true }, 11893 speed: MAIN_SPEED, 11894 allowTouchMove: false, 11895 }); 11896 11897 insetSwiper.slideToLoop(mainSwiper.realIndex, 0, false); 11898 11899 const autoplayInterval = setInterval(() => { 11900 mainSwiper.slideNext(MAIN_SPEED); 11901 insetSwiper.slideNext(INSET_SPEED); 11902 }, AUTOPLAY_DELAY); 11903 11904 storyState.mainSwiper = mainSwiper; 11905 storyState.insetSwiper = insetSwiper; 11906 storyState.autoplayInterval = autoplayInterval; 11907} 11908 11909/** 11910 * æ¢åã®ã¢ãã¡ã¼ã·ã§ã³ã¨Swiperãã¯ãªã¼ã³ã¢ãã 11911 */ 11912function cleanupStoryAnim() { 11913 if (storyState.autoplayInterval) { 11914 clearInterval(storyState.autoplayInterval); 11915 storyState.autoplayInterval = null; 11916 } 11917 11918 if (storyState.mainSwiper) { 11919 storyState.mainSwiper.destroy(true, true); 11920 storyState.mainSwiper = null; 11921 } 11922 11923 if (storyState.insetSwiper) { 11924 storyState.insetSwiper.destroy(true, true); 11925 storyState.insetSwiper = null; 11926 } 11927 11928 if (typeof ScrollTrigger !== "undefined") { 11929 ScrollTrigger.getAll().forEach((trigger) => { 11930 const triggerEl = trigger.trigger; 11931 if ( 11932 triggerEl && 11933 (triggerEl.closest(".story") || triggerEl.classList.contains("story__title") || triggerEl.classList.contains("story__text-line")) 11934 ) { 11935 trigger.kill(); 11936 } 11937 }); 11938 } 11939 11940 const titleEl = document.querySelector(".story__title[data-anim-title]"); 11941 if (titleEl && storyState.originalTitleHTML) { 11942 titleEl.innerHTML = storyState.originalTitleHTML; 11943 } 11944 11945 document.querySelector(".story__text-wrap"); 11946} 11947 11948/** 11949 * STORYã»ã¯ã·ã§ã³ã®ã¢ãã¡ã¼ã·ã§ã³ãåæå 11950 */ 11951function initStoryAnim() { 11952 initStoryTitleAnimation(); 11953 initStorySwiper(); 11954} 11955 11956/** 11957 * ãªãµã¤ãºæã®ååæåï¼ããã¦ã³ã¹å¦çä»ãï¼ 11958 */ 11959let resizeTimer = null; 11960function handleStoryResize() { 11961 if (resizeTimer) clearTimeout(resizeTimer); 11962 11963 resizeTimer = setTimeout(() => { 11964 if (document.querySelector(".story")) { 11965 cleanupStoryAnim(); 11966 initStoryAnim(); 11967 } 11968 }, 300); 11969} 11970 11971// .story ãåå¨ãããã¼ã¸ã§ã®ã¿åæåï¼app.js ã§å¼ã³åºãä¸è¦ï¼ 11972document.addEventListener("DOMContentLoaded", () => { 11973 if (document.querySelector(".story")) { 11974 initStoryAnim(); 11975 window.addEventListener("resize", handleStoryResize); 11976 } 11977}); 11978 11979gsap.registerPlugin(ScrollTrigger); 11980 11981// PC表示ã®å¤å®ï¼768px以ä¸ï¼ 11982const PC_BREAKPOINT = 768; 11983const isPC = () => window.innerWidth >= PC_BREAKPOINT; 11984 11985// ScrollTriggerã¤ã³ã¹ã¿ã³ã¹ãä¿æããé åï¼PCå°ç¨ï¼ 11986let pcScrollTriggers = []; 11987// ScrollTriggerã¤ã³ã¹ã¿ã³ã¹ãä¿æããé åï¼å ±éï¼ 11988let commonScrollTriggers = []; 11989
11990// PCå°ç¨ã¢ãã¡ã¼ã·ã§ã³ã®åæåï¼ç»åã®ããããããã¹ããã§ã¼ããpinï¼ 11991function initPCOnlyAnimation(container) { 11992 // æ¢åã®PCå°ç¨ScrollTriggerããã¹ã¦åé¤ 11993 pcScrollTriggers.forEach(trigger => trigger.kill()); 11994 pcScrollTriggers = []; 11995 11996 // PC表示ã§ãªãå ´åã¯ä½ãããªã 11997 if (!isPC()) { 11998 return; 11999 } 12000 12001 const imageBoxes = gsap.utils.toArray( 12002 container.querySelectorAll(".our-philosophy__image-box"), 12003 ); 12004 const images = gsap.utils.toArray( 12005 container.querySelectorAll(".our-philosophy__image-box:not(:last-of-type)"), 12006 ); 12007 if (imageBoxes.length > 0) { 12008 // ç»åã®z-indexè¨å® 12009 gsap.set(imageBoxes, { zIndex: (i, target, targets) => targets.length - i }); 12010 } 12011 12012 // ç»åã®ãããã¢ãã¡ã¼ã·ã§ã³ï¼PCå°ç¨ï¼ 12013 images.forEach((image, i) => { 12014 const tl = gsap.timeline({ 12015 scrollTrigger: { 12016 trigger: container, 12017 start: () => "top -" + window.innerHeight * (i + 0.5), 12018 end: () => "+=" + window.innerHeight, 12019 scrub: true, 12020 toggleActions: "play none reverse none", 12021 invalidateOnRefresh: true 12022 } 12023 }); 12024 12025 tl.to(image, { height: 0 }); 12026 12027 if (tl.scrollTrigger) { 12028 pcScrollTriggers.push(tl.scrollTrigger); 12029 } 12030 }); 12031 12032 const texts = gsap.utils.toArray(container.querySelectorAll(".our-philosophy__text")); 12033 if (texts.length > 0) { 12034 // ããã¹ãã®z-indexè¨å® 12035 gsap.set(texts, { zIndex: (i, target, targets) => targets.length - i }); 12036 } 12037 12038 // ããã¹ãã®ãã§ã¼ãã¢ãã¡ã¼ã·ã§ã³ï¼PCå°ç¨ï¼ 12039 texts.forEach((text, i) => { 12040 const isLastText = i === texts.length - 1; 12041 const tl = gsap.timeline({ 12042 scrollTrigger: { 12043 trigger: container, 12044 start: () => "top -" + window.innerHeight * i, 12045 end: () => "+=" + window.innerHeight, 12046 scrub: true, 12047 toggleActions: "play none reverse none", 12048 invalidateOnRefresh: true 12049 } 12050 }); 12051 12052 tl.to(text, { duration: 0.33, opacity: 1, y: "-50%" }); 12053 if (!isLastText) { 12054 tl.to(text, { duration: 0.33, opacity: 0, y: "-100%" }, 0.66); 12055 } else { 12056 // æå¾ã®ããã¹ãã¯è¡¨ç¤ºããã¾ã¾ã«ãã¤ã¤ãä»è¦ç´ ã¨åãã¹ã¯ãã¼ã«å°ºãç¶æãã 12057 tl.to(text, { duration: 0.33, opacity: 1, y: "-50%" }, 0.66); 12058 } 12059 12060 if (tl.scrollTrigger) { 12061 pcScrollTriggers.push(tl.scrollTrigger); 12062 } 12063 }); 12064 12065 const section = container.closest(".our-philosophy"); 12066 const pinWrap = section?.querySelector(".our-philosophy__pin-wrap"); 12067 const imageBoxWrap = container.querySelector(".our-philosophy__image-box-wrap"); 12068 12069 // pinã¢ãã¡ã¼ã·ã§ã³ï¼PCå°ç¨ï¼ 12070 const pinTrigger = ScrollTrigger.create({ 12071 trigger: imageBoxWrap || pinWrap || container, 12072 scrub: true, 12073 pin: pinWrap || container, 12074 // pinã®éå§ã»è§£é¤æã®æ¥ãªåº§æ¨è£æ£ã«ããã¬ã¯ã¤ããæãã 12075 anticipatePin: 1, 12076 pinReparent: true, 12077 // fixedåãæ¿ãç±æ¥ã®å¾®å°ãªã¸ã£ã³ããé¿ããããtransformã§pinãã 12078 pinType: "transform", 12079 // image-box-wrap ã®åºæºä½ç½®ãã 150px ä¸ã§ pin éå§ï¼è¦åã鲿¢ï¼ 12080 start: () => "top top+=50", 12081 end: () => "+=" + (images.length + 1) * window.innerHeight, 12082 invalidateOnRefresh: true 12083 }); 12084 pcScrollTriggers.push(pinTrigger); 12085} 12086 12087// å ±éã¢ãã¡ã¼ã·ã§ã³ã®åæåï¼èæ¯ã°ã©ãã¼ã·ã§ã³ãããã¹ãã«ã©ã¼å¤æ´ï¼ 12088function initCommonAnimation(container) { 12089 // æ¢åã®å ±éScrollTriggerããã¹ã¦åé¤ 12090 commonScrollTriggers.forEach(trigger => trigger.kill()); 12091 commonScrollTriggers = []; 12092 12093 const section = container.closest(".our-philosophy"); 12094 if (!section) return; 12095 12096 const bgGradientEl = section.querySelector(".our-philosophy__bg-gradient"); 12097 const bgLogoEl = section.querySelector(".our-philosophy__bg-logo"); 12098 const bgDuration = 1; 12099 gsap.utils.toArray( 12100 container.querySelectorAll(".our-philosophy__image-box:not(:last-of-type)"), 12101 ); 12102 12103 function applyEnterState() { 12104 if (bgGradientEl) { 12105 gsap.to(bgGradientEl, { 12106 opacity: 1, 12107 duration: bgDuration, 12108 ease: "none", 12109 }); 12110 } 12111 if (bgLogoEl) { 12112 gsap.to(bgLogoEl, { 12113 opacity: 1, 12114 duration: bgDuration, 12115 ease: "none", 12116 }); 12117 } 12118 gsap.to(".our-philosophy__lead", { color: "#fff", ease: "none" }); 12119 gsap.to(".our-philosophy__body", { color: "#fff", ease: "none" }); 12120 gsap.to(".our-philosophy__symbol svg", { opacity: 0.1, ease: "none" });
12121 gsap.to(".our-philosophy__section-heading", { color: "#fff", ease: "none" }); 12122 gsap.to(".story__heading", { color: "#fff", ease: "none" }); 12123 gsap.to(".story__title", { color: "#fff", ease: "none" }); 12124 gsap.to(".story__text-wrap", { color: "#fff", ease: "none" }); 12125 } 12126 12127 function applyLeaveState() { 12128 if (bgGradientEl) { 12129 gsap.to(bgGradientEl, { 12130 opacity: 0, 12131 duration: bgDuration, 12132 ease: "none", 12133 }); 12134 } 12135 if (bgLogoEl) { 12136 gsap.to(bgLogoEl, { 12137 opacity: 0, 12138 duration: bgDuration, 12139 ease: "none", 12140 }); 12141 } 12142 gsap.to(".our-philosophy__lead", { color: "#000", ease: "none" }); 12143 gsap.to(".our-philosophy__body", { color: "#1a1a1a", ease: "none" }); 12144 gsap.to(".our-philosophy__symbol svg", { opacity: 1, ease: "none" }); 12145 gsap.to(".our-philosophy__section-heading", { color: "#000", ease: "none" }); 12146 gsap.to(".story__heading", { color: "#000", ease: "none" }); 12147 gsap.to(".story__title", { color: "#000000", ease: "none" }); 12148 gsap.to(".story__text-wrap", { color: "#000", ease: "none" }); 12149 } 12150 12151 // èæ¯ã°ã©ãã¼ã·ã§ã³ã¨ããã¹ãã«ã©ã¼ã®å¤æ´ï¼PC/SPå ±éï¼ 12152 const colorTrigger = ScrollTrigger.create({ 12153 trigger: section, 12154 start: "top 50%", 12155 end: () => isPC() ? "bottom 20%" : "bottom 50%", 12156 onEnter: applyEnterState, 12157 onEnterBack: applyEnterState, 12158 onLeave: applyLeaveState, 12159 onLeaveBack: applyLeaveState, 12160 invalidateOnRefresh: true 12161 }); 12162 commonScrollTriggers.push(colorTrigger); 12163} 12164 12165// å ¨ã¢ãã¡ã¼ã·ã§ã³ã®åæå 12166function initAllAnimations() { 12167 const container = document.querySelector(".our-philosophy__container"); 12168 if (!container) { 12169 // 対象ã»ã¯ã·ã§ã³ããªããã¼ã¸ã§ã¯åæåããªã 12170 return; 12171 } 12172 12173 initPCOnlyAnimation(container); 12174 initCommonAnimation(container); 12175} 12176 12177// ãªãµã¤ãºæã®å¦çï¼ããã¦ã³ã¹ä»ãï¼ 12178function handleResize() { 12179 initAllAnimations(); 12180 scheduleScrollTriggerRefresh(); 12181} 12182 12183// åæå 12184document.addEventListener("DOMContentLoaded", () => { 12185 initAllAnimations(); 12186}); 12187 12188// ãªãµã¤ãºç£è¦ 12189addDebouncedResizeListener(handleResize, 200); 12190 12191/** 12192 * ãµã¹ããããªã㣠OUR MISSION ã»ã¯ã·ã§ã³ 12193 * 12194 * 1. è¦åºã: å ±éã¢ã¸ã¥ã¼ã«(_heading-anim.js)ã§è±èª1æåãã¤+æ¥æ¬èªã»ããã§ä¸âä¸ã¹ã©ã¤ã 12195 * 2. ãã£ããã³ãã¼: ã¹ã¯ãã¼ã«ã§è¡ã»ãããä¸âä¸ã¹ã©ã¤ãã¢ãã 12196 * 3. æ¬æ: ã¹ã¯ãã¼ã«ã§1è¡ãã¤ã°ã©ãã¼ã·ã§ã³ãã¹ã¯è¡¨ç¤º 12197 * 4. ã¹ã©ã¤ãã¼: 大ç»å(5ç§ãºã¼ã +ã¯ãã¹ãã§ã¼ã) + å°ç»å(å³ãããã©ã©ãã¯ã¹) 12198 * PC: 大ç»å=.sus-mission__slider-big, å°ç»å=.sus-mission__slider-small (å³ä¸çµ¶å¯¾é ç½®) 12199 * SP: .sus-mission__slider-sp å ã«å¤§+å°ã㢠12200 */ 12201 12202 12203// ââ ã¹ã©ã¤ãã¼ï¼Swiperãã¼ã¹ãcompanyã®OUR VISIONã¨åæ§ã®æåï¼ââ 12204function initSusMissionSlider() { 12205 if (typeof window.Swiper === "undefined") return; 12206 const Swiper = window.Swiper; 12207 12208 const AUTOPLAY_DELAY = 5000; 12209 const MAIN_SPEED = 2000; 12210 const INSET_SPEED = 1500; 12211 12212 const isSp = window.innerWidth < 768; 12213 12214 // PCç¨ 12215 const pcBigEl = document.querySelector(".sus-mission__big-swiper"); 12216 const pcSmallEl = document.querySelector(".sus-mission__small-swiper"); 12217 12218 // SPç¨ 12219 const spBigEl = document.querySelector(".sus-mission__sp-big-swiper"); 12220 const spSmallEl = document.querySelector(".sus-mission__sp-small-swiper"); 12221 12222 const bigEl = isSp ? spBigEl : pcBigEl; 12223 const smallEl = isSp ? spSmallEl : pcSmallEl; 12224 12225 if (!bigEl || !smallEl) return; 12226 12227 const smallSwiper = new Swiper(smallEl, { 12228 loop: true, 12229 slidesPerView: 1, 12230 slidesPerGroup: 1, 12231 loopPreventsSliding: false, 12232 parallax: true, 12233 speed: INSET_SPEED, 12234 allowTouchMove: false, 12235 watchSlidesProgress: true, 12236 }); 12237 12238 const bigSwiper = new Swiper(bigEl, { 12239 loop: true, 12240 slidesPerView: 1, 12241 slidesPerGroup: 1, 12242 effect: "fade", 12243 fadeEffect: { crossFade: true }, 12244 speed: MAIN_SPEED, 12245 allowTouchMove: false, 12246 }); 12247 12248 // åæä½ç½®ãæãã 12249 smallSwiper.slideToLoop(bigSwiper.realIndex, 0, false); 12250 12251 // åä¸ã¿ã¤ãã³ã°ã§ä¸¡æ¹ã次ã¸ã 12252 setInterval(() => { 12253 bigSwiper.slideNext(MAIN_SPEED); 12254 smallSwiper.slideNext(INSET_SPEED); 12255 }, AUTOPLAY_DELAY); 12256} 12257 12258// ââ åæå ââ 12259function initSusMission() {
12260 initSusMissionSlider(); 12261} 12262 12263window.addEventListener("DOMContentLoaded", () => { 12264 if (document.querySelector(".sus-mission")) { 12265 initSusMission(); 12266 } 12267}); 12268 12269/** 12270 * ãµã¹ããããªã㣠TOP MESSAGE ã»ã¯ã·ã§ã³ 12271 * 12272 * 1. èæ¯è²ã®éå¤åï¼ScrollTrigger onToggle + CSSã¯ã©ã¹ is-blueï¼ 12273 * - GSAP: bg-gradient ã® opacity ã®ã¿ 12274 * - CSS: ããã¹ãè²ã¯ CSS夿° + transition ã§ç®¡çï¼inline styleæ±æãªãï¼ 12275 * 2. ãã£ããã³ãã¼: ã¹ã¯ãã¼ã«ã§è¡ãã¨ã¹ã©ã¤ãã¢ããï¼sus-missionã¨åãï¼ 12276 * 3. æ¬æ: ååãã表示ï¼ã¢ãã¡ãªãï¼ 12277 * 4. ã¹ã¯ãã¼ã«ã«ã¼ãè±èªããã¹ã: CSS animationï¼SCSSå´ã§å®è£ æ¸ã¿ï¼ 12278 */ 12279 12280 12281// ââ èæ¯è²ã®éå¤åï¼ScrollTrigger + CSSã¯ã©ã¹ï¼ââ 12282function initSusTopMessageBg() { 12283 const section = document.querySelector(".sus-topmessage"); 12284 if (!section) return; 12285 12286 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") return; 12287 gsap.registerPlugin(ScrollTrigger); 12288 12289 const bgGradientEl = section.querySelector(".sus-topmessage__bg-gradient"); 12290 if (!bgGradientEl) return; 12291 12292 ScrollTrigger.create({ 12293 trigger: section, 12294 start: "top 50%", 12295 end: window.innerWidth < 768 ? "bottom 30%" : "bottom 50%", 12296 onToggle: ({ isActive }) => { 12297 section.classList.toggle("is-blue", isActive); 12298 const missionSection = document.querySelector(".sus-mission"); 12299 if (missionSection) missionSection.classList.toggle("is-blue", isActive); 12300 const sdgsSection = document.querySelector(".sus-sdgs"); 12301 if (sdgsSection) sdgsSection.classList.toggle("is-blue", isActive); 12302 gsap.to(bgGradientEl, { 12303 opacity: isActive ? 1 : 0, 12304 duration: 1, 12305 ease: "none", 12306 overwrite: "auto", 12307 }); 12308 }, 12309 }); 12310} 12311 12312// ââ åæå ââ 12313function initSusTopMessage() { 12314 initSusTopMessageBg(); 12315} 12316 12317window.addEventListener("DOMContentLoaded", () => { 12318 if (document.querySelector(".sus-topmessage")) { 12319 initSusTopMessage(); 12320 } 12321}); 12322 12323if (document.querySelector('.year-nav__link')) { 12324 document.querySelectorAll('.year-nav__link[href^="#"]').forEach(anchor => { 12325 anchor.addEventListener('click', function (e) { 12326 e.preventDefault(); 12327 const targetId = this.getAttribute('href'); 12328 const targetElement = document.querySelector(targetId); 12329 if (targetElement) { 12330 window.scrollTo({ 12331 top: targetElement.offsetTop, 12332 behavior: 'smooth' 12333 }); 12334 } 12335 }); 12336 }); 12337} 12338 12339document.addEventListener("DOMContentLoaded", () => { 12340 const params = new URLSearchParams(window.location.search); 12341 if (!params.has("ir")) return; 12342 12343 const irInput = document.querySelector( 12344 '.filter-controls__input[name="category"][value="ir-information"]' 12345 ); 12346 if (irInput) { 12347 irInput.checked = true; 12348 irInput.dispatchEvent(new Event("change", { bubbles: true })); 12349 } 12350}); 12351 12352if (document.querySelector('.privacy')) { 12353 document.querySelectorAll('.privacy a[href^="#"]').forEach(anchor => { 12354 anchor.addEventListener('click', function (e) { 12355 e.preventDefault(); 12356 const targetId = this.getAttribute('href'); 12357 const targetElement = document.querySelector(targetId); 12358 if (targetElement) { 12359 const isSp = window.matchMedia('(max-width: 767px)').matches; 12360 const offset = isSp 12361 ? (100 / 390) * window.innerWidth 12362 : parseFloat(getComputedStyle(document.documentElement).fontSize) * (40 / 16); 12363 window.scrollTo({ 12364 top: targetElement.offsetTop - offset, 12365 behavior: 'smooth' 12366 }); 12367 } 12368 }); 12369 }); 12370} 12371 12372(function (c, n, s) { 12373 if (c[n] === void 0) { 12374 c["ULObject"] = n; 12375 c[n] = 12376 c[n] || 12377 function () { 12378 (c[n].q = c[n].q || []).push(arguments); 12379 }; 12380 c[n].l = 1 * new Date(); 12381 var e = document.createElement("script"); 12382 e.async = 1; 12383 e.src = s + "/chatbot.js"; 12384 var t = document.getElementsByTagName("script")[0]; 12385 t.parentNode.insertBefore(e, t); 12386 } 12387})(window, "ul_widget", "https://support-widget.userlocal.jp"); 12388ul_widget("init", { 12389 id: "d0f1efeac56509fa1818", 12390 site_in: true, 12391 lg_id: "", 12392}); 12393 12394function initFutureNavi() { 12395 const nav = document.querySelector("[data-future-navi]"); 12396 if (!nav) return; 12397 12398 const tabs = nav.querySelectorAll("[role='tab']"); 12399 const spContainer = nav.querySelector("[data-future-navi-sp]");
12400 const spTrigger = nav.querySelector("[data-future-navi-trigger]"); 12401 const spLabel = nav.querySelector("[data-future-navi-label]"); 12402 nav.querySelector("[data-future-navi-sp-list]"); 12403 const spOptionBtns = nav.querySelectorAll(".future-navi__sp-option-btn"); 12404 12405 const activateTab = (index) => { 12406 const tabIndex = String(index); 12407 12408 // PC ã¿ãæ´æ° 12409 tabs.forEach((t) => { 12410 t.setAttribute("aria-selected", "false"); 12411 t.closest(".future-navi__item").classList.remove("is-current"); 12412 }); 12413 const target = tabs[index]; 12414 if (!target) return; 12415 target.setAttribute("aria-selected", "true"); 12416 target.closest(".future-navi__item").classList.add("is-current"); 12417 12418 // SP ã©ãã«ã»ãªãã·ã§ã³æ´æ° 12419 if (spLabel) { 12420 spLabel.textContent = target.querySelector(".future-navi__text")?.textContent ?? ""; 12421 } 12422 spOptionBtns.forEach((btn) => { 12423 const option = btn.closest(".future-navi__sp-option"); 12424 const isCurrent = btn.dataset.tabIndex === tabIndex; 12425 option.classList.toggle("is-current", isCurrent); 12426 option.setAttribute("aria-selected", isCurrent ? "true" : "false"); 12427 }); 12428 12429 // ããã«åãæ¿ã 12430 document.querySelectorAll("[data-future-panel]").forEach((panel) => { 12431 const isActive = panel.dataset.futurePanel === tabIndex; 12432 panel.classList.toggle("is-active", isActive); 12433 if (isActive) { 12434 replaySectionHeadingAnimation(panel); 12435 } 12436 }); 12437 }; 12438 12439 const updateUrl = (index) => { 12440 const url = new URL(window.location.href); 12441 url.searchParams.set("tab", index + 1); 12442 history.replaceState(null, "", url); 12443 }; 12444 12445 const closeDropdown = () => { 12446 if (!spContainer) return; 12447 spContainer.classList.remove("is-open"); 12448 spTrigger?.setAttribute("aria-expanded", "false"); 12449 }; 12450 12451 const openDropdown = () => { 12452 if (!spContainer) return; 12453 spContainer.classList.add("is-open"); 12454 spTrigger?.setAttribute("aria-expanded", "true"); 12455 }; 12456 12457 // URLã¯ã¨ãªãã©ã¡ã¼ã¿ã¼ããåæã¿ãã復å ï¼1å§ã¾ã: ?tab=1 â index 0ï¼ 12458 const params = new URLSearchParams(window.location.search); 12459 const tabParam = parseInt(params.get("tab") ?? "1", 10); 12460 const initialIndex = Math.min(Math.max(tabParam - 1, 0), tabs.length - 1); 12461 if (initialIndex > 0) { 12462 activateTab(initialIndex); 12463 } 12464 12465 // PC ã¿ãã¯ãªãã¯
12466 tabs.forEach((tab) => { 12467 tab.addEventListener("click", () => { 12468 if (tab.getAttribute("aria-selected") === "true") return; 12469 const index = parseInt(tab.dataset.tabIndex, 10); 12470 activateTab(index); 12471 updateUrl(index); 12472 }); 12473 }); 12474 12475 // SP ããªã¬ã¼ã¯ãªãã¯ï¼ééï¼ 12476 spTrigger?.addEventListener("click", () => { 12477 if (spContainer.classList.contains("is-open")) { 12478 closeDropdown(); 12479 } else { 12480 openDropdown(); 12481 } 12482 }); 12483 12484 // SP ãªãã·ã§ã³é¸æ 12485 spOptionBtns.forEach((btn) => { 12486 btn.addEventListener("click", () => { 12487 const index = parseInt(btn.dataset.tabIndex, 10); 12488 activateTab(index); 12489 updateUrl(index); 12490 closeDropdown(); 12491 }); 12492 }); 12493 12494 // ãããããã¦ã³å¤ã¯ãªãã¯ã§éãã 12495 document.addEventListener("click", (e) => { 12496 if (spContainer && !spContainer.contains(e.target)) { 12497 closeDropdown(); 12498 } 12499 }); 12500} 12501 12502// ======================================== 12503// Lenisï¼ã¹ã ã¼ã¹ã¹ã¯ãã¼ã«ï¼ã®åæå 12504// ======================================== 12505const initLenis = (retries = 0) => { 12506 if (typeof Lenis === "undefined") { 12507 if (retries > 100) return; 12508 setTimeout(() => initLenis(retries + 1), 50); 12509 return; 12510 } 12511 12512 const lenis = new Lenis({ 12513 duration: 1.2, 12514 easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), 12515 orientation: "vertical", 12516 gestureOrientation: "vertical", 12517 smoothWheel: true, 12518 wheelMultiplier: 1, 12519 syncTouch: true, // ã¿ãããLenisã§å¦ç 12520 touchMultiplier: 1, // æã®åãã«å¯¾ããã¹ã¯ãã¼ã«éï¼2â1ã§ãã£ããã«ï¼ 12521 touchInertiaMultiplier: 28, // ããªãã¯æã®æ £æ§ã®å¼·ãï¼å°ããã§ç©ããã«ï¼ 12522 }); 12523 12524 // GSAP ScrollTriggerã¨çµ±åï¼scrollerProxyã§Lenisã®ã¹ã¯ãã¼ã«å¤ãåç §ãscrubæã®æ £æ§ãå¹ãããã«ããï¼ 12525 if (typeof gsap !== "undefined" && typeof ScrollTrigger !== "undefined") { 12526 ScrollTrigger.scrollerProxy(document.body, { 12527 scrollTop(value) { 12528 if (arguments.length) { 12529 lenis.scrollTo(value, { immediate: true }); 12530 } 12531 return lenis.scroll; 12532 }, 12533 getBoundingClientRect() { 12534 return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight }; 12535 }, 12536 }); 12537 lenis.on("scroll", ScrollTrigger.update); 12538 12539 gsap.ticker.add((time) => { 12540 lenis.raf(time * 1000); 12541 }); 12542 12543 gsap.ticker.lagSmoothing(0); 12544 } else { 12545 // GSAPããªãå ´åã®é常ã®raf 12546 function raf(time) { 12547 lenis.raf(time); 12548 requestAnimationFrame(raf); 12549 } 12550 requestAnimationFrame(raf); 12551 } 12552 12553 // ã°ãã¼ãã«ã«å ¬éï¼ä»ã®ã¹ã¯ãªããããã¢ã¯ã»ã¹å¯è½ã«ï¼ 12554 window.lenis = lenis; 12555 12556 return lenis; 12557};
12558 12559document.addEventListener("DOMContentLoaded", () => { 12560 initLenis(); 12561 12562 // Voice / UserLocal FAQ: åã込㿠iframe ã« data-lenis-preventï¼iframe ä¸ã ã Lenis ç¡å¹ï¼ 12563 const voiceRoot = document.querySelector(".voice"); 12564 const ulAiWidget = document.querySelector("#ulAiWidget"); 12565 const applyVoiceIframeLenisPrevent = () => { 12566 if (!voiceRoot) return; 12567 voiceRoot.querySelectorAll("iframe").forEach((el) => { 12568 el.setAttribute("data-lenis-prevent", ""); 12569 }); 12570 }; 12571 if (voiceRoot) { 12572 applyVoiceIframeLenisPrevent(); 12573 if (ulAiWidget) { 12574 const observer = new MutationObserver(() => { 12575 applyVoiceIframeLenisPrevent(); 12576 }); 12577 observer.observe(ulAiWidget, { childList: true, subtree: true }); 12578 } 12579 } 12580 12581 initHeaderPc(); 12582 initHeaderSp(); 12583 initIrSectionKv(); 12584 initIrSectionList(); 12585 initNewsSectionHeadingAnimation(); 12586 initCtaPanelTitleAnimation(); 12587 initFvVideo(); // åç»srcè¨å®ï¼åçï¼Safari対å¿ï¼ 12588 initFvScroll(); 12589 initFvVideoToggle(); 12590 initFvBgHide(); 12591 initFvNewsTickerAlign(); 12592 initFixedNewsChat(); 12593 12594 // SPçã¨PCçã§ã¢ãã¡ã¼ã·ã§ã³ãåãã 12595 if (window.innerWidth < 768) { 12596 initBrandListAnimationSP(); 12597 } else { 12598 initBrandListAnimation(); 12599 } 12600 12601 // ãã¬ã¼ã¯ãã¤ã³ãè·¨ãæã«ãªãã¼ãï¼JSåæåãããç´ãããï¼ 12602 window.matchMedia("(max-width: 767px)").addEventListener("change", () => location.reload()); 12603 12604 initSustainabilityKv(); 12605 initSustainabilityAccordion(); 12606 12607 initGroupCompanyDate(); 12608 // Aboutãã¼ã¸ã®ã¿ãã¸ã§ã³ã»ã¯ã·ã§ã³ã®ã¢ãã¡ã¼ã·ã§ã³ãæå¹å 12609 if (document.querySelector(".about-vision")) { 12610 initAboutVision(); 12611 } 12612 // Companyãã¼ã¸ OUR VISION ã»ã¯ã·ã§ã³ï¼ã¿ã¤ãã«1æåãã¤ï¼æ¬æè¡åå²ï¼ã¹ã¯ã¤ãã¼ï¼ 12613 if (document.querySelector(".our-vision")) { 12614 initOurVisionAnim(); 12615 } 12616 // SectionHeading å ±éã¢ãã¡ï¼è±èª1æåãã¤ï¼æ¥æ¬èªããã㯠ã¹ã©ã¤ãã¢ããï¼ 12617 initSectionHeadingAnimation(); 12618 12619 initFutureNavi(); 12620 12621 // 横ã¹ã¯ãã¼ã«ãã¼ãã«ã® Lenis ç«¶åãè§£æ¶ãã 12622 // wheel: 横ã¹ã¯ãã¼ã«æã®ã¿ stopPropagation ã§ Lenis ãã¹ããã 12623 // touch: 横ã¹ã¯ã¤ãæã®ã¿ stopPropagationï¼ç¸¦ã¹ã¯ã¤ã㯠Lenis ã«ä¼æãã¦ãã¼ã¸ã¹ã¯ãã¼ã«ï¼ 12624 // definition ãã¼ãã«ã¯æ¨ªã¹ã¯ãã¼ã«ä¸è¦ãªããå¯¾è±¡å¤ 12625 document.querySelectorAll(".editor-table-wrap").forEach((el) => { 12626 el.removeAttribute("data-lenis-prevent"); 12627 el.removeAttribute("data-lenis-prevent-touch"); 12628 12629 // definition ãã¼ãã«ã¯æ¨ªã¹ã¯ãã¼ã«ä¸è¦ãªãã touch ãé¤å¤ããªãï¼ç¸¦ã¹ã¯ãã¼ã«ç¶æï¼ 12630 if (el.querySelector(".editor-table--definition")) return; 12631 12632 // wheel: 横ã¹ã¯ãã¼ã«æã®ã¿ Lenis ã¸æ¸¡ããªã 12633 el.addEventListener("wheel", (e) => { 12634 const canScrollX = el.scrollWidth > el.clientWidth; 12635 if (!canScrollX) return; 12636 const isHorizontalInput = Math.abs(e.deltaX) > Math.abs(e.deltaY); 12637 if (!isHorizontalInput) return; 12638 e.stopPropagation(); 12639 }); 12640 12641 // touch: 横ã¹ã¯ã¤ãæã®ã¿ Lenis ã¸æ¸¡ããªãï¼ç¸¦ã¹ã¯ã¤ã㯠Lenis ã«ä¼æããã¦ãã¼ã¸ã¹ã¯ãã¼ã«ï¼ 12642 let touchStartX = 0; 12643 let touchStartY = 0; 12644 let isHorizontalSwipe = null; 12645 let canScrollX = false; 12646 12647 el.addEventListener("touchstart", (e) => { 12648 touchStartX = e.touches[0].clientX; 12649 touchStartY = e.touches[0].clientY; 12650 isHorizontalSwipe = null; 12651 canScrollX = el.scrollWidth > el.clientWidth; 12652 }, { passive: true }); 12653 12654 el.addEventListener("touchmove", (e) => { 12655 if (isHorizontalSwipe === null) { 12656 const dx = Math.abs(e.touches[0].clientX - touchStartX); 12657 const dy = Math.abs(e.touches[0].clientY - touchStartY); 12658 if (dx === 0 && dy === 0) return; 12659 isHorizontalSwipe = dx > dy; 12660 } 12661 if (isHorizontalSwipe && canScrollX) { 12662 e.stopPropagation(); 12663 } 12664 }, { passive: true }); 12665 }); 12666 12667 // ãµã¤ããã¼: ã¯ã¿åºãæã®ã¿ç¬ç«ã¹ã¯ãã¼ã«ã端å°éã§é常ã¹ã¯ãã¼ã«ã«æ»ã 12668 document.querySelectorAll(".sus-page__side-inner").forEach((inner) => { 12669 const THRESHOLD = 2; 12670 12671 const update = () => { 12672 if (inner.scrollHeight <= inner.clientHeight) { 12673 inner.removeAttribute("data-lenis-prevent"); 12674 return; 12675 } 12676 const atTop = inner.scrollTop <= THRESHOLD; 12677 const atBottom = inner.scrollTop + inner.clientHeight >= inner.scrollHeight - THRESHOLD; 12678 if (atTop || atBottom) { 12679 inner.removeAttribute("data-lenis-prevent"); 12680 } else { 12681 inner.setAttribute("data-lenis-prevent", ""); 12682 } 12683 }; 12684 12685 inner.addEventListener("scroll", update, { passive: true });
12686 inner.addEventListener("wheel", (e) => { 12687 if (inner.scrollHeight <= inner.clientHeight) return; 12688 const atTop = inner.scrollTop <= THRESHOLD; 12689 const atBottom = inner.scrollTop + inner.clientHeight >= inner.scrollHeight - THRESHOLD; 12690 // 端ã«ããæãããã«ç«¯æ¹åã¸ã®ã¹ã¯ãã¼ã«ã¯é常ã¹ã¯ãã¼ã« 12691 // éæ¹åãªããµã¤ããã¼ã¹ã¯ãã¼ã«ã«åãæ¿ã 12692 if (atTop && e.deltaY < 0) return; 12693 if (atBottom && e.deltaY > 0) return; 12694 inner.setAttribute("data-lenis-prevent", ""); 12695 }, { passive: true }); 12696 12697 update(); 12698 window.addEventListener("resize", update); 12699 }); 12700}); 12701 12702})(); 12703 12704;(() => { 12705// ----- script8.js ----- 12706// (vitePreload removed) 12707 12708 12709/*! 12710 * PhotoSwipe Lightbox 5.4.4 - https://photoswipe.com 12711 * (c) 2024 Dmytro Semenov 12712 */ 12713/** @typedef {import('../photoswipe.js').Point} Point */ 12714 12715/** 12716 * @template {keyof HTMLElementTagNameMap} T 12717 * @param {string} className 12718 * @param {T} tagName 12719 * @param {Node} [appendToEl] 12720 * @returns {HTMLElementTagNameMap[T]} 12721 */ 12722function createElement(className, tagName, appendToEl) { 12723 const el = document.createElement(tagName); 12724 12725 if (className) { 12726 el.className = className; 12727 } 12728 12729 if (appendToEl) { 12730 appendToEl.appendChild(el); 12731 } 12732 12733 return el; 12734} 12735/** 12736 * Get transform string 12737 * 12738 * @param {number} x 12739 * @param {number} [y] 12740 * @param {number} [scale] 12741 * @returns {string} 12742 */ 12743 12744function toTransformString(x, y, scale) { 12745 let propValue = `translate3d(${x}px,${
vendor: 54,205 bytes, lines 12745-14666
127450}px,0)`; 12746 12747 if (scale !== undefined) { 12748 propValue += ` scale3d(${scale},${scale},1)`; 12749 } 12750 12751 return propValue; 12752} 12753/** 12754 * Apply width and height CSS properties to element 12755 * 12756 * @param {HTMLElement} el 12757 * @param {string | number} w 12758 * @param {string | number} h 12759 */ 12760 12761function setWidthHeight(el, w, h) { 12762 el.style.width = typeof w === 'number' ? `${w}px` : w; 12763 el.style.height = typeof h === 'number' ? `${h}px` : h; 12764} 12765/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */ 12766 12767/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */ 12768 12769const LOAD_STATE = { 12770 IDLE: 'idle', 12771 LOADING: 'loading', 12772 LOADED: 'loaded', 12773 ERROR: 'error' 12774}; 12775/** 12776 * Check if click or keydown event was dispatched 12777 * with a special key or via mouse wheel. 12778 * 12779 * @param {MouseEvent | KeyboardEvent} e 12780 * @returns {boolean} 12781 */ 12782 12783function specialKeyUsed(e) { 12784 return 'button' in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey; 12785} 12786/** 12787 * Parse `gallery` or `children` options. 12788 * 12789 * @param {import('../photoswipe.js').ElementProvider} [option] 12790 * @param {string} [legacySelector] 12791 * @param {HTMLElement | Document} [parent] 12792 * @returns HTMLElement[] 12793 */ 12794 12795function getElementsFromOption(option, legacySelector, parent = document) { 12796 /** @type {HTMLElement[]} */ 12797 let elements = []; 12798 12799 if (option instanceof Element) { 12800 elements = [option]; 12801 } else if (option instanceof NodeList || Array.isArray(option)) { 12802 elements = Array.from(option); 12803 } else { 12804 const selector = typeof option === 'string' ? option : legacySelector; 12805 12806 if (selector) { 12807 elements = Array.from(parent.querySelectorAll(selector)); 12808 } 12809 } 12810 12811 return elements; 12812} 12813/** 12814 * Check if variable is PhotoSwipe class 12815 * 12816 * @param {any} fn 12817 * @returns {boolean} 12818 */ 12819 12820function isPswpClass(fn) { 12821 return typeof fn === 'function' && fn.prototype && fn.prototype.goTo; 12822} 12823/** 12824 * Check if browser is Safari 12825 * 12826 * @returns {boolean} 12827 */ 12828 12829function isSafari() { 12830 return !!(navigator.vendor && navigator.vendor.match(/apple/i)); 12831} 12832 12833/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */ 12834 12835/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 12836 12837/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 12838 12839/** @typedef {import('../photoswipe.js').DataSource} DataSource */ 12840 12841/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */ 12842 12843/** @typedef {import('../slide/content.js').default} ContentDefault */ 12844 12845/** @typedef {import('../slide/slide.js').default} Slide */ 12846 12847/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 12848 12849/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */ 12850 12851/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */ 12852 12853/** 12854 * Allow adding an arbitrary props to the Content 12855 * https://photoswipe.com/custom-content/#using-webp-image-format 12856 * @typedef {ContentDefault & Record<string, any>} Content 12857 */ 12858 12859/** @typedef {{ x?: number; y?: number }} Point */ 12860 12861/** 12862 * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/ 12863 * 12864 * 12865 * https://photoswipe.com/adding-ui-elements/ 12866 * 12867 * @prop {undefined} uiRegister 12868 * @prop {{ data: UIElementData }} uiElementCreate 12869 * 12870 * 12871 * https://photoswipe.com/events/#initialization-events 12872 * 12873 * @prop {undefined} beforeOpen 12874 * @prop {undefined} firstUpdate 12875 * @prop {undefined} initialLayout 12876 * @prop {undefined} change 12877 * @prop {undefined} afterInit 12878 * @prop {undefined} bindEvents 12879 * 12880 * 12881 * https://photoswipe.com/events/#opening-or-closing-transition-events 12882 * 12883 * @prop {undefined} openingAnimationStart 12884 * @prop {undefined} openingAnimationEnd 12885 * @prop {undefined} closingAnimationStart 12886 * @prop {undefined} closingAnimationEnd 12887 * 12888 * 12889 * https://photoswipe.com/events/#closing-events 12890 * 12891 * @prop {undefined} close 12892 * @prop {undefined} destroy 12893 * 12894 * 12895 * https://photoswipe.com/events/#pointer-and-gesture-events 12896 * 12897 * @prop {{ originalEvent: PointerEvent }} pointerDown 12898 * @prop {{ originalEvent: PointerEvent }} pointerMove 12899 * @prop {{ originalEvent: PointerEvent }} pointerUp 12900 * @prop {{ bgOpacity: number }} pinchClose can be default prevented 12901 * @prop {{ panY: number }} verticalDrag can be default prevented 12902 * 12903 * 12904 * https://photoswipe.com/events/#slide-content-events 12905 * 12906 * @prop {{ content: Content }} contentInit 12907 * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented 12908 * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented 12909 * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete 12910 * @prop {{ content: Content; slide: Slide }} loadError 12911 * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented 12912 * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange 12913 * @prop {{ content: Content }} contentLazyLoad can be default prevented 12914 * @prop {{ content: Content }} contentAppend can be default prevented 12915 * @prop {{ content: Content }} contentActivate can be default prevented 12916 * @prop {{ content: Content }} contentDeactivate can be default prevented 12917 * @prop {{ content: Content }} contentRemove can be default prevented 12918 * @prop {{ content: Content }} contentDestroy can be default prevented 12919 * 12920 * 12921 * undocumented 12922 * 12923 * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented 12924 * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented 12925 * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented 12926 * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented 12927 * 12928 * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented 12929 * @prop {{ x: number; dragging: boolean }} moveMainScroll 12930 * @prop {{ slide: Slide }} firstZoomPan 12931 * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData 12932 * @prop {undefined} beforeResize 12933 * @prop {undefined} resize 12934 * @prop {undefined} viewportSize 12935 * @prop {undefined} updateScrollOffset 12936 * @prop {{ slide: Slide }} slideInit 12937 * @prop {{ slide: Slide }} afterSetContent 12938 * @prop {{ slide: Slide }} slideLoad 12939 * @prop {{ slide: Slide }} appendHeavy can be default prevented 12940 * @prop {{ slide: Slide }} appendHeavyContent 12941 * @prop {{ slide: Slide }} slideActivate 12942 * @prop {{ slide: Slide }} slideDeactivate 12943 * @prop {{ slide: Slide }} slideDestroy 12944 * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo 12945 * @prop {{ slide: Slide }} zoomPanUpdate 12946 * @prop {{ slide: Slide }} initialZoomPan 12947 * @prop {{ slide: Slide }} calcSlideSize 12948 * @prop {undefined} resolutionChanged 12949 * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented 12950 * @prop {{ content: Content }} contentAppendImage can be default prevented 12951 * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented 12952 * @prop {undefined} lazyLoad 12953 * @prop {{ slide: Slide }} calcBounds 12954 * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate 12955 * 12956 * 12957 * legacy 12958 * 12959 * @prop {undefined} init 12960 * @prop {undefined} initialZoomIn 12961 * @prop {undefined} initialZoomOut 12962 * @prop {undefined} initialZoomInEnd 12963 * @prop {undefined} initialZoomOutEnd 12964 * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems 12965 * @prop {{ itemData: SlideData; index: number }} itemData 12966 * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds 12967 */ 12968 12969/** 12970 * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/ 12971 * 12972 * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems 12973 * Modify the total amount of slides. Example on Data sources page. 12974 * https://photoswipe.com/filters/#numitems 12975 * 12976 * @prop {(itemData: SlideData, index: number) => SlideData} itemData 12977 * Modify slide item data. Example on Data sources page. 12978 * https://photoswipe.com/filters/#itemdata 12979 * 12980 * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData 12981 * Modify item data when it's parsed from DOM element. Example on Data sources page. 12982 * https://photoswipe.com/filters/#domitemdata 12983 * 12984 * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex 12985 * Modify clicked gallery item index. 12986 * https://photoswipe.com/filters/#clickedindex 12987 * 12988 * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc 12989 * Modify placeholder image source. 12990 * https://photoswipe.com/filters/#placeholdersrc 12991 * 12992 * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading 12993 * Modify if the content is currently loading. 12994 * https://photoswipe.com/filters/#iscontentloading 12995 * 12996 * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable 12997 * Modify if the content can be zoomed. 12998 * https://photoswipe.com/filters/#iscontentzoomable 12999 * 13000 * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder 13001 * Modify if the placeholder should be used for the content. 13002 * https://photoswipe.com/filters/#usecontentplaceholder 13003 * 13004 * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder 13005 * Modify if the placeholder should be kept after the content is loaded. 13006 * https://photoswipe.com/filters/#iskeepingplaceholder 13007 * 13008 * 13009 * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement 13010 * Modify an element when the content has error state (for example, if image cannot be loaded). 13011 * https://photoswipe.com/filters/#contenterrorelement 13012 * 13013 * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement 13014 * Modify a UI element that's being created. 13015 * https://photoswipe.com/filters/#uielement 13016 * 13017 * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl 13018 * Modify the thumbnail element from which opening zoom animation starts or ends. 13019 * https://photoswipe.com/filters/#thumbel 13020 * 13021 * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds 13022 * Modify the thumbnail bounds from which opening zoom animation starts or ends. 13023 * https://photoswipe.com/filters/#thumbbounds 13024 * 13025 * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth 13026 * 13027 * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent 13028 * 13029 */ 13030 13031/** 13032 * @template {keyof PhotoSwipeFiltersMap} T 13033 * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter 13034 */ 13035 13036/** 13037 * @template {keyof PhotoSwipeEventsMap} T 13038 * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent 13039 */ 13040 13041/** 13042 * @template {keyof PhotoSwipeEventsMap} T 13043 * @typedef {(event: AugmentedEvent<T>) => void} EventCallback 13044 */ 13045 13046/** 13047 * Base PhotoSwipe event object 13048 * 13049 * @template {keyof PhotoSwipeEventsMap} T 13050 */ 13051class PhotoSwipeEvent { 13052 /** 13053 * @param {T} type 13054 * @param {PhotoSwipeEventsMap[T]} [details] 13055 */ 13056 constructor(type, details) { 13057 this.type = type; 13058 this.defaultPrevented = false; 13059 13060 if (details) { 13061 Object.assign(this, details); 13062 } 13063 } 13064 13065 preventDefault() { 13066 this.defaultPrevented = true; 13067 } 13068 13069} 13070/** 13071 * PhotoSwipe base class that can listen and dispatch for events. 13072 * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js 13073 */ 13074 13075 13076class Eventable { 13077 constructor() { 13078 /** 13079 * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }} 13080 */ 13081 this._listeners = {}; 13082 /** 13083 * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }} 13084 */ 13085 13086 this._filters = {}; 13087 /** @type {PhotoSwipe | undefined} */ 13088 13089 this.pswp = undefined; 13090 /** @type {PhotoSwipeOptions | undefined} */ 13091 13092 this.options = undefined; 13093 } 13094 /** 13095 * @template {keyof PhotoSwipeFiltersMap} T 13096 * @param {T} name 13097 * @param {PhotoSwipeFiltersMap[T]} fn 13098 * @param {number} priority 13099 */ 13100 13101 13102 addFilter(name, fn, priority = 100) { 13103 var _this$_filters$name, _this$_filters$name2, _this$pswp; 13104 13105 if (!this._filters[name]) { 13106 this._filters[name] = []; 13107 } 13108 13109 (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({ 13110 fn, 13111 priority 13112 }); 13113 (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority); 13114 (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority); 13115 } 13116 /** 13117 * @template {keyof PhotoSwipeFiltersMap} T 13118 * @param {T} name 13119 * @param {PhotoSwipeFiltersMap[T]} fn 13120 */ 13121 13122 13123 removeFilter(name, fn) { 13124 if (this._filters[name]) { 13125 // @ts-expect-error 13126 this._filters[name] = this._filters[name].filter(filter => filter.fn !== fn); 13127 } 13128 13129 if (this.pswp) { 13130 this.pswp.removeFilter(name, fn); 13131 } 13132 } 13133 /** 13134 * @template {keyof PhotoSwipeFiltersMap} T 13135 * @param {T} name 13136 * @param {Parameters<PhotoSwipeFiltersMap[T]>} args 13137 * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]} 13138 */ 13139 13140 13141 applyFilters(name, ...args) { 13142 var _this$_filters$name3; 13143 13144 (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach(filter => { 13145 // @ts-expect-error 13146 args[0] = filter.fn.apply(this, args); 13147 }); 13148 return args[0]; 13149 } 13150 /** 13151 * @template {keyof PhotoSwipeEventsMap} T 13152 * @param {T} name 13153 * @param {EventCallback<T>} fn 13154 */ 13155 13156 13157 on(name, fn) { 13158 var _this$_listeners$name, _this$pswp2; 13159 13160 if (!this._listeners[name]) { 13161 this._listeners[name] = []; 13162 } 13163 13164 (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn); // When binding events to lightbox, 13165 // also bind events to PhotoSwipe Core, 13166 // if it's open. 13167 13168 (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn); 13169 } 13170 /** 13171 * @template {keyof PhotoSwipeEventsMap} T 13172 * @param {T} name 13173 * @param {EventCallback<T>} fn 13174 */ 13175 13176 13177 off(name, fn) { 13178 var _this$pswp3; 13179 13180 if (this._listeners[name]) { 13181 // @ts-expect-error 13182 this._listeners[name] = this._listeners[name].filter(listener => fn !== listener); 13183 } 13184 13185 (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn); 13186 } 13187 /** 13188 * @template {keyof PhotoSwipeEventsMap} T 13189 * @param {T} name 13190 * @param {PhotoSwipeEventsMap[T]} [details] 13191 * @returns {AugmentedEvent<T>} 13192 */ 13193 13194 13195 dispatch(name, details) { 13196 var _this$_listeners$name2; 13197 13198 if (this.pswp) { 13199 return this.pswp.dispatch(name, details); 13200 } 13201 13202 const event = 13203 /** @type {AugmentedEvent<T>} */ 13204 new PhotoSwipeEvent(name, details); 13205 (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach(listener => { 13206 listener.call(this, event); 13207 }); 13208 return event; 13209 } 13210 13211} 13212 13213class Placeholder { 13214 /** 13215 * @param {string | false} imageSrc 13216 * @param {HTMLElement} container 13217 */ 13218 constructor(imageSrc, container) { 13219 // Create placeholder 13220 // (stretched thumbnail or simple div behind the main image) 13221 13222 /** @type {HTMLImageElement | HTMLDivElement | null} */ 13223 this.element = createElement('pswp__img pswp__img--placeholder', imageSrc ? 'img' : 'div', container); 13224 13225 if (imageSrc) { 13226 const imgEl = 13227 /** @type {HTMLImageElement} */ 13228 this.element; 13229 imgEl.decoding = 'async'; 13230 imgEl.alt = ''; 13231 imgEl.src = imageSrc; 13232 imgEl.setAttribute('role', 'presentation'); 13233 } 13234 13235 this.element.setAttribute('aria-hidden', 'true'); 13236 } 13237 /** 13238 * @param {number} width 13239 * @param {number} height 13240 */ 13241 13242 13243 setDisplayedSize(width, height) { 13244 if (!this.element) { 13245 return; 13246 } 13247 13248 if (this.element.tagName === 'IMG') { 13249 // Use transform scale() to modify img placeholder size 13250 // (instead of changing width/height directly). 13251 // This helps with performance, specifically in iOS15 Safari. 13252 setWidthHeight(this.element, 250, 'auto'); 13253 this.element.style.transformOrigin = '0 0'; 13254 this.element.style.transform = toTransformString(0, 0, width / 250); 13255 } else { 13256 setWidthHeight(this.element, width, height); 13257 } 13258 } 13259 13260 destroy() { 13261 var _this$element; 13262 13263 if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) { 13264 this.element.remove(); 13265 } 13266 13267 this.element = null; 13268 } 13269 13270} 13271 13272/** @typedef {import('./slide.js').default} Slide */ 13273 13274/** @typedef {import('./slide.js').SlideData} SlideData */ 13275 13276/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ 13277 13278/** @typedef {import('../util/util.js').LoadState} LoadState */ 13279 13280class Content { 13281 /** 13282 * @param {SlideData} itemData Slide data 13283 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance 13284 * @param {number} index 13285 */ 13286 constructor(itemData, instance, index) { 13287 this.instance = instance; 13288 this.data = itemData; 13289 this.index = index; 13290 /** @type {HTMLImageElement | HTMLDivElement | undefined} */ 13291 13292 this.element = undefined; 13293 /** @type {Placeholder | undefined} */ 13294 13295 this.placeholder = undefined; 13296 /** @type {Slide | undefined} */ 13297 13298 this.slide = undefined; 13299 this.displayedImageWidth = 0; 13300 this.displayedImageHeight = 0; 13301 this.width = Number(this.data.w) || Number(this.data.width) || 0; 13302 this.height = Number(this.data.h) || Number(this.data.height) || 0; 13303 this.isAttached = false; 13304 this.hasSlide = false; 13305 this.isDecoding = false; 13306 /** @type {LoadState} */ 13307 13308 this.state = LOAD_STATE.IDLE; 13309 13310 if (this.data.type) { 13311 this.type = this.data.type; 13312 } else if (this.data.src) { 13313 this.type = 'image'; 13314 } else { 13315 this.type = 'html'; 13316 } 13317 13318 this.instance.dispatch('contentInit', { 13319 content: this 13320 }); 13321 } 13322 13323 removePlaceholder() { 13324 if (this.placeholder && !this.keepPlaceholder()) { 13325 // With delay, as image might be loaded, but not rendered 13326 setTimeout(() => { 13327 if (this.placeholder) { 13328 this.placeholder.destroy(); 13329 this.placeholder = undefined; 13330 } 13331 }, 1000); 13332 } 13333 } 13334 /** 13335 * Preload content 13336 * 13337 * @param {boolean} isLazy 13338 * @param {boolean} [reload] 13339 */ 13340 13341 13342 load(isLazy, reload) { 13343 if (this.slide && this.usePlaceholder()) { 13344 if (!this.placeholder) { 13345 const placeholderSrc = this.instance.applyFilters('placeholderSrc', // use image-based placeholder only for the first slide, 13346 // as rendering (even small stretched thumbnail) is an expensive operation 13347 this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false, this); 13348 this.placeholder = new Placeholder(placeholderSrc, this.slide.container); 13349 } else { 13350 const placeholderEl = this.placeholder.element; // Add placeholder to DOM if it was already created 13351 13352 if (placeholderEl && !placeholderEl.parentElement) { 13353 this.slide.container.prepend(placeholderEl); 13354 } 13355 } 13356 } 13357 13358 if (this.element && !reload) { 13359 return; 13360 } 13361 13362 if (this.instance.dispatch('contentLoad', { 13363 content: this, 13364 isLazy 13365 }).defaultPrevented) { 13366 return; 13367 } 13368 13369 if (this.isImageContent()) { 13370 this.element = createElement('pswp__img', 'img'); // Start loading only after width is defined, as sizes might depend on it. 13371 // Due to Safari feature, we must define sizes before srcset. 13372 13373 if (this.displayedImageWidth) { 13374 this.loadImage(isLazy); 13375 } 13376 } else { 13377 this.element = createElement('pswp__content', 'div'); 13378 this.element.innerHTML = this.data.html || ''; 13379 } 13380 13381 if (reload && this.slide) { 13382 this.slide.updateContentSize(true); 13383 } 13384 } 13385 /** 13386 * Preload image 13387 * 13388 * @param {boolean} isLazy 13389 */ 13390 13391 13392 loadImage(isLazy) { 13393 var _this$data$src, _this$data$alt; 13394 13395 if (!this.isImageContent() || !this.element || this.instance.dispatch('contentLoadImage', { 13396 content: this, 13397 isLazy 13398 }).defaultPrevented) { 13399 return; 13400 } 13401 13402 const imageElement = 13403 /** @type HTMLImageElement */ 13404 this.element; 13405 this.updateSrcsetSizes(); 13406 13407 if (this.data.srcset) { 13408 imageElement.srcset = this.data.srcset; 13409 } 13410 13411 imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : ''; 13412 imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : ''; 13413 this.state = LOAD_STATE.LOADING; 13414 13415 if (imageElement.complete) { 13416 this.onLoaded(); 13417 } else { 13418 imageElement.onload = () => { 13419 this.onLoaded(); 13420 }; 13421 13422 imageElement.onerror = () => { 13423 this.onError(); 13424 }; 13425 } 13426 } 13427 /** 13428 * Assign slide to content 13429 * 13430 * @param {Slide} slide 13431 */ 13432 13433 13434 setSlide(slide) { 13435 this.slide = slide; 13436 this.hasSlide = true; 13437 this.instance = slide.pswp; // todo: do we need to unset slide? 13438 } 13439 /** 13440 * Content load success handler 13441 */ 13442 13443 13444 onLoaded() { 13445 this.state = LOAD_STATE.LOADED; 13446 13447 if (this.slide && this.element) { 13448 this.instance.dispatch('loadComplete', { 13449 slide: this.slide, 13450 content: this 13451 }); // if content is reloaded 13452 13453 if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) { 13454 this.append(); 13455 this.slide.updateContentSize(true); 13456 } 13457 13458 if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) { 13459 this.removePlaceholder(); 13460 } 13461 } 13462 } 13463 /** 13464 * Content load error handler 13465 */ 13466 13467 13468 onError() { 13469 this.state = LOAD_STATE.ERROR; 13470 13471 if (this.slide) { 13472 this.displayError(); 13473 this.instance.dispatch('loadComplete', { 13474 slide: this.slide, 13475 isError: true, 13476 content: this 13477 }); 13478 this.instance.dispatch('loadError', { 13479 slide: this.slide, 13480 content: this 13481 }); 13482 } 13483 } 13484 /** 13485 * @returns {Boolean} If the content is currently loading 13486 */ 13487 13488 13489 isLoading() { 13490 return this.instance.applyFilters('isContentLoading', this.state === LOAD_STATE.LOADING, this); 13491 } 13492 /** 13493 * @returns {Boolean} If the content is in error state 13494 */ 13495 13496 13497 isError() { 13498 return this.state === LOAD_STATE.ERROR; 13499 } 13500 /** 13501 * @returns {boolean} If the content is image 13502 */ 13503 13504 13505 isImageContent() { 13506 return this.type === 'image'; 13507 } 13508 /** 13509 * Update content size 13510 * 13511 * @param {Number} width 13512 * @param {Number} height 13513 */ 13514 13515 13516 setDisplayedSize(width, height) { 13517 if (!this.element) { 13518 return; 13519 } 13520 13521 if (this.placeholder) { 13522 this.placeholder.setDisplayedSize(width, height); 13523 } 13524 13525 if (this.instance.dispatch('contentResize', { 13526 content: this, 13527 width, 13528 height 13529 }).defaultPrevented) { 13530 return; 13531 } 13532 13533 setWidthHeight(this.element, width, height); 13534 13535 if (this.isImageContent() && !this.isError()) { 13536 const isInitialSizeUpdate = !this.displayedImageWidth && width; 13537 this.displayedImageWidth = width; 13538 this.displayedImageHeight = height; 13539 13540 if (isInitialSizeUpdate) { 13541 this.loadImage(false); 13542 } else { 13543 this.updateSrcsetSizes(); 13544 } 13545 13546 if (this.slide) { 13547 this.instance.dispatch('imageSizeChange', { 13548 slide: this.slide, 13549 width, 13550 height, 13551 content: this 13552 }); 13553 } 13554 } 13555 } 13556 /** 13557 * @returns {boolean} If the content can be zoomed 13558 */ 13559 13560 13561 isZoomable() { 13562 return this.instance.applyFilters('isContentZoomable', this.isImageContent() && this.state !== LOAD_STATE.ERROR, this); 13563 } 13564 /** 13565 * Update image srcset sizes attribute based on width and height 13566 */ 13567 13568 13569 updateSrcsetSizes() { 13570 // Handle srcset sizes attribute. 13571 // 13572 // Never lower quality, if it was increased previously. 13573 // Chrome does this automatically, Firefox and Safari do not, 13574 // so we store largest used size in dataset. 13575 if (!this.isImageContent() || !this.element || !this.data.srcset) { 13576 return; 13577 } 13578 13579 const image = 13580 /** @type HTMLImageElement */ 13581 this.element; 13582 const sizesWidth = this.instance.applyFilters('srcsetSizesWidth', this.displayedImageWidth, this); 13583 13584 if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) { 13585 image.sizes = sizesWidth + 'px'; 13586 image.dataset.largestUsedSize = String(sizesWidth); 13587 } 13588 } 13589 /** 13590 * @returns {boolean} If content should use a placeholder (from msrc by default) 13591 */ 13592 13593 13594 usePlaceholder() { 13595 return this.instance.applyFilters('useContentPlaceholder', this.isImageContent(), this); 13596 } 13597 /** 13598 * Preload content with lazy-loading param 13599 */ 13600 13601 13602 lazyLoad() { 13603 if (this.instance.dispatch('contentLazyLoad', { 13604 content: this 13605 }).defaultPrevented) { 13606 return; 13607 } 13608 13609 this.load(true); 13610 } 13611 /** 13612 * @returns {boolean} If placeholder should be kept after content is loaded 13613 */ 13614 13615 13616 keepPlaceholder() { 13617 return this.instance.applyFilters('isKeepingPlaceholder', this.isLoading(), this); 13618 } 13619 /** 13620 * Destroy the content 13621 */ 13622 13623 13624 destroy() { 13625 this.hasSlide = false; 13626 this.slide = undefined; 13627 13628 if (this.instance.dispatch('contentDestroy', { 13629 content: this 13630 }).defaultPrevented) { 13631 return; 13632 } 13633 13634 this.remove(); 13635 13636 if (this.placeholder) { 13637 this.placeholder.destroy(); 13638 this.placeholder = undefined; 13639 } 13640 13641 if (this.isImageContent() && this.element) { 13642 this.element.onload = null; 13643 this.element.onerror = null; 13644 this.element = undefined; 13645 } 13646 } 13647 /** 13648 * Display error message 13649 */ 13650 13651 13652 displayError() { 13653 if (this.slide) { 13654 var _this$instance$option, _this$instance$option2; 13655 13656 let errorMsgEl = createElement('pswp__error-msg', 'div'); 13657 errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : ''; 13658 errorMsgEl = 13659 /** @type {HTMLDivElement} */ 13660 this.instance.applyFilters('contentErrorElement', errorMsgEl, this); 13661 this.element = createElement('pswp__content pswp__error-msg-container', 'div'); 13662 this.element.appendChild(errorMsgEl); 13663 this.slide.container.innerText = ''; 13664 this.slide.container.appendChild(this.element); 13665 this.slide.updateContentSize(true); 13666 this.removePlaceholder(); 13667 } 13668 } 13669 /** 13670 * Append the content 13671 */ 13672 13673 13674 append() { 13675 if (this.isAttached || !this.element) { 13676 return; 13677 } 13678 13679 this.isAttached = true; 13680 13681 if (this.state === LOAD_STATE.ERROR) { 13682 this.displayError(); 13683 return; 13684 } 13685 13686 if (this.instance.dispatch('contentAppend', { 13687 content: this 13688 }).defaultPrevented) { 13689 return; 13690 } 13691 13692 const supportsDecode = ('decode' in this.element); 13693 13694 if (this.isImageContent()) { 13695 // Use decode() on nearby slides 13696 // 13697 // Nearby slide images are in DOM and not hidden via display:none. 13698 // However, they are placed offscreen (to the left and right side). 13699 // 13700 // Some browsers do not composite the image until it's actually visible, 13701 // using decode() helps. 13702 // 13703 // You might ask "why dont you just decode() and then append all images", 13704 // that's because I want to show image before it's fully loaded, 13705 // as browser can render parts of image while it is loading. 13706 // We do not do this in Safari due to partial loading bug. 13707 if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) { 13708 this.isDecoding = true; // purposefully using finally instead of then, 13709 // as if srcset sizes changes dynamically - it may cause decode error 13710 13711 /** @type {HTMLImageElement} */ 13712 13713 this.element.decode().catch(() => {}).finally(() => { 13714 this.isDecoding = false; 13715 this.appendImage(); 13716 }); 13717 } else { 13718 this.appendImage(); 13719 } 13720 } else if (this.slide && !this.element.parentNode) { 13721 this.slide.container.appendChild(this.element); 13722 } 13723 } 13724 /** 13725 * Activate the slide, 13726 * active slide is generally the current one, 13727 * meaning the user can see it. 13728 */ 13729 13730 13731 activate() { 13732 if (this.instance.dispatch('contentActivate', { 13733 content: this 13734 }).defaultPrevented || !this.slide) { 13735 return; 13736 } 13737 13738 if (this.isImageContent() && this.isDecoding && !isSafari()) { 13739 // add image to slide when it becomes active, 13740 // even if it's not finished decoding 13741 this.appendImage(); 13742 } else if (this.isError()) { 13743 this.load(false, true); // try to reload 13744 } 13745 13746 if (this.slide.holderElement) { 13747 this.slide.holderElement.setAttribute('aria-hidden', 'false'); 13748 } 13749 } 13750 /** 13751 * Deactivate the content 13752 */ 13753 13754 13755 deactivate() { 13756 this.instance.dispatch('contentDeactivate', { 13757 content: this 13758 }); 13759 13760 if (this.slide && this.slide.holderElement) { 13761 this.slide.holderElement.setAttribute('aria-hidden', 'true'); 13762 } 13763 } 13764 /** 13765 * Remove the content from DOM 13766 */ 13767 13768 13769 remove() { 13770 this.isAttached = false; 13771 13772 if (this.instance.dispatch('contentRemove', { 13773 content: this 13774 }).defaultPrevented) { 13775 return; 13776 } 13777 13778 if (this.element && this.element.parentNode) { 13779 this.element.remove(); 13780 } 13781 13782 if (this.placeholder && this.placeholder.element) { 13783 this.placeholder.element.remove(); 13784 } 13785 } 13786 /** 13787 * Append the image content to slide container 13788 */ 13789 13790 13791 appendImage() { 13792 if (!this.isAttached) { 13793 return; 13794 } 13795 13796 if (this.instance.dispatch('contentAppendImage', { 13797 content: this 13798 }).defaultPrevented) { 13799 return; 13800 } // ensure that element exists and is not already appended 13801 13802 13803 if (this.slide && this.element && !this.element.parentNode) { 13804 this.slide.container.appendChild(this.element); 13805 } 13806 13807 if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) { 13808 this.removePlaceholder(); 13809 } 13810 } 13811 13812} 13813 13814/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 13815 13816/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ 13817 13818/** @typedef {import('../photoswipe.js').Point} Point */ 13819 13820/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 13821 13822/** 13823 * @param {PhotoSwipeOptions} options 13824 * @param {PhotoSwipeBase} pswp 13825 * @returns {Point} 13826 */ 13827function getViewportSize(options, pswp) { 13828 if (options.getViewportSizeFn) { 13829 const newViewportSize = options.getViewportSizeFn(options, pswp); 13830 13831 if (newViewportSize) { 13832 return newViewportSize; 13833 } 13834 } 13835 13836 return { 13837 x: document.documentElement.clientWidth, 13838 // TODO: height on mobile is very incosistent due to toolbar 13839 // find a way to improve this 13840 // 13841 // document.documentElement.clientHeight - doesn't seem to work well 13842 y: window.innerHeight 13843 }; 13844} 13845/** 13846 * Parses padding option. 13847 * Supported formats: 13848 * 13849 * // Object 13850 * padding: { 13851 * top: 0, 13852 * bottom: 0, 13853 * left: 0, 13854 * right: 0 13855 * } 13856 * 13857 * // A function that returns the object 13858 * paddingFn: (viewportSize, itemData, index) => { 13859 * return { 13860 * top: 0, 13861 * bottom: 0, 13862 * left: 0, 13863 * right: 0 13864 * }; 13865 * } 13866 * 13867 * // Legacy variant 13868 * paddingLeft: 0, 13869 * paddingRight: 0, 13870 * paddingTop: 0, 13871 * paddingBottom: 0, 13872 * 13873 * @param {'left' | 'top' | 'bottom' | 'right'} prop 13874 * @param {PhotoSwipeOptions} options PhotoSwipe options 13875 * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 } 13876 * @param {SlideData} itemData Data about the slide 13877 * @param {number} index Slide index 13878 * @returns {number} 13879 */ 13880 13881function parsePaddingOption(prop, options, viewportSize, itemData, index) { 13882 let paddingValue = 0; 13883 13884 if (options.paddingFn) { 13885 paddingValue = options.paddingFn(viewportSize, itemData, index)[prop]; 13886 } else if (options.padding) { 13887 paddingValue = options.padding[prop]; 13888 } else { 13889 const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1); // @ts-expect-error 13890 13891 if (options[legacyPropName]) { 13892 // @ts-expect-error 13893 paddingValue = options[legacyPropName]; 13894 } 13895 } 13896 13897 return Number(paddingValue) || 0; 13898} 13899/** 13900 * @param {PhotoSwipeOptions} options 13901 * @param {Point} viewportSize 13902 * @param {SlideData} itemData 13903 * @param {number} index 13904 * @returns {Point} 13905 */ 13906 13907function getPanAreaSize(options, viewportSize, itemData, index) { 13908 return { 13909 x: viewportSize.x - parsePaddingOption('left', options, viewportSize, itemData, index) - parsePaddingOption('right', options, viewportSize, itemData, index), 13910 y: viewportSize.y - parsePaddingOption('top', options, viewportSize, itemData, index) - parsePaddingOption('bottom', options, viewportSize, itemData, index) 13911 }; 13912} 13913 13914const MAX_IMAGE_WIDTH = 4000; 13915/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 13916 13917/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 13918 13919/** @typedef {import('../photoswipe.js').Point} Point */ 13920 13921/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 13922 13923/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */ 13924 13925/** 13926 * Calculates zoom levels for specific slide. 13927 * Depends on viewport size and image size. 13928 */ 13929 13930class ZoomLevel { 13931 /** 13932 * @param {PhotoSwipeOptions} options PhotoSwipe options 13933 * @param {SlideData} itemData Slide data 13934 * @param {number} index Slide index 13935 * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet 13936 */ 13937 constructor(options, itemData, index, pswp) { 13938 this.pswp = pswp; 13939 this.options = options; 13940 this.itemData = itemData; 13941 this.index = index; 13942 /** @type { Point | null } */ 13943 13944 this.panAreaSize = null; 13945 /** @type { Point | null } */ 13946 13947 this.elementSize = null; 13948 this.fit = 1; 13949 this.fill = 1; 13950 this.vFill = 1; 13951 this.initial = 1; 13952 this.secondary = 1; 13953 this.max = 1; 13954 this.min = 1; 13955 } 13956 /** 13957 * Calculate initial, secondary and maximum zoom level for the specified slide. 13958 * 13959 * It should be called when either image or viewport size changes. 13960 * 13961 * @param {number} maxWidth 13962 * @param {number} maxHeight 13963 * @param {Point} panAreaSize 13964 */ 13965 13966 13967 update(maxWidth, maxHeight, panAreaSize) { 13968 /** @type {Point} */ 13969 const elementSize = { 13970 x: maxWidth, 13971 y: maxHeight 13972 }; 13973 this.elementSize = elementSize; 13974 this.panAreaSize = panAreaSize; 13975 const hRatio = panAreaSize.x / elementSize.x; 13976 const vRatio = panAreaSize.y / elementSize.y; 13977 this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio); 13978 this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio); // zoom.vFill defines zoom level of the image 13979 // when it has 100% of viewport vertical space (height) 13980 13981 this.vFill = Math.min(1, vRatio); 13982 this.initial = this._getInitial(); 13983 this.secondary = this._getSecondary(); 13984 this.max = Math.max(this.initial, this.secondary, this._getMax()); 13985 this.min = Math.min(this.fit, this.initial, this.secondary); 13986 13987 if (this.pswp) { 13988 this.pswp.dispatch('zoomLevelsUpdate', { 13989 zoomLevels: this, 13990 slideData: this.itemData 13991 }); 13992 } 13993 } 13994 /** 13995 * Parses user-defined zoom option. 13996 * 13997 * @private 13998 * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max) 13999 * @returns { number | undefined } 14000 */ 14001 14002 14003 _parseZoomLevelOption(optionPrefix) { 14004 const optionName = 14005 /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */ 14006 optionPrefix + 'ZoomLevel'; 14007 const optionValue = this.options[optionName]; 14008 14009 if (!optionValue) { 14010 return; 14011 } 14012 14013 if (typeof optionValue === 'function') { 14014 return optionValue(this); 14015 } 14016 14017 if (optionValue === 'fill') { 14018 return this.fill; 14019 } 14020 14021 if (optionValue === 'fit') { 14022 return this.fit; 14023 } 14024 14025 return Number(optionValue); 14026 } 14027 /** 14028 * Get zoom level to which image will be zoomed after double-tap gesture, 14029 * or when user clicks on zoom icon, 14030 * or mouse-click on image itself. 14031 * If you return 1 image will be zoomed to its original size. 14032 * 14033 * @private 14034 * @return {number} 14035 */ 14036 14037 14038 _getSecondary() { 14039 let currZoomLevel = this._parseZoomLevelOption('secondary'); 14040 14041 if (currZoomLevel) { 14042 return currZoomLevel; 14043 } // 3x of "fit" state, but not larger than original 14044 14045 14046 currZoomLevel = Math.min(1, this.fit * 3); 14047 14048 if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) { 14049 currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x; 14050 } 14051 14052 return currZoomLevel; 14053 } 14054 /** 14055 * Get initial image zoom level. 14056 * 14057 * @private 14058 * @return {number} 14059 */ 14060 14061 14062 _getInitial() { 14063 return this._parseZoomLevelOption('initial') || this.fit; 14064 } 14065 /** 14066 * Maximum zoom level when user zooms 14067 * via zoom/pinch gesture, 14068 * via cmd/ctrl-wheel or via trackpad. 14069 * 14070 * @private 14071 * @return {number} 14072 */ 14073 14074 14075 _getMax() { 14076 // max zoom level is x4 from "fit state", 14077 // used for zoom gesture and ctrl/trackpad zoom 14078 return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4); 14079 } 14080 14081} 14082 14083/** 14084 * Lazy-load an image 14085 * This function is used both by Lightbox and PhotoSwipe core, 14086 * thus it can be called before dialog is opened. 14087 * 14088 * @param {SlideData} itemData Data about the slide 14089 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance 14090 * @param {number} index 14091 * @returns {Content} Image that is being decoded or false. 14092 */ 14093 14094function lazyLoadData(itemData, instance, index) { 14095 const content = instance.createContentFromData(itemData, index); 14096 /** @type {ZoomLevel | undefined} */ 14097 14098 let zoomLevel; 14099 const { 14100 options 14101 } = instance; // We need to know dimensions of the image to preload it, 14102 // as it might use srcset, and we need to define sizes 14103 14104 if (options) { 14105 zoomLevel = new ZoomLevel(options, itemData, -1); 14106 let viewportSize; 14107 14108 if (instance.pswp) { 14109 viewportSize = instance.pswp.viewportSize; 14110 } else { 14111 viewportSize = getViewportSize(options, instance); 14112 } 14113 14114 const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index); 14115 zoomLevel.update(content.width, content.height, panAreaSize); 14116 } 14117 14118 content.lazyLoad(); 14119 14120 if (zoomLevel) { 14121 content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial)); 14122 } 14123 14124 return content; 14125} 14126/** 14127 * Lazy-loads specific slide. 14128 * This function is used both by Lightbox and PhotoSwipe core, 14129 * thus it can be called before dialog is opened. 14130 * 14131 * By default, it loads image based on viewport size and initial zoom level. 14132 * 14133 * @param {number} index Slide index 14134 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance 14135 * @returns {Content | undefined} 14136 */ 14137 14138function lazyLoadSlide(index, instance) { 14139 const itemData = instance.getItemData(index); 14140 14141 if (instance.dispatch('lazyLoadSlide', { 14142 index, 14143 itemData 14144 }).defaultPrevented) { 14145 return; 14146 } 14147 14148 return lazyLoadData(itemData, instance, index); 14149} 14150 14151/** @typedef {import("../photoswipe.js").default} PhotoSwipe */ 14152 14153/** @typedef {import("../slide/slide.js").SlideData} SlideData */ 14154 14155/** 14156 * PhotoSwipe base class that can retrieve data about every slide. 14157 * Shared by PhotoSwipe Core and PhotoSwipe Lightbox 14158 */ 14159 14160class PhotoSwipeBase extends Eventable { 14161 /** 14162 * Get total number of slides 14163 * 14164 * @returns {number} 14165 */ 14166 getNumItems() { 14167 var _this$options; 14168 14169 let numItems = 0; 14170 const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource; 14171 14172 if (dataSource && 'length' in dataSource) { 14173 // may be an array or just object with length property 14174 numItems = dataSource.length; 14175 } else if (dataSource && 'gallery' in dataSource) { 14176 // query DOM elements 14177 if (!dataSource.items) { 14178 dataSource.items = this._getGalleryDOMElements(dataSource.gallery); 14179 } 14180 14181 if (dataSource.items) { 14182 numItems = dataSource.items.length; 14183 } 14184 } // legacy event, before filters were introduced 14185 14186 14187 const event = this.dispatch('numItems', { 14188 dataSource, 14189 numItems 14190 }); 14191 return this.applyFilters('numItems', event.numItems, dataSource); 14192 } 14193 /** 14194 * @param {SlideData} slideData 14195 * @param {number} index 14196 * @returns {Content} 14197 */ 14198 14199 14200 createContentFromData(slideData, index) { 14201 return new Content(slideData, this, index); 14202 } 14203 /** 14204 * Get item data by index. 14205 * 14206 * "item data" should contain normalized information that PhotoSwipe needs to generate a slide. 14207 * For example, it may contain properties like 14208 * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image. 14209 * 14210 * @param {number} index 14211 * @returns {SlideData} 14212 */ 14213 14214 14215 getItemData(index) { 14216 var _this$options2; 14217 14218 const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource; 14219 /** @type {SlideData | HTMLElement} */ 14220 14221 let dataSourceItem = {}; 14222 14223 if (Array.isArray(dataSource)) { 14224 // Datasource is an array of elements 14225 dataSourceItem = dataSource[index]; 14226 } else if (dataSource && 'gallery' in dataSource) { 14227 // dataSource has gallery property, 14228 // thus it was created by Lightbox, based on 14229 // gallery and children options 14230 // query DOM elements 14231 if (!dataSource.items) { 14232 dataSource.items = this._getGalleryDOMElements(dataSource.gallery); 14233 } 14234 14235 dataSourceItem = dataSource.items[index]; 14236 } 14237 14238 let itemData = dataSourceItem; 14239 14240 if (itemData instanceof Element) { 14241 itemData = this._domElementToItemData(itemData); 14242 } // Dispatching the itemData event, 14243 // it's a legacy verion before filters were introduced 14244 14245 14246 const event = this.dispatch('itemData', { 14247 itemData: itemData || {}, 14248 index 14249 }); 14250 return this.applyFilters('itemData', event.itemData, index); 14251 } 14252 /** 14253 * Get array of gallery DOM elements, 14254 * based on childSelector and gallery element. 14255 * 14256 * @param {HTMLElement} galleryElement 14257 * @returns {HTMLElement[]} 14258 */ 14259 14260 14261 _getGalleryDOMElements(galleryElement) { 14262 var _this$options3, _this$options4; 14263 14264 if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) { 14265 return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || []; 14266 } 14267 14268 return [galleryElement]; 14269 } 14270 /** 14271 * Converts DOM element to item data object. 14272 * 14273 * @param {HTMLElement} element DOM element 14274 * @returns {SlideData} 14275 */ 14276 14277 14278 _domElementToItemData(element) { 14279 /** @type {SlideData} */ 14280 const itemData = { 14281 element 14282 }; 14283 const linkEl = 14284 /** @type {HTMLAnchorElement} */ 14285 element.tagName === 'A' ? element : element.querySelector('a'); 14286 14287 if (linkEl) { 14288 // src comes from data-pswp-src attribute, 14289 // if it's empty link href is used 14290 itemData.src = linkEl.dataset.pswpSrc || linkEl.href; 14291 14292 if (linkEl.dataset.pswpSrcset) { 14293 itemData.srcset = linkEl.dataset.pswpSrcset; 14294 } 14295 14296 itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0; 14297 itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0; // support legacy w & h properties 14298 14299 itemData.w = itemData.width; 14300 itemData.h = itemData.height; 14301 14302 if (linkEl.dataset.pswpType) { 14303 itemData.type = linkEl.dataset.pswpType; 14304 } 14305 14306 const thumbnailEl = element.querySelector('img'); 14307 14308 if (thumbnailEl) { 14309 var _thumbnailEl$getAttri; 14310 14311 // msrc is URL to placeholder image that's displayed before large image is loaded 14312 // by default it's displayed only for the first slide 14313 itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src; 14314 itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute('alt')) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : ''; 14315 } 14316 14317 if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) { 14318 itemData.thumbCropped = true; 14319 } 14320 } 14321 14322 return this.applyFilters('domItemData', itemData, element, linkEl); 14323 } 14324 /** 14325 * Lazy-load by slide data 14326 * 14327 * @param {SlideData} itemData Data about the slide 14328 * @param {number} index 14329 * @returns {Content} Image that is being decoded or false. 14330 */ 14331 14332 14333 lazyLoadData(itemData, index) { 14334 return lazyLoadData(itemData, this, index); 14335 } 14336 14337} 14338 14339/** 14340 * @template T 14341 * @typedef {import('../types.js').Type<T>} Type<T> 14342 */ 14343 14344/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 14345 14346/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 14347 14348/** @typedef {import('../photoswipe.js').DataSource} DataSource */ 14349 14350/** @typedef {import('../photoswipe.js').Point} Point */ 14351 14352/** @typedef {import('../slide/content.js').default} Content */ 14353 14354/** @typedef {import('../core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */ 14355 14356/** @typedef {import('../core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */ 14357 14358/** 14359 * @template {keyof PhotoSwipeEventsMap} T 14360 * @typedef {import('../core/eventable.js').EventCallback<T>} EventCallback<T> 14361 */ 14362 14363/** 14364 * PhotoSwipe Lightbox 14365 * 14366 * - If user has unsupported browser it falls back to default browser action (just opens URL) 14367 * - Binds click event to links that should open PhotoSwipe 14368 * - parses DOM strcture for PhotoSwipe (retrieves large image URLs and sizes) 14369 * - Initializes PhotoSwipe 14370 * 14371 * 14372 * Loader options use the same object as PhotoSwipe, and supports such options: 14373 * 14374 * gallery - Element | Element[] | NodeList | string selector for the gallery element 14375 * children - Element | Element[] | NodeList | string selector for the gallery children 14376 * 14377 */ 14378 14379class PhotoSwipeLightbox extends PhotoSwipeBase { 14380 /** 14381 * @param {PhotoSwipeOptions} [options] 14382 */ 14383 constructor(options) { 14384 super(); 14385 /** @type {PhotoSwipeOptions} */ 14386 14387 this.options = options || {}; 14388 this._uid = 0; 14389 this.shouldOpen = false; 14390 /** 14391 * @private 14392 * @type {Content | undefined} 14393 */ 14394 14395 this._preloadedContent = undefined; 14396 this.onThumbnailsClick = this.onThumbnailsClick.bind(this); 14397 } 14398 /** 14399 * Initialize lightbox, should be called only once. 14400 * It's not included in the main constructor, so you may bind events before it. 14401 */ 14402 14403 14404 init() { 14405 // Bind click events to each gallery 14406 getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => { 14407 galleryElement.addEventListener('click', this.onThumbnailsClick, false); 14408 }); 14409 } 14410 /** 14411 * @param {MouseEvent} e 14412 */ 14413 14414 14415 onThumbnailsClick(e) { 14416 // Exit and allow default browser action if: 14417 if (specialKeyUsed(e) // ... if clicked with a special key (ctrl/cmd...) 14418 || window.pswp) { 14419 // ... if PhotoSwipe is already open 14420 return; 14421 } // If both clientX and clientY are 0 or not defined, 14422 // the event is likely triggered by keyboard, 14423 // so we do not pass the initialPoint 14424 // 14425 // Note that some screen readers emulate the mouse position, 14426 // so it's not the ideal way to detect them. 14427 // 14428 14429 /** @type {Point | null} */ 14430 14431 14432 let initialPoint = { 14433 x: e.clientX, 14434 y: e.clientY 14435 }; 14436 14437 if (!initialPoint.x && !initialPoint.y) { 14438 initialPoint = null; 14439 } 14440 14441 let clickedIndex = this.getClickedIndex(e); 14442 clickedIndex = this.applyFilters('clickedIndex', clickedIndex, e, this); 14443 /** @type {DataSource} */ 14444 14445 const dataSource = { 14446 gallery: 14447 /** @type {HTMLElement} */ 14448 e.currentTarget 14449 }; 14450 14451 if (clickedIndex >= 0) { 14452 e.preventDefault(); 14453 this.loadAndOpen(clickedIndex, dataSource, initialPoint); 14454 } 14455 } 14456 /** 14457 * Get index of gallery item that was clicked. 14458 * 14459 * @param {MouseEvent} e click event 14460 * @returns {number} 14461 */ 14462 14463 14464 getClickedIndex(e) { 14465 // legacy option 14466 if (this.options.getClickedIndexFn) { 14467 return this.options.getClickedIndexFn.call(this, e); 14468 } 14469 14470 const clickedTarget = 14471 /** @type {HTMLElement} */ 14472 e.target; 14473 const childElements = getElementsFromOption(this.options.children, this.options.childSelector, 14474 /** @type {HTMLElement} */ 14475 e.currentTarget); 14476 const clickedChildIndex = childElements.findIndex(child => child === clickedTarget || child.contains(clickedTarget)); 14477 14478 if (clickedChildIndex !== -1) { 14479 return clickedChildIndex; 14480 } else if (this.options.children || this.options.childSelector) { 14481 // click wasn't on a child element 14482 return -1; 14483 } // There is only one item (which is the gallery) 14484 14485 14486 return 0; 14487 } 14488 /** 14489 * Load and open PhotoSwipe 14490 * 14491 * @param {number} index 14492 * @param {DataSource} [dataSource] 14493 * @param {Point | null} [initialPoint] 14494 * @returns {boolean} 14495 */ 14496 14497 14498 loadAndOpen(index, dataSource, initialPoint) { 14499 // Check if the gallery is already open 14500 if (window.pswp || !this.options) { 14501 return false; 14502 } // Use the first gallery element if dataSource is not provided 14503 14504 14505 if (!dataSource && this.options.gallery && this.options.children) { 14506 const galleryElements = getElementsFromOption(this.options.gallery); 14507 14508 if (galleryElements[0]) { 14509 dataSource = { 14510 gallery: galleryElements[0] 14511 }; 14512 } 14513 } // set initial index 14514 14515 14516 this.options.index = index; // define options for PhotoSwipe constructor 14517 14518 this.options.initialPointerPos = initialPoint; 14519 this.shouldOpen = true; 14520 this.preload(index, dataSource); 14521 return true; 14522 } 14523 /** 14524 * Load the main module and the slide content by index 14525 * 14526 * @param {number} index 14527 * @param {DataSource} [dataSource] 14528 */ 14529 14530 14531 preload(index, dataSource) { 14532 const { 14533 options 14534 } = this; 14535 14536 if (dataSource) { 14537 options.dataSource = dataSource; 14538 } // Add the main module 14539 14540 /** @type {Promise<Type<PhotoSwipe>>[]} */ 14541 14542 14543 const promiseArray = []; 14544 const pswpModuleType = typeof options.pswpModule; 14545 14546 if (isPswpClass(options.pswpModule)) { 14547 promiseArray.push(Promise.resolve( 14548 /** @type {Type<PhotoSwipe>} */ 14549 options.pswpModule)); 14550 } else if (pswpModuleType === 'string') { 14551 throw new Error('pswpModule as string is no longer supported'); 14552 } else if (pswpModuleType === 'function') { 14553 promiseArray.push( 14554 /** @type {() => Promise<Type<PhotoSwipe>>} */ 14555 options.pswpModule()); 14556 } else { 14557 throw new Error('pswpModule is not valid'); 14558 } // Add custom-defined promise, if any 14559 14560 14561 if (typeof options.openPromise === 'function') { 14562 // allow developers to perform some task before opening 14563 promiseArray.push(options.openPromise()); 14564 } 14565 14566 if (options.preloadFirstSlide !== false && index >= 0) { 14567 this._preloadedContent = lazyLoadSlide(index, this); 14568 } // Wait till all promises resolve and open PhotoSwipe 14569 14570 14571 const uid = ++this._uid; 14572 Promise.all(promiseArray).then(iterableModules => { 14573 if (this.shouldOpen) { 14574 const mainModule = iterableModules[0]; 14575 14576 this._openPhotoswipe(mainModule, uid); 14577 } 14578 }); 14579 } 14580 /** 14581 * @private 14582 * @param {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} module 14583 * @param {number} uid 14584 */ 14585 14586 14587 _openPhotoswipe(module, uid) { 14588 // Cancel opening if UID doesn't match the current one 14589 // (if user clicked on another gallery item before current was loaded). 14590 // 14591 // Or if shouldOpen flag is set to false 14592 // (developer may modify it via public API) 14593 if (uid !== this._uid && this.shouldOpen) { 14594 return; 14595 } 14596 14597 this.shouldOpen = false; // PhotoSwipe is already open 14598 14599 if (window.pswp) { 14600 return; 14601 } 14602 /** 14603 * Pass data to PhotoSwipe and open init 14604 * 14605 * @type {PhotoSwipe} 14606 */ 14607 14608 14609 const pswp = typeof module === 'object' ? new module.default(this.options) // eslint-disable-line 14610 : new module(this.options); // eslint-disable-line 14611 14612 this.pswp = pswp; 14613 window.pswp = pswp; // map listeners from Lightbox to PhotoSwipe Core 14614 14615 /** @type {(keyof PhotoSwipeEventsMap)[]} */ 14616 14617 Object.keys(this._listeners).forEach(name => { 14618 var _this$_listeners$name; 14619 14620 (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.forEach(fn => { 14621 pswp.on(name, 14622 /** @type {EventCallback<typeof name>} */ 14623 fn); 14624 }); 14625 }); // same with filters 14626 14627 /** @type {(keyof PhotoSwipeFiltersMap)[]} */ 14628 14629 Object.keys(this._filters).forEach(name => { 14630 var _this$_filters$name; 14631 14632 (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.forEach(filter => { 14633 pswp.addFilter(name, filter.fn, filter.priority); 14634 }); 14635 }); 14636 14637 if (this._preloadedContent) { 14638 pswp.contentLoader.addToCache(this._preloadedContent); 14639 this._preloadedContent = undefined; 14640 } 14641 14642 pswp.on('destroy', () => { 14643 // clean up public variables 14644 this.pswp = undefined; 14645 delete window.pswp; 14646 }); 14647 pswp.init(); 14648 } 14649 /** 14650 * Unbinds all events, closes PhotoSwipe if it's open. 14651 */ 14652 14653 14654 destroy() { 14655 var _this$pswp; 14656 14657 (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.destroy(); 14658 this.shouldOpen = false; 14659 this._listeners = {}; 14660 getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => { 14661 galleryElement.removeEventListener('click', this.onThumbnailsClick, false); 14662 }); 14663 } 14664 14665} 14666
14667function initEditorImageLightbox() { 14668 document.querySelectorAll(".editor-image--lightbox").forEach((frame) => { 14669 if (frame.dataset.lightboxInit) return; 14670 frame.dataset.lightboxInit = "true"; 14671 const img = frame.querySelector("img"); 14672 if (!img) return; 14673 frame.style.cursor = "zoom-in"; 14674 frame.addEventListener("click", () => { 14675 const w = img.naturalWidth || 2e3; 14676 const h = img.naturalHeight || 1400; 14677 const lightbox = new PhotoSwipeLightbox({ 14678 dataSource: [{ src: img.src, width: w, height: h, alt: img.alt }], 14679 pswpModule: () => Promise.resolve(PhotoSwipe), 14680 showHideAnimationType: "fade", 14681 bgOpacity: 0.9 14682 }); 14683 lightbox.on("openingAnimationStart", () => { 14684 document.documentElement.style.overflow = "hidden"; 14685 document.body.style.overflow = "hidden"; 14686 }); 14687 lightbox.on("closingAnimationEnd", () => { 14688 document.documentElement.style.overflow = ""; 14689 document.body.style.overflow = ""; 14690 }); 14691 lightbox.init(); 14692 lightbox.loadAndOpen(0); 14693 }); 14694 }); 14695} 14696initEditorImageLightbox(); 14697window.addEventListener("load", initEditorImageLightbox); 14698 14699})(); 14700 14701;(() => { 14702// ----- script9.js ----- 14703document.querySelector(".sus-article-navi")?.addEventListener("click", (e) => { 14704 const link = e.target.closest('a[href^="#"]'); 14705 if (!link) return; 14706 const hash = link.getAttribute("href"); 14707 const target = document.querySelector(hash); 14708 if (!target) return; 14709 e.preventDefault(); 14710 target.scrollIntoView({ behavior: "smooth" }); 14711 history.pushState(null, "", hash); 14712 target.focus({ preventScroll: true }); 14713}); 14714 14715})(); 14716
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.