PageSourceSearch

https://www.jr-cross.co.jp/common/js/top_youtube_play.js

js jr-cross.co.jp collected 2026-10-01 12:17:43 UTC 9,212 bytes, 346 lines download raw bytes

1(function () {
2  const root = document.querySelector(".bl_topMovieWrapper");
3  if (!root) return;
4
5  const container = root.querySelector(".swiper-container");
6  const wrapper = root.querySelector(".swiper-wrapper");
7  const slideSelector = ".bl_topMovieSwiperListItem";
8  const playerSelector = ".js_topMoviePlayer";
9
10  const iosTwoTapMode = false; // trueにするとiOS/iPadOSは2タップ音声付き前提
11
12  function extractYoutubeId(src) {
13    if (!src) return "";
14
15    try {
16      const url = new URL(src, location.href);
17
18      if (url.hostname.includes("youtube.com") || url.hostname.includes("youtube-nocookie.com")) {
19        const pathParts = url.pathname.split("/").filter(Boolean);
20
21        if (pathParts[0] === "embed" && pathParts[1]) {
22          return pathParts[1];
23        }
24
25        return url.searchParams.get("v") || "";
26      }
27
28      if (url.hostname === "youtu.be") {
29        return url.pathname.split("/").filter(Boolean)[0] || "";
30      }
31    } catch (e) {
32      return "";
33    }
34
35    return "";
36  }
37
38  function escapeAttr(value) {
39    return String(value)
40      .replace(/&/g, "&")
41      .replace(/"/g, """)
42      .replace(/</g, "&lt;")
43      .replace(/>/g, "&gt;");
44  }
45
46  function getSwiperInstance() {
47    return (container && container.swiper) || window.swiperTopMovie || null;
48  }
49
50  function getYoutubeId(slide) {
51    if (!slide) return "";
52
53    if (slide.dataset.youtubeId) {
54      return slide.dataset.youtubeId;
55    }
56
57    const iframe = slide.querySelector('iframe[src*="youtube"]');
58    const id = iframe ? extractYoutubeId(iframe.getAttribute("src")) : "";
59
60    if (id) {
61      slide.dataset.youtubeId = id;
62    }
63
64    return id;
65  }
66
67  function getPlayer(slide) {
68    let player = slide.querySelector(playerSelector);
69
70    if (!player) {
71      player = slide.querySelector(".bl_topMovieSwiperListItemInner");
72
73      if (player) {
74        player.classList.add("js_topMoviePlayer");
75      }
76    }
77
78    return player;
79  }
80
81  function isIOSDevice() {
82    const ua = window.navigator.userAgent;
83
84    const isIOS = /iPhone|iPad|iPod/i.test(ua);
85
86    const isIPadOS =
87      /Macintosh/i.test(ua) &&
88      window.navigator.maxTouchPoints &&
89      window.navigator.maxTouchPoints > 1;
90
91    return isIOS || isIPadOS;
92  }
93
94  function createThumbnailHtml(title, thumbSrc) {
95    return (
96      '<button type="button" class="bl_topMovieThumb js_topMovieThumb" aria-label="' + escapeAttr(title) + 'を再生">' +
97        '<img class="bl_topMovieThumb__img" src="' + escapeAttr(thumbSrc) + '" alt="" loading="lazy">' +
98        '<span class="bl_topMovieThumb__play" aria-hidden="true">' +
99          '<img src="/common/image/top/icon_play_youtube.svg" alt="" loading="lazy">' +
100        "</span>" +
101      "</button>"
102    );
103  }
104
105  function createMissingThumbnailHtml() {
106    return (
107      '<div class="bl_topMovieThumb bl_topMovieThumb--missing" aria-label="サムネイル画像未設定">' +
108        '<span class="bl_topMovieThumb__missingText">サムネイル画像未設定</span>' +
109      "</div>"
110    );
111  }
112
113  function renderThumbnail(slide) {
114    const id = getYoutubeId(slide);
115    const player = getPlayer(slide);
116
117    if (!id || !player) return;
118
119    const title = slide.dataset.youtubeTitle || "YouTube動画";
120    const fixedThumbSrc = slide.dataset.thumbSrc || "";
121
122    slide.classList.remove("is-youtube-playing");
123
124    // data-thumb-src 必須。未指定の場合も自動取得しない
125    if (!fixedThumbSrc) {
126      player.innerHTML = createMissingThumbnailHtml();
127
128      if (window.console && typeof window.console.warn === "function") {
129        console.warn("[top_youtube] data-thumb-src が未設定です。サムネイル画像を指定してください。", slide);
130      }
131
132      return;
133    }
134
135    player.innerHTML = createThumbnailHtml(title, fixedThumbSrc);
136  }
137
138  function renderIframe(slide) {
139    const id = getYoutubeId(slide);
140    const player = getPlayer(slide);
141
142    if (!id || !player) return;
143
144    const params = new URLSearchParams({
145      rel: "0",
146      playsinline: "1",
147      controls: "1"
148    });
149
150    if (iosTwoTapMode && isIOSDevice()) {
151      // iOS / iPadOS:autoplayもmuteも付けず、2タップで音声付き再生
152    } else {
153      // PC / Android / iOS通常時:ミュート自動再生
154      params.set("autoplay", "1");
155      params.set("mute", "1");
156    }
157
158    const src =
159      "https://www.youtube.com/embed/" +
160      encodeURIComponent(id) +
161      "?" +
162      params.toString();
163
164    player.innerHTML =
165      '<iframe width="560" height="315" src="' + src + '"' +
166      ' title="YouTube video player"' +
167      ' frameborder="0"' +
168      ' allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen"' +
169      ' allowfullscreen' +
170      ' webkitallowfullscreen' +
171      ' mozallowfullscreen' +
172      '></iframe>';
173
174    slide.classList.add("is-youtube-playing");
175  }
176
177  function resetAllMovies() {
178    const slides = root.querySelectorAll(slideSelector);
179
180    slides.forEach(function (slide) {
181      if (
182        slide.classList.contains("is-youtube-playing") ||
183        slide.querySelector('iframe[src*="youtube"]')
184      ) {
185        renderThumbnail(slide);
186      }
187    });
188  }
189
190  function isEventFromPlayingSlide(event) {
191    if (!event || !event.target || typeof event.target.closest !== "function") {
192      return false;
193    }
194
195    return !!event.target.closest(slideSelector + ".is-youtube-playing");
196  }
197
198  function resetAllMoviesByUserMove(event) {
199    // YouTube iframe表示中のスライド内操作は、拡大ボタンなどのプレイヤー操作の可能性があるため戻さない
200    if (isEventFromPlayingSlide(event)) {
201      return;
202    }
203
204    resetAllMovies();
205  }
206
207  function isCenteredActiveSlide(slide) {
208    if (!slide || !container) return false;
209
210    if (!slide.classList.contains("swiper-slide-active")) {
211      return false;
212    }
213
214    const slideRect = slide.getBoundingClientRect();
215    const containerRect = container.getBoundingClientRect();
216
217    const slideCenter = slideRect.left + slideRect.width / 2;
218    const containerCenter = containerRect.left + containerRect.width / 2;
219
220    return Math.abs(slideCenter - containerCenter) <= 8;
221  }
222
223  function moveBySlidePosition(slide) {
224    const swiper = getSwiperInstance();
225
226    if (
227      !swiper ||
228      typeof swiper.slidePrev !== "function" ||
229      typeof swiper.slideNext !== "function" ||
230      !container
231    ) {
232      return;
233    }
234
235    const slideRect = slide.getBoundingClientRect();
236    const containerRect = container.getBoundingClientRect();
237
238    const slideCenter = slideRect.left + slideRect.width / 2;
239    const containerCenter = containerRect.left + containerRect.width / 2;
240
241    resetAllMovies();
242
243    if (slideCenter < containerCenter) {
244      swiper.slidePrev();
245    } else {
246      swiper.slideNext();
247    }
248  }
249
250  function initSlides() {
251    const slides = root.querySelectorAll(slideSelector);
252
253    slides.forEach(function (slide) {
254      const id = getYoutubeId(slide);
255      if (!id) return;
256
257      // 初期HTMLにiframeが残っていた場合もサムネイルへ置換
258      if (slide.querySelector('iframe[src*="youtube"]')) {
259        renderThumbnail(slide);
260        return;
261      }
262
263      if (
264        !slide.querySelector(".js_topMovieThumb") &&
265        !slide.querySelector(".bl_topMovieThumb--missing")
266      ) {
267        renderThumbnail(slide);
268      }
269    });
270  }
271
272  /**
273   * サムネイルクリック制御
274   *
275   * 中央スライド:再生
276   * 左見切れ:前へ
277   * 右見切れ:次へ
278   */
279  root.addEventListener(
280    "click",
281    function (event) {
282      const thumb = event.target.closest(".js_topMovieThumb");
283      if (!thumb) return;
284
285      const slide = thumb.closest(slideSelector);
286      if (!slide || !container) return;
287
288      event.preventDefault();
289      event.stopPropagation();
290
291      if (typeof event.stopImmediatePropagation === "function") {
292        event.stopImmediatePropagation();
293      }
294
295      if (isCenteredActiveSlide(slide)) {
296        resetAllMovies();
297        renderIframe(slide);
298        return;
299      }
300
301      moveBySlidePosition(slide);
302    },
303    true
304  );
305
306  /**
307   * prev / next / pagination を押したらiframeをサムネイルへ戻す
308   */
309  root.addEventListener(
310    "click",
311    function (event) {
312      if (
313        event.target.closest(".swiper-button-prev") ||
314        event.target.closest(".swiper-button-next") ||
315        event.target.closest(".bl_topMovieSwiperNavigationWrapper")
316      ) {
317        resetAllMovies();
318      }
319    },
320    true
321  );
322
323  /**
324   * スワイプ・ドラッグ開始時もiframeをサムネイルへ戻す
325   * ただし、再生中スライド内のYouTube操作は除外
326   */
327  if (container) {
328    container.addEventListener("touchstart", resetAllMoviesByUserMove, { passive: true });
329    container.addEventListener("mousedown", resetAllMoviesByUserMove);
330  }
331
332  /**
333   * loop複製などでスライドDOMが変わった場合にも対応
334   */
335  if (wrapper) {
336    const observer = new MutationObserver(function () {
337      initSlides();
338    });
339
340    observer.observe(wrapper, {
341      childList: true
342    });
343  }
344
345  initSlides();
346})();

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.