PageSourceSearch

https://kartie.jp/points/js/card-slider.js

js kartie.jp collected 2026-09-24 15:04:36 UTC 5,583 bytes, 173 lines download raw bytes

1/**
2 * Card Slider Component
3 * SP(768px以下)のみ動作
4 *
5 * 設置手順:
6 * 1. card-slider.css を読み込む
7 * 2. card-slider.js を読み込む
8 * 3. .about-cards 内のカード群が自動でスライダー化
9 */
10(function () {
11  'use strict';
12
13  var BREAKPOINT = 768;
14  var INITIAL_INDEX = 1; // ← 初期表示: 2番目(0始まり)
15  var container = document.querySelector('.about-cards');
16  if (!container) return;
17
18  var cards = Array.prototype.slice.call(container.querySelectorAll('.feature-card'));
19  if (cards.length === 0) return;
20
21  var currentIndex = INITIAL_INDEX;
22  var isSliderMode = false;
23
24  /* ========================================
25     DOM 構築(ナビ・ドット)
26     ======================================== */
27  var nav = document.createElement('div');
28  nav.className = 'about-cards-nav';
29
30  var btnPrev = document.createElement('button');
31  btnPrev.className = 'about-cards-nav__btn';
32  btnPrev.setAttribute('aria-label', '前へ');
33  btnPrev.innerHTML = '<svg viewBox="0 0 24 24"><polyline points="15 6 9 12 15 18"/></svg>';
34
35  var btnNext = document.createElement('button');
36  btnNext.className = 'about-cards-nav__btn';
37  btnNext.setAttribute('aria-label', '次へ');
38  btnNext.innerHTML = '<svg viewBox="0 0 24 24"><polyline points="9 6 15 12 9 18"/></svg>';
39
40  var dotsWrap = document.createElement('div');
41  dotsWrap.className = 'about-cards-dots';
42
43  var dots = [];
44  cards.forEach(function (_, i) {
45    var dot = document.createElement('button');
46    dot.className = 'about-cards-dots__dot';
47    dot.setAttribute('aria-label', 'スライド ' + (i + 1));
48    dotsWrap.appendChild(dot);
49    dots.push(dot);
50  });
51
52  nav.appendChild(btnPrev);
53  nav.appendChild(dotsWrap);
54  nav.appendChild(btnNext);
55
56  container.parentNode.insertBefore(nav, container.nextSibling);
57
58  /* ========================================
59     スライド位置の更新(px 計算)
60     ======================================== */
61  function updateSlider() {
62    var containerW = container.offsetWidth;
63
64    // カードの実際の幅(margin含む)を取得
65    var cardEl = cards[0];
66    var style = window.getComputedStyle(cardEl);
67    var cardW = cardEl.offsetWidth;
68    var marginL = parseFloat(style.marginLeft) || 0;
69    var marginR = parseFloat(style.marginRight) || 0;
70    var cardStep = cardW + marginL + marginR;
71
72    // 中央配置: (コンテナ幅 - カード幅) / 2 - 左margin
73    var centerOffset = (containerW - cardW) / 2 - marginL;
74
75    // アクティブカードを中央に配置
76    var translatePx = centerOffset - currentIndex * cardStep;
77
78    cards.forEach(function (card, i) {
79      var scale = (i === currentIndex) ? ' scale(1)' : ' scale(0.88)';
80      card.style.transform = 'translateX(' + translatePx + 'px)' + scale;
81      card.classList.toggle('is-active', i === currentIndex);
82    });
83
84    dots.forEach(function (dot, i) {
85      dot.classList.toggle('is-current', i === currentIndex);
86    });
87
88    // 端に来たらボタンを無効化
89    btnPrev.disabled = (currentIndex === 0);
90    btnNext.disabled = (currentIndex === cards.length - 1);
91  }
92
93  function goTo(index) {
94    if (index < 0) index = 0;
95    if (index >= cards.length) index = cards.length - 1;
96    currentIndex = index;
97    updateSlider();
98  }
99
100  /* ========================================
101     イベント
102     ======================================== */
103  btnPrev.addEventListener('click', function () { goTo(currentIndex - 1); });
104  btnNext.addEventListener('click', function () { goTo(currentIndex + 1); });
105
106  dots.forEach(function (dot, i) {
107    dot.addEventListener('click', function () { goTo(i); });
108  });
109
110  /* ========================================
111     タッチスワイプ
112     ======================================== */
113  var touchStartX = 0;
114  var touchStartY = 0;
115  var isSwiping = false;
116
117  container.addEventListener('touchstart', function (e) {
118    if (!isSliderMode) return;
119    touchStartX = e.touches[0].clientX;
120    touchStartY = e.touches[0].clientY;
121    isSwiping = false;
122  }, { passive: true });
123
124  container.addEventListener('touchmove', function (e) {
125    if (!isSliderMode) return;
126    var dx = e.touches[0].clientX - touchStartX;
127    var dy = e.touches[0].clientY - touchStartY;
128    if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10) {
129      isSwiping = true;
130    }
131  }, { passive: true });
132
133  container.addEventListener('touchend', function (e) {
134    if (!isSliderMode || !isSwiping) return;
135    var dx = e.changedTouches[0].clientX - touchStartX;
136    if (dx < -40) {
137      goTo(currentIndex + 1);
138    } else if (dx > 40) {
139      goTo(currentIndex - 1);
140    }
141  }, { passive: true });
142
143  /* ========================================
144     スライダーモード ON/OFF(リサイズ対応)
145     ======================================== */
146  function checkMode() {
147    var shouldBeSlider = window.innerWidth <= BREAKPOINT;
148
149    if (shouldBeSlider && !isSliderMode) {
150      isSliderMode = true;
151      currentIndex = INITIAL_INDEX;
152      updateSlider();
153    } else if (!shouldBeSlider && isSliderMode) {
154      isSliderMode = false;
155      cards.forEach(function (card) {
156        card.style.transform = '';
157        card.classList.remove('is-active');
158      });
159    } else if (shouldBeSlider && isSliderMode) {
160      // リサイズ時に再計算(画面幅変更で位置ズレ防止)
161      updateSlider();
162    }
163  }
164
165  var resizeTimer;
166  window.addEventListener('resize', function () {
167    clearTimeout(resizeTimer);
168    resizeTimer = setTimeout(checkMode, 150);
169  });
170
171  checkMode();
172
173})();

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.