Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trigger callback when slide dragged is over centralised position #7318

Closed
5 of 6 tasks
mattwdavies opened this issue Feb 15, 2024 · 0 comments
Closed
5 of 6 tasks

Trigger callback when slide dragged is over centralised position #7318

mattwdavies opened this issue Feb 15, 2024 · 0 comments
Labels

Comments

@mattwdavies
Copy link

mattwdavies commented Feb 15, 2024

Check that this is really a bug

  • I confirm

Reproduction link

sandbox

Bug description

I cannot find a clear way to use the API to log the slide index that is hovered over the central position. (i.e the slide that will snap to centre when drag is released).

I have built a number line with drag and release functionality, that logs the current slide index on the screen. I have used a combination of the slides lengths and swiper.progress to roughly calculate when a slide is dragged over the middle, but it gets inaccurate when using different sized slides (see GIF)

2024-02-15 16 08 37

Is there anything in the API that will allow me to highlight a slide when it is the chosen slide to snap to the middle?

import React, { useState, useRef } from 'react';
import { Swiper, SwiperClass, SwiperSlide } from 'swiper/react';
import 'swiper/swiper-bundle.css';
import './TimelineStepper.css';
import { Autoplay } from 'swiper/modules';
import { SwiperOptions } from 'swiper/types';

interface Timestamp {
  label: string;
}

interface TimelineStepperProps {
  onTimelineChange: (index: number, label: string) => void;
}

const generateTimestamps = (): Timestamp[] => {
  const timestamps: Timestamp[] = [];

  for (let i = 0; i < 24; i++) {
    const label = `${i.toString().padStart(2, '0')}:00`;
    timestamps.push({ label });
  }

  return timestamps;
};

const TimelineStepper: React.FC<TimelineStepperProps> = ({ onTimelineChange }) => {
  const [currentSlide, setCurrentSlide] = useState<number>(12);
  const [autoplay, setAutoplay] = useState<boolean>(false);
  const timestamps = generateTimestamps();
  const swiperRef = useRef<SwiperClass | null>(null);
  const swiperContainerRef = useRef<HTMLDivElement | null>(null);


  const handleSlideClick = (index: number) => {
    setCurrentSlide(index);
    onTimelineChange(index, timestamps[index].label);
  };

  const handleSlideChange = (swiper: SwiperClass) => {
    setCurrentSlide(swiper.realIndex);
    onTimelineChange(swiper.realIndex, timestamps[swiper.realIndex].label);
  };

  const toggleAutoplay = () => {
    setAutoplay(!autoplay);
  };

  const calculateMiddleSlideIndex = (progress: number): number => {
    const swiper = swiperRef.current;

    if (!swiper) {
      return 0;
    }

    const slidesInfo: [number, number][] = Array.from(swiper.slides).map((slide, index) => [index, slide.offsetWidth]);

    // Calculate the total width of all slides
    const totalWidth = slidesInfo.reduce((acc, [, width]) => acc + width, 0);

    // Calculate the width of the virtual middle section, subtracting half of the first and last slide widths
    const newTotalWidth = totalWidth - slidesInfo[0][1] / 2 - slidesInfo[slidesInfo.length - 1][1] / 2;

    // Calculate the position in the whole slider based on progress using the adjusted total width
    const currentPosition = progress * newTotalWidth;

    let middleSlideIndex = 0;
    let cumulativeWidth = 0;

    // Iterate through slidesInfo to find the one containing the middle position
    for (const [slideIndex, slideWidth] of slidesInfo) {
      const slideStart = cumulativeWidth;
      console.log(slideWidth, slideIndex)
      // const slideStart = cumulativeWidth + slideWidth / 2;

      const slideEnd = slideStart + slideWidth + (slideWidth / 2);

      if (currentPosition >= slideStart && currentPosition <= slideEnd) {
        middleSlideIndex = slideIndex;
      }

      cumulativeWidth += slideWidth;
    }

    // If the current slide is partially over the middle point, set it as the middleSlideIndex
    if (currentPosition > cumulativeWidth) {
      middleSlideIndex += 1;
    }

    onTimelineChange(middleSlideIndex, timestamps[middleSlideIndex]?.label);
    setCurrentSlide(middleSlideIndex);
    return middleSlideIndex;
  };




  const swiperParams: SwiperOptions = {
    slidesPerView: 'auto',
    spaceBetween: 0,
    centeredSlides: true,
    loop: false,
    navigation: false,
    slideToClickedSlide: true,
    autoplay: {
      delay: 500,
      disableOnInteraction: false,
    },
    modules: [Autoplay],
  };

  if (swiperRef.current) {
    if (autoplay) {
      swiperRef.current.autoplay.start();
    } else {
      swiperRef.current.autoplay.stop();
    }
  }


  return (
    <div ref={swiperContainerRef} className='swiper-main-wrapper'>
      <Swiper
        {...swiperParams}
        onSwiper={(swiper) => {
          swiperRef.current = swiper;
        }}
        onSlideChange={(swiper) => handleSlideChange(swiper)}
        onTouchStart={() => setAutoplay(false)}
        onSliderMove={(swiper) => {
          calculateMiddleSlideIndex(swiper.progress);
        }}
      >
        {timestamps.map((timestamp, index) => (
          <SwiperSlide key={index} onClick={() => handleSlideClick(index)} className={index > 5 ? 'wide-div' : ''}>
            <div>
              <p className='text-label'>{timestamp.label}</p>
              <div className={`container timeline-button ${index === currentSlide ? 'active' : ''} ${index < currentSlide ? 'past' : ''} ${index > currentSlide ? 'upcoming' : ''}`}>
                <div className={`left-line ${index < currentSlide ? 'past-line' : ''}`}></div>
                <div className={`divider ${index < currentSlide ? 'past-divider' : ''}`}></div>
                <div className={`right-line ${index < currentSlide ? 'past-line' : ''}`}></div>
              </div>
            </div>
          </SwiperSlide>
        ))}
      </Swiper>
      <div className='control-cont'>
        <div className="line"></div>
        <button type="button" onClick={toggleAutoplay}>
          {autoplay ? 'Pause' : 'Play'}
        </button>

      </div>
    </div>
  );
};

export default TimelineStepper;

Expected Behavior

No response

Actual Behavior

2024-02-15 16 08 37

Swiper version

11.0.6

Platform/Target and Browser Versions

macOS, Chrome

Validations

  • Follow our Code of Conduct
  • Read the docs.
  • Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
  • Make sure this is a Swiper issue and not a framework-specific issue

Would you like to open a PR for this bug?

  • I'm willing to open a PR
Repository owner locked and limited conversation to collaborators Mar 19, 2024
@nolimits4web nolimits4web converted this issue into discussion #7394 Mar 19, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
Projects
None yet
Development

No branches or pull requests

1 participant