PageSourceSearch

https://www.elle.cz/js/modules/articleCarousel.js

js elle.cz collected 2026-10-02 07:55:28 UTC 3,105 bytes, 120 lines download raw bytes

1import {getHorizontalItemStep} from '../core/measure.js';
2
3const DESKTOP_BREAKPOINT = 1024;
4const VISIBLE_CARD_COUNT = 3;
5const ANIMATION_DURATION = 300;
6
7const createArticleCarousel = (container) => {
8  const track = container.querySelector('.article-carousel__track');
9  const prevButton = container.querySelector('[data-article-carousel-prev]');
10  const nextButton = container.querySelector('[data-article-carousel-next]');
11
12  if (!(track instanceof HTMLElement)) {
13    return {syncToViewport() {}};
14  }
15
16  const originalCards = Array.from(track.children);
17  const canLoop = originalCards.length > VISIBLE_CARD_COUNT;
18  const pendingDirections = [];
19  let isEnabled = false;
20  let activeMove = null;
21
22  for (const button of [prevButton, nextButton]) {
23    if (button) {
24      button.hidden = !canLoop;
25    }
26  }
27
28  const startMove = (direction) => {
29    if (!isEnabled) {
30      return;
31    }
32    if (activeMove) {
33      pendingDirections.push(direction);
34      return;
35    }
36
37    const step = getHorizontalItemStep(track, '.article-carousel__card');
38    if (step <= 0) {
39      return;
40    }
41
42    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
43      if (direction > 0) {
44        track.append(track.firstElementChild);
45      } else {
46        track.prepend(track.lastElementChild);
47      }
48      return;
49    }
50
51    if (direction < 0) {
52      track.prepend(track.lastElementChild);
53      track.style.transform = `translateX(-${step}px)`;
54    }
55
56    const animation = track.animate([
57      {transform: `translateX(${direction > 0 ? 0 : -step}px)`},
58      {transform: `translateX(${direction > 0 ? -step : 0}px)`}
59    ], {
60      duration: ANIMATION_DURATION,
61      easing: 'ease',
62      fill: 'forwards'
63    });
64
65    activeMove = {animation, direction};
66    animation.onfinish = finishMove;
67  };
68
69  const finishMove = () => {
70    if (!activeMove) {
71      return;
72    }
73
74    const {animation, direction} = activeMove;
75    activeMove = null;
76    animation.onfinish = null;
77    animation.cancel();
78
79    if (direction > 0) {
80      track.append(track.firstElementChild);
81    }
82    track.style.transform = '';
83
84    if (isEnabled && pendingDirections.length > 0) {
85      startMove(pendingDirections.shift());
86    }
87  };
88
89  prevButton?.addEventListener('click', () => startMove(-1));
90  nextButton?.addEventListener('click', () => startMove(1));
91
92  return {
93    syncToViewport() {
94      const shouldEnable = canLoop && window.innerWidth >= DESKTOP_BREAKPOINT;
95
96      if (!shouldEnable) {
97        isEnabled = false;
98        pendingDirections.length = 0;
99        finishMove();
100        track.replaceChildren(...originalCards);
101        return;
102      }
103
104      isEnabled = true;
105      finishMove();
106    }
107  };
108};
109
110export function initArticleCarouselNavigation() {
111  const containers = document.querySelectorAll('[data-article-carousel]');
112  if (containers.length === 0) {
113    return;
114  }
115
116  const instances = Array.from(containers, createArticleCarousel);
117  const refresh = () => instances.forEach((instance) => instance.syncToViewport());
118  refresh();
119  window.addEventListener('resize', refresh);
120}

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.