ismail9k / vue3-carousel

Vue 3 carousel component
https://ismail9k.github.io/vue3-carousel/
MIT License
653 stars 162 forks source link

Adding touch events (start/move) to pauseAutoplayOnHover #352

Closed EleoXDA closed 5 months ago

EleoXDA commented 5 months ago

Is your feature request related to a problem? Please describe. As a mobile user, I would also like to have the carousel autoscroll be paused when I am checking one of the items.

Describe the solution you'd like I would like to have the pauseAutoplayOnHover (or extra boolean as pauseAutoplayOnTouch) that controls autoplay on touch events.

Describe alternatives you've considered I tried to override the functions on the component that I use as following:

import { Carousel, Slide, Pagination } from 'vue3-carousel';
import { ref, onMounted, onUnmounted } from 'vue';

const carouselRef = ref(null);

const handleTouchStart = () => {
  if (carouselRef.value) {
    clearInterval(carouselRef.value.autoplayTimer);
    carouselRef.value.autoplayTimer = null;
  }
};

const handleTouchEnd = () => {
  if (carouselRef.value && carouselRef.value.config.autoplay) {
    carouselRef.value.initAutoplay();
  }
};

onMounted(() => {
  const carouselElement = carouselRef.value.$el;
  carouselElement.addEventListener('touchstart', handleTouchStart);
  carouselElement.addEventListener('touchend', handleTouchEnd);
});

onUnmounted(() => {
  const carouselElement = carouselRef.value.$el;
  carouselElement.removeEventListener('touchstart', handleTouchStart);
  carouselElement.removeEventListener('touchend', handleTouchEnd);
});

But for some reason it did not work. I tried to be more concise as well:

import { Carousel, Slide, Pagination } from 'vue3-carousel';
import { ref, onMounted, onUnmounted } from 'vue';

const carouselRef = ref(null);
let isTouching = false;
let isDragging = false;
let startX = 0;
const dragThreshold = 10;

const pauseAutoplay = () => {
  if (carouselRef.value) {
    clearInterval(carouselRef.value.autoplayTimer);
    carouselRef.value.autoplayTimer = null;
  }
};

const resumeAutoplay = () => {
  if (carouselRef.value && carouselRef.value.config.autoplay && !isDragging) {
    carouselRef.value.initAutoplay();
  }
};

const handleTouchStart = (e) => {
  isTouching = true;
  isDragging = false;
  startX = e.touches[0].clientX;
  pauseAutoplay();
};

const handleTouchMove = (e) => {
  if (!isTouching) return;
  const currentX = e.touches[0].clientX;
  if (Math.abs(currentX - startX) > dragThreshold) {
    isDragging = true;
  }
};

const handleTouchEnd = () => {
  isTouching = false;
  resumeAutoplay();
};

onMounted(() => {
  const carouselElement = carouselRef.value.$el;
  carouselElement.addEventListener('touchstart', handleTouchStart);
  carouselElement.addEventListener('touchmove', handleTouchMove);
  carouselElement.addEventListener('touchend', handleTouchEnd);
});

onUnmounted(() => {
  const carouselElement = carouselRef.value.$el;
  carouselElement.removeEventListener('touchstart', handleTouchStart);
  carouselElement.removeEventListener('touchmove', handleTouchMove);
  carouselElement.removeEventListener('touchend', handleTouchEnd);
});

But unfortunately still failed. I guess having these inside the node_module could be much useful than doing it from external.

P.S. I am using script setup

EleoXDA commented 5 months ago

Solved. Even added control over detection of horizontal/vertical touch gesture. Trick was to add control regions on template:

<template>
    <div
        @touchstart="handleTouchStart"
        @touchmove="handleTouchMove"
        @touchend="handleTouchEnd"
    >
        <Carousel
            ref="carouselRef"
            :autoplay="isAutoplayActive ? autoPlayDelay : 0"
            :pauseAutoplayOnHover="true"
            :transition="1000"
            :wrap-around="true"
        >
            <Slide v-for="item in items" :key="item.id || item">
                <div class="item">
                    <component />
                </div>
            </Slide>
            <template #addons>
                <Pagination />
            </template>
        </Carousel>
    </div>
</template>
<script setup>
import { Carousel, Slide, Pagination } from 'vue3-carousel';
import { ref, reactive } from 'vue';
import 'vue3-carousel/dist/carousel.css';
const autoPlayDelay = 5000;
const carouselRef = ref(null);
const isAutoplayActive = ref(true);
const touchPosition = reactive({ startX: 0, startY: 0, endX: 0, endY: 0 });
const isScrollingVertically = ref(false);
// eslint-disable-next-line no-unused-vars

const handleTouchStart = (event) => {
    touchPosition.startX = event.touches[0].clientX;
    touchPosition.startY = event.touches[0].clientY;
};
const handleTouchMove = (event) => {
    touchPosition.endX = event.touches[0].clientX;
    touchPosition.endY = event.touches[0].clientY;
    const deltaX = Math.abs(touchPosition.endX - touchPosition.startX);
    const deltaY = Math.abs(touchPosition.endY - touchPosition.startY);
    if (deltaX > deltaY) {
        isAutoplayActive.value = true;
    }
    if (deltaY > 2 * deltaX) {
        isScrollingVertically.value = true;
    } else {
        isScrollingVertically.value = false;
    }
};
const handleTouchEnd = () => {
    const deltaX = Math.abs(touchPosition.endX - touchPosition.startX);
    const deltaY = Math.abs(touchPosition.endY - touchPosition.startY);
    isScrollingVertically.value = false;
    if (deltaX > deltaY) {
        isAutoplayActive.value = true;
    }
};
</script>