PageSourceSearch

https://fortune-cookie.tokyo/assets/js/gacha-group-carousel.js?v=1.0.610

js fortune-cookie.tokyo collected 2026-10-07 17:58:18 UTC 5,214 bytes, 136 lines download raw bytes

1/**
2 * ガチャグループカルーセルの Swiper 初期化 + destroy 管理
3 *
4 * 元は resources/views/components/gacha-group-carousel.blade.php L53-131 の @once script。
5 * 差分リロード (fragment swap) で新規カルーセルの init + 旧カルーセルの destroy を行うため
6 * 外部 JS 化。Swiper インスタンス leak (spec-reviewer [重大] #2) の解消。
7 *
8 * 公開 API:
9 *   - window.GachaGroupCarousel.initAll(root?) — root スコープ内の未 init カルーセルを init
10 *   - window.GachaGroupCarousel.destroyRemoved(root?) — root スコープ内の disconnected な
11 *     Swiper インスタンスを destroy して Map から削除
12 *
13 * 冪等化:
14 *   - window.__GACHA_GROUP_CAROUSEL_LOADED__ ガードで module 二重ロード防止
15 *   - 各要素に data-swiperInitialized="1" マーカー + Map<Element, Swiper> でインスタンス管理
16 *
17 * イベント:
18 *   - top:gacha-list-will-swap — swap 前 hook: 消える予定の Swiper インスタンスを destroy
19 *   - top:gacha-list-updated — swap 後 hook: 新規カルーセルを init
20 *
21 * Spec: docs/features/top-main-gacha-partial-reload/spec.md §3.D-2
22 */
23(function () {
24    if (window.__GACHA_GROUP_CAROUSEL_LOADED__) return;
25    window.__GACHA_GROUP_CAROUSEL_LOADED__ = true;
26
27    // Swiper インスタンス管理: DOM element → Swiper instance
28    // swap 前に destroy(true, true) を呼ぶために instance を保持する
29    var instances = new Map();
30
31    function setupSwipeHint(el, swiper) {
32        if (swiper.slides.length < 2) {
33            return;
34        }
35        el.classList.add('swipe-hint-active');
36        var stopHint = function () {
37            el.classList.remove('swipe-hint-active');
38        };
39        swiper.once('touchStart', stopHint);
40        swiper.once('slideChange', stopHint);
41    }
42
43    function initOne(el) {
44        if (el.dataset.swiperInitialized === '1') {
45            return;
46        }
47        el.dataset.swiperInitialized = '1';
48
49        try {
50            var wrapper = el.closest('.gacha-group-carousel');
51            var params = {
52                centeredSlides: true,
53                slidesPerView: 1.05,
54                spaceBetween: 12,
55                loop: false
56            };
57
58            var paginationEl = wrapper ? wrapper.querySelector('.gacha-group-carousel-pagination') : null;
59            if (paginationEl) {
60                params.pagination = {
61                    el: paginationEl,
62                    clickable: true
63                };
64            }
65
66            var nextEl = el.querySelector('.swiper-button-next');
67            var prevEl = el.querySelector('.swiper-button-prev');
68            if (nextEl && prevEl) {
69                params.navigation = {
70                    nextEl: nextEl,
71                    prevEl: prevEl
72                };
73            }
74
75            var swiper = new Swiper(el, params);
76            instances.set(el, swiper);
77
78            setupSwipeHint(el, swiper);
79        } catch (e) {
80            // 1 件の init 失敗が他のカルーセルの init を巻き込まないようにする
81        }
82    }
83
84    function initAll(root) {
85        if (typeof Swiper === 'undefined') {
86            // Swiper JS が読み込めなかった場合は CSS フォールバック(横スクロール)に任せる
87            return;
88        }
89        var scope = root || document;
90        scope.querySelectorAll('.gacha-group-carousel-swiper').forEach(initOne);
91    }
92
93    function destroyOne(el) {
94        var swiper = instances.get(el);
95        if (swiper && typeof swiper.destroy === 'function') {
96            try {
97                swiper.destroy(true, true);
98            } catch (e) {
99                // destroy 失敗しても Map から削除は行う (leak 防止)
100            }
101        }
102        instances.delete(el);
103    }
104
105    function destroyRemoved(root) {
106        // root 配下 (== これから swap で消える container) 内の Swiper インスタンスを全て destroy
107        // container.innerHTML = '' の前に呼ぶことで DOM 参照が生きているうちに destroy できる
108        var scope = root || document;
109        scope.querySelectorAll('.gacha-group-carousel-swiper').forEach(destroyOne);
110    }
111
112    // 公開 API
113    window.GachaGroupCarousel = {
114        initAll: initAll,
115        destroyRemoved: destroyRemoved,
116    };
117
118    // 初期ロード時の自動 init (元の script が window load を待っていたので同タイミングで実行)
119    if (document.readyState === 'complete') {
120        initAll();
121    } else {
122        window.addEventListener('load', function () { initAll(); }, { once: true });
123    }
124
125    // swap 前 hook: 消える container 内の Swiper インスタンスを de
125stroy
126    document.addEventListener('top:gacha-list-will-swap', function (e) {
127        var root = (e && e.detail && e.detail.container) ? e.detail.container : null;
128        if (root) destroyRemoved(root);
129    });
130
131    // swap 後 hook: 新規カルーセルを init
132    document.addEventListener('top:gacha-list-updated', function (e) {
133        var root = (e && e.detail && e.detail.container) ? e.detail.container : document;
134        initAll(root);
135    });
136})();

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.