PageSourceSearch

https://www.synology.com/js/syno/home/carousel.js?v=8a6eec

js synology.com collected 2026-09-24 08:25:47 UTC 9,515 bytes, 254 lines download raw bytes

1function initHomeHeroCarousel() {
2  const heroCarouselProgressBar = document.querySelector('.home-hero__carousel-progress-bar');
3  const heroCarouselControls = document.querySelector('.home-hero__carousel-controls');
4  const firstSlide = document.querySelector('.home-hero__carousel-item:nth-child(1)');
5  const firstVideoItem = firstSlide?.querySelector('.home-hero__carousel-item--video');
6  const heroCarouselItemsVideo = document?.querySelectorAll('.home-hero__carousel-item--video');
7
8  let heroCarousel;
9  let currentSlideIndex;
10  let timeoutVideoCover = null;
11  let TIMEOUT_TIME = 1000;
12
13
14  // * carousel video 的基本設定
15  const setupCarouselVideoItems = () => {
16    if (heroCarouselItemsVideo.length > 0) {
17      let hasHiddenControls = false;
18
19      heroCarouselItemsVideo.forEach((videoItem) => {
20        const parent = videoItem.parentElement;
21        const sLink = parent?.querySelector('.home-hero__carousel-item--video-dialog');
22        const isVideoLoaded = videoItem.getAttribute('data-video-loaded');
23        const isVisible = videoItem.offsetParent !== null;
24
25        // + 如果影片尚未載入,暫停 carousel,並且不能 click control (僅針對可見的影片)
26        if (!isVideoLoaded && isVisible) {
27          heroCarousel.Components.Autoplay.pause();
28          heroCarouselControls.style.display = 'none';
29          hasHiddenControls = true;
30
31          // 第一次載入成功後
32          document.addEventListener(`s-vid-loaded:${videoItem.id}`, () => {
33            // 標記載入完成
34            videoItem.setAttribute('data-video-loaded', 'true');
35
36            // 如果影片是第一張 slide,就要讓立即播放
37            if (heroCarousel.index === 0) {
38              videoReplay(videoItem, true);
39            }
40
41            // 恢復 control 點擊
42            heroCarouselControls.style.display = 'flex';
43          });
44        }
45
46        // + 隱藏的影片也需要監聽載入事件 (切換螢幕寬度時可能變為可見)
47        if (!isVideoLoaded && !isVisible) {
48          document.addEventListener(`s-vid-loaded:${videoItem.id}`, () => {
49            videoItem.setAttribute('data-video-loaded', 'true');
50          });
51        }
52  
53        // + 監聽影片的 control 變化
54        document.addEventListener(`s-media-control-play-callback:mc-${videoItem.id}`, (e) => {
55          let playState = e.detail.playState;
56  
57          // 影片暫停 -> carrousel 暫停
58          if (playState === 'play') {
59            heroCarousel.Components.Autoplay.pause();
60          }
61  
62          // 影片播放 -> carrousel 播放
63          if (playState === 'pause') {
64            heroCarousel.Components.Autoplay.play();
65          }
66        });
67
68        // + 監聽 s-link 的 dialog 打開與關閉,並同步控制 影片/carousel 播放與暫停 
69        if (sLink) {
70          // 獲取 dialog id
71          const dialogId = sLink.getAttribute('dialog');
72          // 獲取 video cover
73          const videoCover = parent?.querySelector('.home-hero__carousel-item--video-cover');
74
75          // dialog 打開時,暫停影片與 carousel
76          document.addEventListener(`s-dialog-open-callback:${dialogId}`, () => {
77            heroCarousel.Components.Autoplay.pause();
78            document.dispatchEvent(new CustomEvent(`s-vid-pause:${videoItem.id}`));
79
80            // videoCover 和 timeoutVideoCover 存在時,就清除 timeout
81            // (dialog 關閉的時候,再讓它直接播放)
82            if (videoCover && timeoutVideoCover) {
83              clearTimeout(timeoutVideoCover);
84              timeoutVideoCover = null;
85            }
86          });
87
88          // dialog 關閉時,播放影片與 carousel
89          document.addEventListener(`s-dialog-close-callback:${dialogId}`, () => {
90            // 播放影片 & carousel
91            heroCarousel.Components.Autoplay.play();
92            document.dispatchEvent(new CustomEvent(`s-vid-play:${videoItem.id}`));
93            
94            // 確保關閉 dialog 後,隱藏 cover
95            if (videoCover) videoCover.classList.add('hide');
96          });
97        };
98      });
99    }
100  }
101
102  // * 確認該 slide 是否有可見的 s-video (支援同一 slide 有多個 video 的情境)
103  const isSlideHasVideo = (index) => {
104    const slide = heroCarousel.Components.Elements.slides[index];
105    const videoItems = slide?.querySelectorAll('.home-hero__carousel-item--video');
106    if (!videoItems || videoItems.length === 0) return null;
107    if (videoItems.length === 1) return videoItems[0];
108    return Array.from(videoItems).find(v => v.offsetParent !== null) || null;
109  }
110
111  // * video Replay (重頭播放)
112  const videoReplay = (videoItem, isFirstPlay = false) => {
113    const parent = videoItem.parentElement;
114    const sLink = parent?.querySelector('.home-hero__carousel-item--video-dialog');
115    const videoCover = parent?.querySelector('.home-hero__carousel-item--video-cover');
116
117    // 如果有 video cover,先顯示封面再播放
118    if(videoCover) {
119      // 如果有 sLink 的話,先讓它無法點擊
120      if (sLink) sLink.style.pointerEvents = 'none';
121
122      // 影片 & carouesel 暫停播放
123      document.dispatchEvent(new CustomEvent(`s-vid-pause:${videoItem.id}`));
124      heroCarousel.Components.Autoplay.pause();
125
126      // 顯示封面
127      videoCover.classList.remove('hide');
128
129      // timmer for 隱藏封面 & 恢復 sLink 點擊 & 播放影片 & carouesel
130      timeoutVideoCover = setTimeout(() => {
131        // 隱藏封面 & 恢復 sLink 點擊
132        videoCover.classList.add('hide');
133        if (sLink) sLink.style.pointerEvents = 'auto';
134
135        // 影片 & carouesel 播放
136        document.dispatchEvent(new CustomEvent(`s-vid-replay:${videoItem.id}`));
137        heroCarousel.Components.Autoplay.play();
138      }, TIMEOUT_TIME);
139
140      return;
141    }
142
143    // 沒有 cover,直接重頭播放
144    document.dispatchEvent(new CustomEvent(`s-vid-replay:${videoItem.id}`));
145    heroCarousel.Components.Autoplay.play();
146    // 恢復 sLink 點擊 (如果是第一次載入,要讓它等個 1s 不然會有 s-link 的執行順序 bug)
147    if (sLink) {
148      isFirstPlay ?
149        setTimeout(() => sLink.style.pointerEvents = 'auto', 1000)
150        : sLink.style.pointerEvents = 'auto';
151    }
152  };
153
154  // * hero carousel
155  const heroCarouselActive = () => {
156    heroCarousel = new Splide('.home-hero__carousel', {
157      type: 'fade',
158      rewind: true,
159      speed: 1500,
160      interval: 6000,
161      drag: true,
162      arrows: false,
163      autoplay: true,
164      resetProgress: false,
165      pauseOnHover: false, // hover 時是否暫停 autoplay
166      pauseOnFocus: false, // focus 時是否暫停 autoplay
167      classes: {
168        page: 'splide__pagination__page home-hero__carousel-page',
169      },
170    });
171
172    // + 監聽 slide 切換事件
173    heroCarousel.on('active', (slide) => {
174      // 檢查當前 slide 是否有影片子元素
175      const videoItem = isSlideHasVideo(slide.index);
176      
177      // 如果沒有影片子元素,繼續 autoplay
178      if (!videoItem) {
179        heroCarousel.Components.Autoplay.play();
180      }
181      
182      // 如果上一張有影片子元素,做 control "暫停" 同步處理
183      if (currentSlideIndex !== undefined) {
184        // 檢查上一張 slide 是否有影片子元素
185        const previousVideoItem = isSlideHasVideo(currentSlideIndex);
186
187        // 有影片的話,讓它暫停
188        if (previousVideoItem) {
189          // console.log(previousVideoItem);
190          document.dispatchEvent(new CustomEvent(`s-vid-pause:${previousVideoItem.id}`));
191        }
192      }
193
194      // 如果有影片子元素,讓影片 replay
195      if (videoItem) {
196        // 取得影片是否已載入
197        const isVideoLoaded = videoItem.getAttribute('data-video-loaded');
198
199        // 若已載入,影片直接重頭播放
200        if (isVideoLoaded) {
201          videoReplay(videoItem);
202        }
203      }
204
205      // 更新 currentSlideIndex
206      currentSlideIndex = slide.index;
207    });
208
209    // + 監聽 autoplay 播放
210    heroCarousel.on('autoplay:play', () => {
211      // 檢查當前 slide 是否有影片子元素
212      const videoItem = isSlideHasVideo(heroCarousel.index);
213
214      // 如果有影片子元素,做 control "播放" 同步處理
215      if (videoItem) {
216        document.dispatchEvent( new CustomEvent(`s-vid-play:${videoItem.id}`));
217      }
218    });
219
220    // + 監聽 autoplay 暫停
221    heroCarousel.on('autoplay:pause', () => {
222      // 檢查當前 slide 是否有影片子元素
223      const videoItem = isSlideHasVideo(heroCarousel.index);
224
225      // 如果有影片子元素,做 control "暫停" 同步處理
226      if (videoItem) {
227        document.dispatchEvent( new CustomEvent(`s-vid-pause:${videoItem.id}`));
228      }
229    });
230
231    // + 監聽 autoplay 進度事件
232    heroCarousel.on('autoplay:playing', (rate) => {
233      heroCarouselProgressBar.style.transform = `translateX(${(rate - 1) * 100}%)`;
234    });
235    
236    heroCarousel.mount();
237  };
238
239  // * 處理 第一個 item 是 video 的情形
240  const handleIfFirstSlideHasVideo = () => {
241    if (firstVideoItem) {
242      const activePage = document.querySelector('.home-hero__carousel-page.is-active');
243
244      // 先暫停後觸發當前 slide 的 click 事件
245      // 讓 splide 能抓到 data-splide-interval (for data-splide-interval bug)
246      heroCarousel.Components.Autoplay.pause();
247      if (activePage) activePage.click();
248    }
249  }
250  
251  heroCarouselActive();
252  setupCarouselVideoItems();
253  handleIfFirstSlideHasVideo();
254}

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.