Material UI Carousel - with autoscroll

Viewed 2555

enter image description here

I am building a carousel component in react -- and the client wants there to be an autoscroll option - my concern though is interfering with the user interface -- what kind of logic to build to break/pause the autoscroll without interfering with the user trying to use it in the first place. So hover detection is needed?

//Original Carousel https://codesandbox.io/s/holy-https-h0yv3

//With current autoscroll option on - but ignoring all UI considerations https://codesandbox.io/s/nervous-brown-2inki?file=/src/Carousel/Carousel.js

also - the carousel after going through the slides - will just ZIP through the deck - without being able to snap to the slide - so that's another issue

import React, { Component } from 'react';

import Button from '@material-ui/core/Button';
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';

import './Carousel.scss';

class Carousel extends Component {
    constructor() {
      super();
      this.myRef = React.createRef();
      this.state = {
        currentSlide: 0,
        slideWidth: 0,
        slideCount: 0
      };
      this.updateDimensions = this.updateDimensions.bind(this);
      this.handleKeyPress = this.handleKeyPress.bind(this);

      this.prev = this.prev.bind(this);
      this.next = this.next.bind(this);
    }

    componentDidMount() {
      window.addEventListener("resize", this.updateDimensions);
      this.updateDimensions();

      this.setState({
        slideCount: this.props.items.length
      });

      this.autoSlide();
    }

    componentWillUnmount() {
      window.removeEventListener("resize", this.updateDimensions);
    }

    handleKeyPress(e) {
      if (e.keyCode === 39) {
        this.next();
      }
      if (e.keyCode === 37) {
        this.prev();
      }
    }

    updateDimensions(){
      //update the dimensions of the slide for responsiveness
      this.setState({
        slideWidth: this.myRef.current.getBoundingClientRect().width
      });
    }

    prev(){
      //if the current slide won't hit negative slide - change current slide
      if(this.state.currentSlide > 0){
        let prevSlide = this.state.currentSlide-1;
        this.setState({
          currentSlide: prevSlide
        });
      }
    }

    next(){
      //if the next slide will not go beyond the items - change current slide
      if(this.state.currentSlide < this.props.items.length-1){
        let nextSlide = this.state.currentSlide+1;
        this.setState({
          currentSlide: nextSlide
        });
      }
    }

    selected(slide){
      console.log(slide)
      this.setState({
        currentSlide: slide
      });
    }

    autoSlide(){
      let that = this;
      let count = this.props.items.length;

      let i = 0;
      setInterval(function(){ 
        that.selected(i);
        i++;

        if(count === i){
          i=0;//resets
        }
      }, 3000);
    }

    render() {
        return (
          <div ref={this.myRef} className="carousel" onKeyDown={this.handleKeyPress} tabIndex={-1}>
            <div className="carousel-holder">
              <div className="carousel-wrapper">
                  <div 
                    className="slide-wrapper"
                    style={{
                      left: -this.state.slideWidth*this.state.currentSlide,
                      width: this.state.slideWidth*this.state.slideCount
                    }}
                  >
                  {
                    this.props.items.map((item, j) => {
                      return(
                        <div 
                          key={j}
                          className="slide" 
                          style={{
                            width: this.state.slideWidth
                          }}               
                        >
                          <div className={"gutter " + (this.props.hasGutter? "has-gutter" : "")}>
                            {item.slide}
                          </div>
                        </div>
                      )
                    })
                  }
                  </div>
              </div>

              <Button 
                className="carousel-arrow prev-arrow"
                onClick={this.prev}
                disabled={(this.state.currentSlide === 0)}
              >
                <ChevronLeftIcon />
              </Button>

              <Button 
                className="carousel-arrow next-arrow"
                onClick={this.next}
                disabled={(this.state.currentSlide === this.props.items.length-1)}
              >
                <ChevronRightIcon />
              </Button>
            </div>

            <div className="carousel-dots">
              <ul className="dot-wrapper">
                {
                  this.props.items.map((item, x) => {
                    return (
                      <li 
                        key={x}
                        className={(this.state.currentSlide === x? "selected" : "")}
                      >
                        <Button 
                          className="dot"
                          onClick={() => this.selected(x)}
                        />
                      </li>
                    )
                  })
                }
              </ul>
            </div>
          </div>
        );
    }
}

export default Carousel;
2 Answers

You should create a boolean shouldAutoScroll which should be passed as prop to this Carousel component. Initially, it's value would be true, which means this slides should autoscroll, based on a time-interval you can keep changing the active slide.

One the user hovers over the images or once the user clicks the next/previous button, set that shouldAutoScroll to false, which would mean that logic to autoscroll should be stopped.

this.state = {
      shouldAutoScroll: true,
    ...
    }

and since you are already calling it on componentDidMount

componentDidMount() {
    ...
    this.state.shouldAutoScroll && this.autoSlide();
  }

Toggle it's value, on the next() and prev(), where it is certain that user wants to take control of the scrolling.

next/prev () {
this.setState(p => p.shouldAutoScroll && ({...p, shouldAutoScroll: false}));
... // further logic
}

I'm not sure how to adjust your code in the given scenario according to your expectation. But I also wanted the same thing to be done in my carousel. Then I found this npm package that can be used to display a carousel. I think you also can use this and rectify your problem.

Related