PageSourceSearch

https://webcir.org/js/Carrousel.js

js webcir.org collected 2026-10-02 12:18:49 UTC 13,366 bytes, 448 lines download raw bytes

1class CgCarousel {
2    /**
3     * Constructor
4     */
5    constructor(selector, options = {}, hooks = []) {
6        this.container = document.querySelector(selector);
7        if (!this.container) return;
8
9        // Selectors
10        this.slidesSelector = options.slidesSelector || '.js-carousel__slide';
11        this.trackSelector = options.trackSelector || '.js-carousel__track';
12        this.slides = [];
13        this.track = this.container.querySelector(this.trackSelector);
14        this.slidesLength = 0;
15
16        // Breakpoints
17        this.currentBreakpoint = undefined;
18        this.breakpoints = options.breakpoints || {};
19
20        // Hooks
21        this.hooks = hooks;
22
23        // Options
24        this.initialOptions = {
25            loop: options.loop || false,
26            autoplay: options.autoplay || false,
27            autoplaySpeed: options.autoplaySpeed || 3000,
28            transitionSpeed: options.transitionSpeed || 650,
29            slidesPerView: options.slidesPerView || 1,
30            spacing: options.spacing || 0,
31        };
32        this.options = this.initialOptions;
33
34        // Transitions
35        this.animationStart = undefined;
36        this.animation = undefined;
37        this.animationCurrentTrans = 0;
38        this.animationIndex = 0;
39
40        // Animations
41        window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
42            window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
43        window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
44
45
46        // Functional
47        this.autoplayInterval = undefined;
48        this.isButtonRightDisabled = false;
49        this.isButtonLeftDisabled = false;
50        this.currentIndex = 0;
51        this.maxIndex = 0;
52        this.isInfinite = false;
53        this.isPrevInfinite = false;
54
55        // Swipe Event
56        this.swipeStartX = undefined;
57        this.swipeStartY = undefined;
58        this.swipeThreshold = 100; // Required min distance traveled to be considered swipe.
59        this.swipeRestraint = 100; // Maximum distance allowed at the same time in perpendicular direction.
60        this.swipeDir = undefined;
61
62        if (!this.track) return;
63
64        this.addEventListeners();
65        this.initCarousel();
66    };
67
68    /**
69     * Fire hook.
70     */
71    hook(name) {
72        this.hooks[name] && this.hooks[name](this);
73    };
74
75    /**
76     * Is touchable device?
77     */
78    isTouchableDevice() {
79        return window.matchMedia("(pointer: coarse)").matches;
80    };
81
82    /**
83     * Handle Swipe.
84     */
85    handleSwipe() {
86        switch (this.swipeDir) {
87            case 'top':
88            case 'bottom':
89                break;
90            case 'left':
91                this.next();
92                break;
93            case 'right':
94                this.prev();
95                break;
96            default:
97                break;
98        }
99    };
100
101    /**
102     * On swipe start.
103     */
104    onSwipeStart(e) {
105        if (!this.isTouchableDevice() || !e.changedTouches) return;
106        const touch = e.changedTouches[0];
107        this.swipeStartX = touch.pageX;
108        this.swipeStartY = touch.pageY;
109    };
110
111    /**
112     * Set swipe direction.
113     */
114    setSwipeDirection(e) {
115        const touch = e.changedTouches[0];
116        const distX = touch.pageX - this.swipeStartX;
117        const distY = touch.pageY - this.swipeStartY;
118
119        if (Math.abs(distX) >= this.swipeThreshold && Math.abs(distY) <= this.swipeRestraint) {
120            this.swipeDir = (distX < 0) ? 'left' : 'right';
121        } else if (Math.abs(distY) >= this.swipeThreshold && Math.abs(distX) <= this.swipeRestraint) {
122            this.swipeDir = (distY < 0) ? 'up' : 'down';
123        }
124    };
125
126    /**
127     * On swipe move.
128     */
129    onSwipeMove(e) {
130        if (!this.isTouchableDevice() || !e.changedTouches) return;
131        this.setSwipeDirection(e);
132        if (['left', 'right'].includes(this.swipeDir) && e.cancelable) e.preventDefault();
133    };
134
135    /**
136     * On swipe end.
137     */
138    onSwipeEnd(e) {
139        if (!this.isTouchableDevice() || !e.changedTouches) return;
140        this.setSwipeDirection(e);
141        this.handleSwipe();
142    };
143
144    /**
145     * Add Event Listeners.
146     */
147    addEventListeners() {
148        // Resize Events
149        window.addEventListener('orientationchange', () => this.onResize());
150        window.addEventListener('resize', () => this.onResize());
151
152        // Swipe Events
153        this.container.addEventListener('touchstart', (e) => this.onSwipeStart(e), {
154            passive: true
155        });
156        this.container.addEventListener('touchmove', (e) => this.onSwipeMove(e), false);
157        this.container.addEventListener('touchend', (e) => this.onSwipeEnd(e), {
158            passive: true
159        });
160    };
161
162    /**
163     * On Resize Event.
164     */
165    onResize() {
166        const breakpoint = this.checkBreakpoint();
167        breakpoint && this.buildCarousel();
168        this.hook('resized');
169    };
170
171    /**
172     * Configure Autoplay.
173     */
174    setUpAutoplay() {
175        if (!this.options.autoplay) return;
176        clearInterval(this.autoplayInterval);
177        this.autoplayInterval = setInterval(() => this.next(), this.options.autoplaySpeed);
178    };
179
180    /**
181     * Check current breakpoint.
182     */
183    checkBreakpoint() {
184        if (!this.breakpoints) return;
185
186        const currentBreakpoint = Object.keys(this.breakpoints).reverse().find(breakpoint => {
187            const media = `(min-width: ${breakpoint}px)`;
188            return window.matchMedia(media).matches;
189        });
190
191        if (this.currentBreakpoint === currentBreakpoint) return;
192
193        this.currentBreakpoint = currentBreakpoint;
194        const options = currentBreakpoint ? this.breakpoints[currentBreakpoint] : this.initialOptions;
195        this.options = {
196            ...this.initialOptions,
197            ...options
198        };
199        return true;
200    };
201
202    /**
203     * Set buttons visibility.
204     */
205    setButtonsVisibility() {
206        this.isButtonLeftDisabled = !this.options.loop && this.currentIndex === 0;
207        this.isButtonRightDisabled = !this.options.loop && this.currentIndex === (this.maxIndex - 1);
208    };
209
210    /**
211     * Clear Carousel Styles.
212     */
213    clearCarouselStyles() {
214        const trackStyles = ['grid-auto-columns', 'gap', 'transition', 'left'];
215        trackStyles.map(style => this.track.style.removeProperty(style));
216        const slideStyles = ['grid-column-start', 'grid-column-end', 'grid-row-start', 'grid-row-end', 'left'];
217        this.slides.forEach(slide => {
218            slideStyles.map(style => slide.style.removeProperty(style));
219        });
220    };
221
222    /**
223     * Set Carousel Classes.
224     */
225    setCarouselStyles() {
226        if (!this.slides) return;
227        const slidesPerView = this.options.slidesPerView;
228        const slideWidth = 100 / slidesPerView;
229        const gap = (this.options.spacing * (slidesPerView - 1)) / slidesPerView;
230        this.track.style.gridAutoColumns = `calc(${slideWidth}% - ${gap}px)`;
231        this.track.style.gridGap = `${this.options.spacing}px`;
232        this.track.style.left = 0;
233    };
234
235    /**
236     * Build Carousel with current Options.
237     */
238    buildCarousel() {
239        this.maxIndex = Math.ceil(this.slidesLength / this.options.slidesPerView);
240        this.clearCarouselStyles();
241        this.setCarouselStyles();
242        this.setButtonsVisibility();
243        this.setUpAutoplay();
244        this.currentIndex = 0;
245        this.hook('built');
246    };
247
248    /**
249     * Initialize carousel and fire created event.
250     */
251    initCarousel() {
252        this.slides = this.container.querySelectorAll(this.slidesSelector);
253        this.slidesLength = this.slides?.length;
254        this.checkBreakpoint();
255        this.buildCarousel();
256        this.hook('created');
257    };
258
259    /**
260     * On Animation End.
261     */
262    onAnimationEnd() {
263        const gap = this.options.spacing * this.animationIndex;
264        const trans = this.animationIndex * -100;
265        this.track.style.left = `calc(${trans}% - ${gap}px)`;
266        this.animationCurrentTrans = trans;
267        this.animation = null;
268        this.isInfinite && this.clearInfinite();
269        this.isPrevInfinite && this.clearPrevInfinite();
270    }
271
272    /**
273     * Cancel Animation frame.
274     */
275    moveAnimateAbort() {
276        if (!this.animation) return;
277
278        window.cancelAnimationFrame(this.animation);
279        this.onAnimationEnd();
280    };
281
282    /**
283     * Animate Left.
284     * @param timestamp
285     * @param trans
286     * @param gap
287     * @param duration
288     */
289    animateLeft(timestamp, trans, gap, duration) {
290        const runtime = timestamp - this.animationStart;
291        const easing = t => 1 - Math.pow(1 - t, 5);
292        const progress = easing(runtime / duration);
293        const dist = ((trans * progress) + (this.animationCurrentTrans * (1 - progress))).toFixed(2);
294        this.track.style.left = `calc(${dist}% - ${gap}px)`;
295
296        if (runtime >= duration) {
297            this.onAnimationEnd();
298            return;
299        }
300
301        this.animation = window.requestAnimationFrame(timestamp => {
302            this.animateLeft(timestamp, trans, gap, duration)
303        })
304    };
305
306    /**
307     * Move Slide.
308     */
309    moveSlide(index, cIndex) {
310        this.moveAnimateAbort();
311        const gap = this.options.spacing * index;
312        const trans = index * -100;
313
314        this.animation = window.requestAnimationFrame((timestamp) => {
315            index === this.maxIndex && this.setInfinite();
316            index === -1 && this.setPrevInfinite();
317            this.animationStart = timestamp;
318            this.animationIndex = this.currentIndex;
319            this.animateLeft(timestamp, trans, gap, this.options.transitionSpeed);
320        });
321
322        this.currentIndex = cIndex;
323
324        this.setUpAutoplay();
325        this.setButtonsVisibility();
326        this.hook('moved');
327    };
328
329    /**
330     * Set Infinity.
331     */
332    setInfinite() {
333        this.isInfinite = true;
334        const count = this.options.slidesPerView * this.maxIndex;
335
336        for (let idx = 0; idx < this.options.slidesPerView; idx++) {
337            const slide = this.slides[idx];
338            slide.style.left = `calc((100% * ${count}) + (${this.options.spacing}px * ${count}))`;
339        }
340    };
341
342    /**
343     * Clear Infinity.
344     */
345    clearInfinite() {
346        this.isInfinite = false;
347        this.track.style.left = `calc(${this.currentIndex * -100}% - ${this.options.spacing * this.currentIndex}px)`;
348
349        this.slides.forEach((slide, idx) => {
350            if (idx >= this.options.slidesPerView) return;
351            slide.style.removeProperty('left');
352        })
353    };
354
355    /**
356     * Next Slide.
357     */
358    next() {
359        const nextIndex = this.currentIndex === this.maxIndex - 1 ? 0 : this.currentIndex + 1;
360        if (!this.options.loop && nextIndex < this.currentIndex) return;
361
362        if (nextIndex < this.currentIndex) {
363            this.moveSlide(this.currentIndex + 1, nextIndex);
364            return;
365        }
366
367        this.moveSlide(nextIndex, nextIndex);
368    };
369
370    /**
371     * Set Prev Infinity.
372     */
373    setPrevInfinite() {
374        this.isPrevInfinite = true;
375        const count = this.options.slidesPerView * this.maxIndex;
376        const maxIdx = count - this.options.slidesPerView;
377
378        for (let idx = this.slides.length - 1; idx >= 0; idx--) {
379            if (idx < maxIdx) return;
380            const slide = this.slides[idx];
381            slide.style.left = `calc((-100% * ${count}) - (${this.options.spacing}px * ${count}))`;
382        }
383    };
384
385    /**
386     * Clear Prev Infinity.
387     */
388    clearPrevInfinite() {
389        this.isPrevInfinite = false;
390        this.track.style.left = `calc(${this.currentIndex * -100}% - ${this.options.spacing * this.currentIndex}px)`;
391        this.slides.forEach((slide, idx) => {
392            slide.style.removeProperty('left');
393        });
394    };
395
396    /**
397     * Prev Slide.
398     */
399    prev() {
400        const nextIndex = this.currentIndex === 0 ? this.maxIndex - 1 : this.currentIndex - 1;
401        if (!this.options.loop && nextIndex > this.currentIndex) return;
402
403        if (nextIndex > this.currentIndex) {
404            this.moveSlide(this.currentIndex - 1, nextIndex);
405            return;
406        }
407
408        this.moveSlide(nextIndex, nextIndex);
409    };
410
411    /**
412     * Move to Slide.
413     */
414    moveToSlide(index) {
415        if (index === this.currentIndex) return;
416        this.moveSlide(index, index);
417    };
418
419    /**
420     * Get Slides
421     */
422    getSlides() {
423        return this.slides;
424    };
425
426    /**
427     * Get Current Index.
428     */
429    getCurrentIndex() {
430        return this.currentIndex;
431    };
432
433    /**
434     * Get Options.
435     */
436    getOptions() {
437        return this.options;
438    };
439
440    /**
441     * Get Page Size.
442     */
443    getPageSize() {
444        return this.maxIndex;
445    };
446}
447
448window.CgCarousel = CgCarousel;

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.