PageSourceSearch

https://ses-media.jp/js/carousel.js

js ses-media.jp collected 2026-10-04 19:28:43 UTC 5,916 bytes, 192 lines download raw bytes

1document.addEventListener("DOMContentLoaded", () => {
2  // ページ内のすべてのカルーセルラッパーを取得
3  const carousels = document.querySelectorAll(".carousel-wrapper");
4
5  // 各カルーセルに対して個別に処理を実行
6  carousels.forEach((wrapper) => {
7    const container = wrapper.querySelector(".carousel-container");
8    const track = wrapper.querySelector(".carousel-track");
9    const items = wrapper.querySelectorAll(".carousel-item");
10    const prevBtn = wrapper.querySelector(".carousel-nav.prev");
11    const nextBtn = wrapper.querySelector(".carousel-nav.next");
12
13    // 必要な要素が揃っていない場合はスキップ
14    if (!container || !track || !items.length || !prevBtn || !nextBtn) return;
15
16    let currentIndex = 0;
17    let itemsPerView = 3; // デフォルトはPC表示
18
19    // レスポンシブ対応:画面幅に応じて表示数を変更
20    function updateItemsPerView() {
21      const width = window.innerWidth;
22      if (width <= 768) {
23        itemsPerView = 1; // SP: 1列
24      } else if (width <= 1024) {
25        itemsPerView = 2; // タブレット: 2列
26      } else {
27        itemsPerView = 3; // PC: 3列
28      }
29    }
30
31    // カルーセルの位置を更新
32    function updateCarousel() {
33      const itemWidth = items[0].offsetWidth;
34      // CSSのgapの値を取得(計算用)
35      const style = window.getComputedStyle(track);
36      const gap = parseFloat(style.gap) || 0; // gapが未設定の場合は0
37      
38      const offset = -(currentIndex * (itemWidth + gap));
39      track.style.transform = `translateX(${offset}px)`;
40
41      // ボタンの状態を更新
42      updateButtons();
43    }
44
45    // ボタンの有効/無効状態を更新
46    function updateButtons() {
47      const maxIndex = Math.max(0, items.length - itemsPerView);
48
49      // 最初の位置
50      if (currentIndex <= 0) {
51        prevBtn.disabled = true;
52        prevBtn.classList.add('disabled'); // CSSスタイリング用クラス
53      } else {
54        prevBtn.disabled = false;
55        prevBtn.classList.remove('disabled');
56      }
57
58      // 最後の位置
59      if (currentIndex >= maxIndex) {
60        nextBtn.disabled = true;
61        nextBtn.classList.add('disabled');
62      } else {
63        nextBtn.disabled = false;
64        nextBtn.classList.remove('disabled');
65      }
66    }
67
68    // 前へ
69    function movePrev() {
70      if (currentIndex > 0) {
71        currentIndex--;
72        updateCarousel();
73      }
74    }
75
76    // 次へ
77    function moveNext() {
78      const maxIndex = Math.max(0, items.length - itemsPerView);
79      if (currentIndex < maxIndex) {
80        currentIndex++;
81        updateCarousel();
82      }
83    }
84
85    // イベントリスナー(ボタン)
86    prevBtn.addEventListener("click", movePrev);
87    nextBtn.addEventListener("click", moveNext);
88
89    // リサイズ対応
90    let resizeTimer;
91    window.addEventListener("resize", () => {
92      clearTimeout(resizeTimer);
93      resizeTimer = setTimeout(() => {
94        const oldItemsPerView = itemsPerView;
95        updateItemsPerView();
96
97        // 表示数が変わった場合、位置を調整
98        if (oldItemsPerView !== itemsPerView) {
99          const maxIndex = Math.max(0, items.length - itemsPerView);
100          if (currentIndex > maxIndex) {
101            currentIndex = maxIndex;
102          }
103          updateCarousel();
104        }
105      }, 250);
106    });
107
108    // --- タッチスワイプ対応(スマホ用) ---
109    let touchStartX = 0;
110    let touchEndX = 0;
111    let touchStartY = 0;
112    let touchEndY = 0;
113    const minSwipeDistance = 50;
114
115    container.addEventListener("touchstart", (e) => {
116      touchStartX = e.changedTouches[0].screenX;
117      touchStartY = e.changedTouches[0].screenY;
118    }, { passive: true });
119
120    container.addEventListener("touchend", (e) => {
121      touchEndX = e.changedTouches[0].screenX;
122      touchEndY = e.changedTouches[0].screenY;
123      handleSwipe();
124    }, { passive: true });
125
126    // --- マウスドラッグ対応(PC用) ---
127    let isDragging = false;
128    let hasDragged = false;
129
130    container.addEventListener("mousedown", (e) => {
131      if (e.target.closest('.carousel-nav')) return;
132      isDragging = true;
133      hasDragged = false;
134      touchStartX = e.clientX;
135      touchStartY = e.clientY;
136      container.style.cursor = 'grabbing';
137      e.preventDefault();
138    });
139
140    container.addEventListener("mousemove", (e) => {
141      if (!isDragging) return;
142      const moveX = Math.abs(e.clientX - touchStartX);
143      const moveY = Math.abs(e.clientY - touchStartY);
144      if (moveX > 5 || moveY > 5) {
145        hasDragged = true;
146        e.preventDefault();
147      }
148    });
149
150    container.addEventListener("mouseup", (e) => {
151      if (!isDragging) return;
152      isDragging = false;
153      container.style.cursor = 'grab';
154      touchEndX = e.clientX;
155      touchEndY = e.clientY;
156      if (hasDragged) {
157        handleSwipe();
158        e.preventDefault();
159      }
160    });
161
162    container.addEventListener("mouseleave", () => {
163      if (isDragging) {
164        isDragging = false;
165        container.style.cursor = 'grab';
166      }
167    });
168
169    container.addEventListener("dragstart", (e) => {
170      e.preventDefault();
171    });
172
173    container.style.cursor = 'grab';
174
175    function handleSwipe() {
176      const swipeDistanceX = touchEndX - touchStartX;
177      const swipeDistanceY = touchEndY - touchStartY;
178
179      if (Math.abs(swipeDistanceY) > Math.abs(swipeDistanceX)) return;
180
181      if (swipeDistanceX < -minSwipeDistance) {
182        moveNext();
183      } else if (swipeDistanceX > minSwipeDistance) {
184        movePrev();
185      }
186    }
187
188    // 初期化実行
189    updateItemsPerView();
190    updateCarousel();
191  });
192});

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.