5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue3-carouselの両端を固定する

Last updated at Posted at 2024-11-08

はじめに

Vueのプロジェクトでカルーセルスライダーを作りたい時、自分で一から作るのは面倒なので、ライブラリを利用することも多いんじゃないかと思います。

Vue3でカルーセルスライダーを使う選択肢としてはVue3-carouselが便利なので、こちらを選ぶ方が多いと思いますが、Vue3-carouselは両端の固定を行わないため、少し挙動に違和感を感じます。

例えばスライド1は左端なのでこれ以上左に移動して欲しくありません。
マイムービー 1.gif

右端に当たるスライド10も同様です。
マイムービー 1-1.gif

今回はこの挙動を改善し、両端のスライダーを固定する機能を自作したので共有します。
(slick.js, swiperなども同様に端が固定されませんが、本記事の内容を修正することでそちらも対応できるのではと思います)

Vue3-carouselの仕組み

Vue3-carouselはtranslateXの値を0から変化させていくことで横にスライドしていく動作を実現しています。

スライド1の時はtranslateXが0pxになっています。
スクリーンショット 2024-11-07 10.18.11.png
スクリーンショット 2024-11-07 10.18.17.png

スライド10だと、translateXが-1800pxです。
スクリーンショット 2024-11-07 10.19.07.png
スクリーンショット 2024-11-07 10.19.13.png

両端の挙動の原因

左端、右端からさらに移動しようとした時に固定されない原因は、translateXの最大値、最小値の制御を行う機能が存在していないためです。
つまり、

  • translateXが0pxより大きくならない
  • translateXが-1800px未満にならない

が達成されれば両端を固定できます。

解決

MutationObserverを利用してcarousel__trackクラスのスタイルの変化を監視し、translateXが上記条件の幅に収まるように実装しました。
下記が実際に利用したコードです。

<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import 'vue3-carousel/dist/carousel.css'
import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'

onMounted(() => {
  const track = document.querySelector('.carousel__track') as HTMLElement;
  if (!track) return;

  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
        const transform = track.style.transform;
        // 正規表現でtranslateXが存在するかどうか確認
        const match = transform.match(/translateX\(([-\d.]+)px\)/);
        if (!match) return;

        // 現在のpx値を取得
        const currentX = parseFloat(match[1]);
        
        let limitedX = 0;
        // -1800より小さくならず、0より大きくならない範囲指定。
        // 今回は固定値で入力しているが、スライドの数や配置方法で自由に変更すると良い
        limitedX = Math.max(-1800, Math.min(0, currentX));
        
        if (currentX !== limitedX) {
          track.style.transform = `translateX(${limitedX}px)`;
        }
      }
    });
  });

  observer.observe(track, {
    attributes: true,
    attributeFilter: ['style']
  });

  onUnmounted(() => {
    observer.disconnect();
  });
});
</script>

<template>
  <div>
    <Carousel :items-to-show="1">
      <Slide v-for="slide in 10" :key="slide">
        <div class="carousel__item">{{ slide }}</div>
      </Slide>
      <template #addons>
        <Navigation />
        <Pagination />
      </template>
    </Carousel>
  </div>
</template>

これで両端の固定ができました。実際の挙動が下のgifになります。
マイムービー 3.gif

マイムービー 3−2.gif

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?