Публікація

Простий карусель-слайдер з підтримкою скрола та навігації

Занотовую приклад каруселі-слайдера на AlpineJS

Зазвичай каруселі роблять за допомогою відємного margin та маніпуляції цим margin через кнопки навігації.

Я подумав, що не треба перевигадувати слайдер, який ітак підтримується браузером з коробки, це звичайний Scroll та умовний Scroll API.

Якщо покласти елементи в flexbox-контейнер, дати кожному слайду width 100% від контейнера та скролити за допомогою навігації у право чи ліво на width контейнера, то як раз отримаємо звичайну карусель. Тим паче ми можемо використовувати scroll snap, що дасть можливість “докручувати” карусель до потрібного слайда.

З браузероного Scroll API мені потрібна тільки функція element.scrollTo(), яка може прокручувати контейнер в потрібному напрямку на порібну кількість пікселів, а така опція як behavior: "smooth" додає плавності прокрутці.

Розглянемо наступний HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="carousel" x-data="carousel">
    <div class="carousel-items" x-ref="items">
        <div class="carousel-item">1</div>
        <div class="carousel-item">2</div>
        <div class="carousel-item">3</div>
    </div>
    <button
        type="button"
        class="carousel-button carousel-button-start"
        x-bind:disabled="scrolledStart"
        x-on:click="prev"
    >
        &larr;
    </button>
    <button
        type="button"
        class="carousel-button carousel-button-end"
        x-bind:disabled="scrolledEnd"
        x-on:click="next"
    >
        &rarr;
    </button>
</div>

HTML складається з загального контейнера carousel, контейнера carousel-items який утримує слайди, та навігаційні кнопоки carousel-button carousel-button-start “попередній слайд”, carousel-button carousel-button-end “наступний слайд”.

Контейнеру задамо postion, щоб мати змогу позіціонувати кнопки відносно цього блоку.

1
2
3
.carousel {
    position: relative;
}

Далі додамо стилі контейнеру, який утримує слайди. Цей контейнер повинен мати відображення flexbox, щоб мати змогу розтушувати елементи всередені в горізонтальну лінію. Ще додамо overflow для скрола по горізонталі і заборонемо скрол по вертикалі. Також scroll-snap-type дасть змогу “докручувати” слайдер до потрібного слайду.

1
2
3
4
5
6
7
8
9
10
.carousel-items {
    display: flex;
    width: 100%;
    flex-direction: row;
    gap: .75rem;
    overflow-y: hidden;
    overflow-x: scroll;
    scrollbar-width: none;
    scroll-snap-type: x mandatory;
}

Для елементів всередені carousel-items дамо ширину 100% контейнера, елемент буде мати пропорційну висоту 16:9.

1
2
3
4
5
6
7
8
9
10
.carousel-item {
    display: flex;
    aspect-ratio: 16 / 9;
    min-width: 100%;
    align-items: center;
    justify-content: center;
    border-radius: .5rem;
    background-color: silver;
    scroll-snap-align: start;
}

Щодо кнопок, то їх спозіціонуємо у відповідні частини контейнера (зліва і зправа).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.carousel-button {
    position: absolute;
    top: 0;
    bottom: 0;
    margin-top: auto;
    margin-bottom: auto;
    height: 2rem;
    width: 2rem;
    border-radius: 9999px;
    background-color: #000;
    padding: .25rem;
    color: rgb(255 255 255);
}

.carousel-button.carousel-button-start {
    left: .75rem;
}

.carousel-button.carousel-button-end {
    right: .75rem;
}

Перейдемо до JS, він буде написаний з використанням фреймворка AlpineJS, проте цей код можно легко переписати на чистому JS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
window.carousel = () => {
  let handleScroll, timer, cleanup;

  return {
    scrolledStart: false, // Флаг, який каже що контейнер .carousel-items проскролено до лівої межі
    scrolledEnd: false, // Флаг, який каже що контейнер .carousel-items проскролено до правої межі

    init() {
      // Функція, яка викликається щоразу як скорлиться контейнер .carousel-items, відслідковує
			// позицію скрола та встановлює флаги scrolledStart, scrolledEnd у відповідні значення
      handleScroll = () => {
        if (timer) {
          clearTimeout(timer);
        }

        timer = setTimeout(() => {
          this.scrolledEnd =
            Math.ceil(
              this.$refs.items.scrollLeft + this.$refs.items.clientWidth,
            ) >= Math.ceil(this.$refs.items.scrollWidth);
          this.scrolledStart = this.$refs.items.scrollLeft <= 0;
        }, 10);
      };

      handleScroll();

      this.$refs.items.addEventListener("scroll", handleScroll, false);
      addEventListener("resize", handleScroll, false);

      cleanup = () => {
        this.$refs.items.removeEventListener("scroll", handleScroll, false);
        removeEventListener("resize", handleScroll, false);
      };
    },

    destroy() {
      cleanup();
    },

    // Функція яка скролить контейнер .carousel-items на один слайд вправо (вперед)
    next() {
      const left =
        this.$refs.items.scrollLeft + this.$refs.items.clientWidth >=
        Math.ceil(this.$refs.items.scrollWidth)
          ? this.$refs.items.scrollWidth - this.$refs.items.clientWidth
          : this.$refs.items.scrollLeft + this.$refs.items.clientWidth;

      this.$refs.items.scrollTo({
        top: 0,
        left,
        behavior: "smooth",
      });
    },

    // Функція яка скролить контейнер .carousel-items на один слайд вліво (назад)
    prev() {
      const left =
        this.$refs.items.scrollLeft - this.$refs.items.clientWidth > 0
          ? this.$refs.items.scrollLeft - this.$refs.items.clientWidth
          : 0;

      this.$refs.items.scrollTo({
        top: 0,
        left,
        behavior: "smooth",
      });
    },
  };
};

Загальний результат можно переглянути за посиланням: jsfiddle.net/jmas/g3r05pzu/13/.

Публікація захищена ліцензією CC BY 4.0 .

© jmas. Деякі права захищено.

Powered by Jekyll with Chirpy theme