preface

Yesterday, I was wandering in a group, a brother suddenly added me as a friend, probably because I often bragged in the group, was mistaken as a leader, said to me, brother, I have a demand, can I ask you to help me do it?

The requirement was to make a seamless wheel cast diagram, and I said, isn’t there a lot of wheels out there? Later, I learned that he had a special requirement that the small dots should be outside the wheel map, because most plug-ins now write small dots inside the wheel map, which is really difficult for those who do not know the internal structure of plug-ins to modify.

I haven’t written a plugin for a long time. I’m going to write a Plugin for React.

Seamless shuffling

Seamless rotation does not backtrack from the last slide to the first slide, it is like a wheel, seamless from end to start, very natural cycle.

Implementation approach

There are many ways to realize the rotation map. We adopt the simplest rotation map scheme here, as shown in the figure above, that is, when the rotation map reaches the x image, only the X image is retained in the current whole rotation map list, and the DOM of other images is hidden.

So you have a question, won’t this cause incoherent switching? You don’t have to worry about this, we can add animation to the last rotation and the next rotation to achieve a sense of continuity.

Visualize the code structure for use

The following usage structure is derived from the reference to most of the rotation chart components.

import Carousel,{ Item } from 'components'


render(){
    return (
        <Carousel>
            <Item><img src="xxx" /></Item>
            <Item><img src="xxx" /></Item>
            <Item><img src="xxx" /></Item>
        </Carousel>)}Copy the code

Carousel components

Create a new Carousel component, which is the overall framework file for the component.

Add an inner div to act as the viewport for the current display of the rotation diagram

const Carousel=() = >{
    return (
        <div className={'carousel'} >
            <div className={'carousel-inner'} >{XXX}</div> 
        </div>)}Copy the code

Overflow: hidden is the key

.inner{
    width:100%;
    height:100%;
    position: relative;
    overflow: hidden;
} 
Copy the code

CarouselItem components

Create a new CarouselItem component, which is a child of the Carousel component and is a container for each item in the Carousel list.

const CarouselItem=(props) = >{ 

    return (
        <div className={'carousel-item'} >
            {props.children}
        </div>)}Copy the code

Note that you need to use top0 left0 otherwise the display will be offset from top to top and cause errors

.carousel-item{
    position: absolute;
    left:0;
    top:0;
    width: 100%;
    height:100%; 
}
Copy the code

Improve the component

  1. How do I display the current wheel map element

Earlier we said that the core idea of our rotation diagram is to show the current element, so when do we show the current element?

In the Carousel component, there is a state that indicates the current number of the graph. We call it current here, and according to the order of the elements passed into carousel, we can get the current number of the picture inside the item. Then, if the two are equal, the current picture will be displayed.

How do we get the current index of the element? We can take advantage of the React API for parsing children

// util
import React from "react";

export function injecteIndex(children:React.ElementType,current) :any{
    let result:React.ReactElement[]=[];
    React.Children.forEach(children,(item,index) = >{
        result.push(React.cloneElement((item as any),{
            //selfIndex is the index of the multicast graph
            selfIndex:index,
            key:index,
            current
        }))
    });
    return result;
}
/ / carousel components
// Initial defaults to 0 for the incoming configuration, i.e. the first graph is displayed by default
const Carousel=() = >{

    const {
        initial
    }=props;

    const [current,setCurrent]=useState<number>(initial);

    return (
        <div className={'carousel'} >
            <div className={'carousel-inner'} > 
                {injecteIndex(children,current)}
            </div> 
        </div>)}/ / carousel - item component
const CarouselItem=(props) = >{ 

    const { selfIndex,current }=props;

    const visible=selfIndex===current

    return (
        {visible && <div className={'carousel-item'} >
            {props.children}
        </div>})}Copy the code
  1. To realize the autoPlay

We’ve actually achieved the first picture above, so how do we get it moving? It’s actually pretty simple. Let’s do it together

//useLatest
import { useEffect, useRef } from "react"

