React实现一个简易版Swiper( 四 )

截止到这里,其实简易的自动轮播就完成了,但是其实很多同学也会有疑问,是不是还缺少分页器(Pagination) 。
5、分页器(Pagination)分页器的原理其实很简单,我们可以分成两个步骤来看 。1、渲染与图片相同个数的节点;2、根据activeIndex动态改变分页样式 。
import React, { FC } from "react";import styled from "styled-components";const SwiperSlideBar = styled.div`  margin-top: 16px;  width: 100%;  height: 4px;  display: flex;  align-items: center;  justify-content: center;`;const SwiperSlideBarItem: any = styled.div`  cursor: pointer;  width: ${(props: any) => (props.isActive ? "26px" : "16px")};  height: 4px;  background: #e6e6e6;  margin-right: 6px;`;const SlideBarInner: any = styled.div`  width: 100%;  height: 100%;  background: #0075ff;  animation: ${innerFrame} ${(props: any) => `${props.speed}s`} ease;`;{urls?.length > 1 ? (  <SwiperSlideBar>    {urls?.map((f: string, index: number) => (      <SwiperSlideBarItem        onClick={() => slideToOne(index)}        isActive={index === activeIndex}      >        {index === activeIndex ? <SlideBarInner speed={speed} /> : null}      </SwiperSlideBarItem>    ))}  </SwiperSlideBar>) : null}细心的同学可能看到我在这里为什么还有一个SlideBarInner元素,其实是在这里实现了一个当前所在分页停留时间进度条展示的功能,感兴趣的同学可以自己看一下,我这里就不在赘述了 。
6、整体实现代码最后,我们可以看到完整的Swiper代码如下:
import React, { FC, useEffect, useRef, useState } from "react";import styled, { keyframes } from "styled-components";const innerFrame = keyframes`  from {    width: 0%;  }  to {    width: 100%;  }`;const Swiper = styled.div`  overflow: hidden;  position: relative;`;const SwiperNextTip = styled.div`  position: absolute;  top: 50%;  transform: translateY(-50%);  right: 24px;  width: 32px;  height: 32px;  border-radius: 50%;  background: #ffffff70;  display: flex;  justify-content: center;  align-items: center;  cursor: pointer;  opacity: 0.7;  user-select: none;  :hover {    opacity: 1;    background: #ffffff80;  }`;const SwiperPrevTip = (styled as any)(SwiperNextTip)`  left: 24px;`;const SwiperContainer = styled.div`  position: relative;  display: flex;  align-item: center;  justify-content: flex-start;  transition: all 0.3s ease;  -webkit-transition: all 0.3s ease;  -moz-transition: all 0.3s ease;  -o-transition: all 0.3s ease;`;const SwiperSlide = styled.div`  display: flex;  align-item: center;  justify-content: center;  flex-shrink: 0;`;const SwiperSlideBar = styled.div`  margin-top: 16px;  width: 100%;  height: 4px;  display: flex;  align-items: center;  justify-content: center;`;const SwiperSlideBarItem: any = styled.div`  cursor: pointer;  width: ${(props: any) => (props.isActive ? "26px" : "16px")};  height: 4px;  background: #e6e6e6;  margin-right: 6px;`;const SlideBarInner: any = styled.div`  width: 100%;  height: 100%;  background: #0075ff;  animation: ${innerFrame} ${(props: any) => `${props.speed}s`} ease;`;const Swiper: FC<  {    direction?: 'horizontal' | 'vertical';    speed?: number;    width: string;    height: string;    urls: string[];  }> = ({  direction = "horizontal",  speed = 3,  width = "",  height = "",  urls = []}) => {  const [activeIndex, setActiveIndex] = useState<number>(0);  const [isDone, setDone] = useState<boolean>(false);  const [swiperStyle, setSwiperStyle] = useState<{    width: string;    height: string;  }>({    width: (width as string)?.replace("%", "vw"),    height: (height as string)?.replace("%", "vh"),  } as any);  const timer = useRef<any>(null);  const swiperContainerRef = useRef<HTMLDivElement>(null);  const styles = {    width: isNaN(+swiperStyle.width)      ? swiperStyle!.width      : `${swiperStyle!.width}px`,    height: isNaN(+swiperStyle.height)      ? swiperStyle.height      : `${swiperStyle.height}px`,  };  const startPlaySwiper = () => {    if (speed <= 0) return;    timer.current = setInterval(() => {      setActiveIndex((preValue) => preValue + 1);    }, speed * 1000);  };  const slideToOne = (index: number) => {    if (index === activeIndex) return;    setActiveIndex(index);    clearInterval(timer?.current);    startPlaySwiper();  };  useEffect(() => {    if (swiperContainerRef?.current) {      startPlaySwiper();    }    return () => {      clearInterval(timer?.current);      timer.current = null;    };  }, [swiperContainerRef?.current]);  useEffect(() => {    const swiper = document.querySelector("#swiper-container") as any;    if (direction === "vertical") {      swiper.style.bottom = (height as string)?.includes("%")        ? `${activeIndex * +(height as string)?.replace("%", "")}vh`        : `${activeIndex * +height}px`;    } else {      swiper.style.right = (width as string)?.includes("%")        ? `${activeIndex * +(width as string)?.replace("%", "")}vw`        : `${activeIndex * +width}px`;    }    if (activeIndex >= urls.length - 1) {      clearInterval(timer?.current);      timer.current = null;      setDone(true);    }  }, [activeIndex, urls]);  return (<>      <Swiper style={{ width, height }}>        <SwiperContainer          id="swiper-container"          ref={swiperContainerRef}          style={{            height,            // 根据轮播方向参数,调整flex布局方向            flexDirection: direction === "horizontal" ? "row" : "column",          }}        >         {urls.map((f: string, index: number) => (            <SwiperSlide style={{ ...styles }}>              <img src=https://www.huyubaike.com/biancheng/{f} style={{ ...styles }} />

经验总结扩展阅读