export default (params:any)=>{

    const latest=useRef();

    useEffect(() = >{
        latest.current=params;
    })

    return latest;
}
/ / carousel components
const Carousel=(props) = >{

    const {
        // Initial defaults to 0 for the incoming configuration, i.e. the first graph is displayed by default
        initial=0.// Whether to automatically rotate the default value is Yes
        autoplay=true.// The default interval is 3 seconds
        interval=3000
    }=props;

    // Create a timer to store variables
    const timer:any=useRef(null);

    const [current,setCurrent]=useState<number>(initial);

    const [itemLength]=useState<number>(React.Children.count(children)); 

    // Resolve closure issues
    const latest:any=useLatest(current); 

    const autoPlay=() = >{
        // Set the timer to switch to the next image every 3s
        timer.current=setInterval(() = >{
            setStep('next');
        },interval);
    }

    const setStep=(direction:'prev'|'next') = >{
        switch(direction){
            case 'prev':
                let prevStep:number=latest.current-1;
                // If it is the first card, skip to the fifth card
                if(prevStep===-1){
                    prevStep=itemLength-1;
                }
                setCurrent(prevStep);
                break;
            case 'next':
                let nextStep:number=latest.current+1;
                // If it is the last card, jump to the first card
                if(nextStep===itemLength){
                    nextStep=0;
                }
                setCurrent(nextStep);
                break;
            default:

        }
    }

    useEffect(() = >{
        if(autoplay){
            // automatic rotation
            autoPlay();
        }
        return () = >{
            // Destroy the timer
            clearInterval(timer.current);
            timer.current=null;
        }
    },[autoplay]);

    return (
        <div className={'carousel'} >
            <div className={'carousel-inner'} > 
                {injecteIndex(children,current)}
            </div> 
        </div>)}` `In fact, we have already completed the seamless rotation seeding business logic, so how to let him round seeding? We're using a transition library recommended by React because React doesn't provide us with the Transition API that Vue does. `` `js
const CarouselItem=(props) = >{ 

    const {  
        children,
        selfIndex 
    }=props; 

    const visible=selfIndex===current; 

    return  (
        <CSSTransition mountOnEnter unmountOnExit appear in={visible} timeout={500}  classNames={'carousel'} >
            <div className={'carousel-item'} >
                {children}
            </div>
        </CSSTransition>)});Copy the code
.carousel-enter-active..carousel-exit-active{
        transition: all 500ms linear;
}
.carousel-enter{ 
        transform: translateX(100%);
        opacity: 0.8;
}
.carousel-enter-active{
        transform: translateX(0);
        opacity: 1;
}
.carousel-exit{
        transform: translateX(0); 
        opacity: 1;
}
.carousel-exit-active{
        transform: translateX(-100%); 
        opacity: 0.8;
}
Copy the code

Top :0 left:0 indicates that the origin of the rotation element is the top left corner of the container. During the Enter process, the translateX is animated from right to left. Before exit, the translateX is animated from left to right from 0 to -100

Finish the dot

In fact, the biggest difficulty for me to write a rotation chart is the position of the dots, because most of the rotation chart dots are written inside the inner element and the inner element is overflow:hidden, that is, no matter what the dots are, they will overflow and hide. So this time we put the dot on the inner element and provide an attribute to make the dot adjustable.

//carousel 
<div className={classes}>
    <div className={'inner'} >
        {injecteIndex(children)}
    </div>
    <Dots length={itemLength}  position={dotPosition}/>
</div>
/ / dots at the origin
<div className={classnames(
            classes)},style={{... position}}>
            {
                newArray.map((item,index)=>(
                    <div className={classnames(` ${prefixCls}-item`,{
                        'active':current= = =index
                    })} key={index} onClick={()= >onClick(index)}/>
                ))
            }
</div>
Copy the code

Dude thanks 🙏

Finally I sent the code to dude, and got dude’s thanks, I was already very satisfied hahaha

Finally, dude achieves the effect

The source address

If you want to use it, you can rest assured that the source code has been sent to Github and NPM above

npm install @parrotjs/carousel -S
Copy the code