【前端】react+ts 轮播图的实现

news/2025/2/26 10:14:56

一、场景描述

在很多网站的页面中都有轮播图,所以我想利用react.js和ts实现一个轮播图。自动轮播图已经在前面实现过了,如:https://blog.csdn.net/weixin_43872912/article/details/145622444?sharetype=blogdetail&sharerId=145622444&sharerefer=PC&sharesource=weixin_43872912&spm=1011.2480.3001.8118。
思维导图可以在网站:https://download.csdn.net/download/weixin_43872912/90429355?spm=1001.2014.3001.5503下载高清原图。
在这里插入图片描述

二、问题拆解

轮播图(如下图轮播图所示)的实现可以分为三个:
第一个是自动轮播,就是每过一定的时间自动变成下一张图;
第二个是前后按钮的轮播,就是按左右的按钮,按左边的按钮时,跳转到前一张图,按右边的按钮时,跳转到后一张图。
第三个就是按底部的按钮切换轮播图。
在这里插入图片描述
在这里插入图片描述

三、相关知识

3.1 setTimeout与setInterval的用法与详解

setTimeout是指过了多久之后执行内部代码,一次性的;setInterval是每过多久就要执行一次代码。setTimeout里面开启计时器的话,就需要先过setTimeout的时间,然后过setInterval的一次时间才会运行第一次。

    var time = 0;
    setInterval(() => {
      time += 1;
      console.log("当前时间为" + time + "秒");
    }, 1000);
    let count = 0;
    //test setTimeout & setInterval
    var testTimeFunction = function () {
      setTimeout(() => {
        setInterval(() => {
          count += 1;
          console.log("count输出了" + count + "次");
        }, 3000);
      }, 3000);
    };
    testTimeFunction();

运行结果如下图所示:
在这里插入图片描述

3.2 react中的ref,利用ref获取dom元素,并对dom元素的class进行操作

typescript">import React, { useState, useRef } from "react”;

const divRef = useRef<HTMLDivElement>(null);
//tsx部分
<div ref={divRef}>
  <button
    onClick={() => {
      console.log(divRef);
      let target = divRef.current as HTMLElement;  //ts里面要将其定义为htmlElement再操作
      target.classList.add("preActive");
      console.log(target);
    }}
  >打印ref
  </button>
</div>

四、轮播图的按钮部分实现

4.1 上一张和下一张的按钮实现

在这里插入图片描述
上一张和下一张的按钮部分其实就是自动轮播部分的手动操作,这部分的内容关注一下自动轮播部分和手动轮播部分的联动就可以了。
按钮事件:
跳转的按钮事件, 可以看到其实就是设置了一下currentIndex,这是一个state,更新之后会引起组件渲染,然后就会更新dom元素的class。

typescript">//跳转到上一张图的按钮事件。
<button
  className="carousel-control-prev"
  type="button"
  data-bs-target="#carouselExample"
  onClick={() => {
    //clearCrouselClass();
    setCurrentIndex((pre) => {
      let nowCurrentIndex =
        currentIndex.currentIndex - 1 === -1
          ? img.length - 1
          : currentIndex.currentIndex - 1;
      let nowPreIndex =
        nowCurrentIndex - 1 === -1
          ? img.length - 1
          : nowCurrentIndex - 1;
      let nownextIndex =
        nowCurrentIndex + 1 === img.length ? 0 : nowCurrentIndex + 1;
      return {
        preIndex: nowPreIndex,
        currentIndex: nowCurrentIndex,
        nextIndex: nownextIndex,
      };
    });
  }}
>
//跳转到下一张图的按钮事件。
<button
  className="carousel-control-next"
  type="button"
  data-bs-target="#carouselExample"
  onClick={() => {
    //clearCrouselClass();
    setCurrentIndex((pre) => {
      let nowCurrentIndex =
        currentIndex.currentIndex + 1 === img.length
          ? 0
          : currentIndex.currentIndex + 1;
      let nowPreIndex =
        nowCurrentIndex - 1 === -1
          ? img.length - 1
          : nowCurrentIndex - 1;
      let nownextIndex =
        nowCurrentIndex + 1 === img.length ? 0 : nowCurrentIndex + 1;
      return {
        preIndex: nowPreIndex,
        currentIndex: nowCurrentIndex,
        nextIndex: nownextIndex,
      };
    });
  }}
>

手动换图与自动轮播图之间的不和谐在于,手动换图后自动轮播还在执行会导致换两次可能,所以手动换图的时候需要停止自动轮播,结束后开启。
换图每次都会伴随着cunrrentIndex的变动,所以在这里我们用useEffect去检测cunrrentIndex的变化,只要变化就停止计时器,然后重新开启。 — 这里我用了异步

typescript"> //开启计时器,设置preIndex是当前index的前一个index(index-1),nextIndex是index+1
  const startAutoplay = async () => {
    interval.current = setInterval(() => {
      setCurrentIndex((preCurrentIndex) => {
        return {
          preIndex:
            ((preCurrentIndex.currentIndex + 1) % img.length) - 1 === -1
              ? img.length - 1
              : ((preCurrentIndex.currentIndex + 1) % img.length) - 1,
          currentIndex: (preCurrentIndex.currentIndex + 1) % img.length,
          nextIndex:
            ((preCurrentIndex.currentIndex + 1) % img.length) + 1 === img.length
              ? 0
              : ((preCurrentIndex.currentIndex + 1) % img.length) + 1,
        };
      });
    }, 3000);
  };
 
  const stopAutoPlay = () => {
    if (interval.current !== null) {
      clearInterval(interval.current as NodeJS.Timeout);
      interval.current = null;
    }
  };  

  useEffect(() => {
    clearInterval(interval.current as NodeJS.Timeout);
    interval.current = null;
    let target = imgRef.current as HTMLElement;
    console.log(target.childNodes);
    if (interval.current !== null) {
      stopAutoPlay();
    }
 
    if (interval.current === null) {   //避免因为定时器什么的缘故导致计时器多开
      startAutoplay();
    }
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentIndex]);

4.2 底部小圆点按钮

这块部分就是将点击按钮所对应的图片转到主页。这个要实现用setCurrentIndex()就可以,但是要加动画,首先需要将目标页先移到上一页或者下一页的位置,准备平移至主页位置。所以,需要预先将目标图移到动画的起始位置。
在这里插入图片描述
如上图所示,如果是目标页是当前页的前面几页(目标页的index比currentIndex小)。需要提前把目标页移到上一页的位置,反之,移到下一页的位置。
移完之后设置currentIndex进行重新渲染。

typescript">if (index < currentIndex.currentIndex) {
  target.classList.add("preActive”);        //移到上一页的位置,class设为preActive
  // target1.classList.add("nextActive");
  // target1.classList.remove("active");
  setTimeout(() => {
    setCurrentIndex((pre) => {
      console.log(currentIndex);
      // let nextIndex = index + 1 === img.length ? 0 : index + 1;
      // if(nextIndex === pre.currentIndex) return;
      return {
        preIndex:
          index - 1 === -1 ? img.length - 1 : index - 1,
        currentIndex: index,
        nextIndex: pre.currentIndex,
      };
    });
  }, 10);
} else if (index > currentIndex.currentIndex) {
  target.classList.add("nextActive”);     //移到下一页的位置,class设为nextActive
  // target1.classList.add("preActive");
  // target1.classList.remove("active");
  console.log(currentIndex);
  setTimeout(() => {
    setCurrentIndex((pre) => {
      return {
        nextIndex:
          index + 1 === img.length ? 0 : index + 1,
        currentIndex: index,
        preIndex: pre.currentIndex,
      };
    });
  }, 10);
}

到此为止出现的问题:
和“自动轮播和前后按钮换图”之前的联动出现的错误是:当跳到前面页面的时候,下一页的类名没有nextActive所以跳到下一页可能没有动画;相同的跳到前面页的时候,上一页的类名没有preActive,可能没有翻页动画。因此,我们要保证当前页的前一页类名有preActive,后一页类名有nextActive.

typescript"><div
key={index}
className={`carousel-item ${
  index === currentIndex.currentIndex ? "active" : ""
}${
  index ===
    (currentIndex.currentIndex - 1 === -1
      ? img.length - 1
      : currentIndex.currentIndex - 1) ||
  (index === currentIndex.preIndex )
    ? "preActive"
    : ""
} ${
  index ===
    (currentIndex.currentIndex + 1 === img.length
      ? 0
      : currentIndex.currentIndex + 1) ||
  index === currentIndex.nextIndex
    ? "nextActive"
    : ""
}`}
>

这样写存在的错误是会造成同一个图片有preActive和nextActive的情况
在这里插入图片描述
解决方案:按键跳转前的主页是目标页的下一页:因此,当preIndex等于currentIndex+1不给preIndex

typescript"><div
key={index}
className={`carousel-item ${
  index === currentIndex.currentIndex ? "active" : ""
}${
  index ===
    (currentIndex.currentIndex - 1 === -1
      ? img.length - 1
      : currentIndex.currentIndex - 1) ||
  //下面这部分代码就是 当preIndex等于currentIndex+1不给preIndex
  (index === currentIndex.preIndex &&
    currentIndex.preIndex !==
      (currentIndex.currentIndex + 1 === img.length
        ? 0
        : currentIndex.currentIndex + 1))
    ? "preActive"
    : ""
} ${
  index ===
    (currentIndex.currentIndex + 1 === img.length
      ? 0
      : currentIndex.currentIndex + 1) ||
  index === currentIndex.nextIndex
    ? "nextActive"
    : ""
}`}
>

在这里插入图片描述
五、遇到的问题

typescript">    useEffect(() => {
      console.log("停止计时器");
      console.log(currentIndex);
      clearInterval(interval.current as NodeJS.Timeout);
      interval.current = null;
      let target = imgRef.current as HTMLElement;
      console.log(target.childNodes);
      if (interval.current !== null) {
        stopAutoPlay();
      }
      //如果这里这样写会出现问题,如果在这三秒内点的话,就会导致setCurrentIndex的值都变成NaN
      setTimeout(()=>{
          if (interval.current === null) {
          startAutoplay();
          }
      },3000)
      //eslint-disable-next-line react-hooks/exhaustive-deps
    }, [currentIndex]);

//改完之后的版本
  useEffect(() => {
    console.log("停止计时器");
    console.log(currentIndex);
    clearInterval(interval.current as NodeJS.Timeout);
    interval.current = null;
    let target = imgRef.current as HTMLElement;
    console.log(target.childNodes);
    if (interval.current !== null) {
      stopAutoPlay();
    }
//改成异步函数,或者setTimeout的事件变短一点
 
    if (interval.current === null) {
      startAutoplay();
    }
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentIndex]);

整个项目可以在下面链接下载:https://download.csdn.net/download/weixin_43872912/90429357?spm=1001.2014.3001.5501


http://www.niftyadmin.cn/n/5868535.html

相关文章

SQL进阶实战技巧:汽车转向次数分析 | 真实场景案例

目录 0 问题描述 1 数据准备 2 问题分析 3 小结 关键技术总结 0 问题描述 现有一组实际汽车在平整路面安全行驶数据,每秒记录一次汽车的车头绝对指向,车头方向记为[0-360)度,部分数据如下,完整数据后附文件。

Docker基础-常见命令

docker images -查看所有的本地镜像。 docker pull -把远端镜像拉取到本地。 docker rmi -删除镜像。 docker push -推到镜像仓库。 docker run -创建并运行容器&#xff08;自动化&#xff0c;如果发现镜像不存在会先去拉取&#xff0c; 拉取完了以后再去自动创建容器&am…

langchain-go调用deepseek

1.查看官网 发现只有ollama,openai,Mistral于是查看代码 2.代码查看 先从llm, err : openai.New(url, model, token)开始 发现New方法可以传option参数&#xff0c;再看一下option参数 const (tokenEnvVarName "OPENAI_API_KEY" //nolint:gosecmodelE…

HITCON2017SSRFME-学习复盘

代码审计 192.168.122.15 <?phpif (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {$http_x_headers explode(,, $_SERVER[HTTP_X_FORWARDED_FOR]);//用逗号分割多个IP$_SERVER[REMOTE_ADDR] $http_x_headers[0];}echo $_SERVER["REMOTE_ADDR"];//给第一个IP发送请…

Linux主机用户登陆安全配置

Linux主机用户登陆安全配置 在Linux主机上进行用户登录安全配置是一个重要的安全措施&#xff0c;可以防止未经授权的访问。以下是如何创建用户hbu、赋予其sudo权限&#xff0c;以及禁止root用户SSH登录&#xff0c;以及通过ssh key管理主机用户登陆。 创建用户hbu 使用具有…

从零到一:如何用阿里云百炼和火山引擎搭建专属 AI 助手(DeepSeek)?

本文首发&#xff1a;从零到一&#xff1a;如何用阿里云百炼和火山引擎搭建专属 AI 助手&#xff08;DeepSeek&#xff09;&#xff1f; 阿里云百炼和火山引擎都推出了免费的 DeepSeek 模型体验额度&#xff0c;今天我和大家一起搭建一个本地的专属 AI 助手。  阿里云百炼为 …

单链表删除算法(p=L; j=0;与p=p->next;j=1的辨析)

算法描述 Status ListDelete&#xff08;LinkList &L,int i&#xff09; { //在带头结点的单链表 L 中&#xff0c;删除第 i 个元素 pL; j0; while ((p->next) && (j<i-1)) {pp->next; j;} if (!(p->next)||(j>i-1)) return ERROR; qp->nex…

【Docker基础】理解 Docker:本质、性质、架构与核心组件

文章目录 Docker 本质Docker 的引擎迭代Docker 和虚拟机的区别Docker 为什么比虚拟机资源利用率高&#xff0c;速度快&#xff1f;Docker 和 JVM 虚拟化的区别Docker 版本1. LXC (Linux Containers)2. libcontainer3. Moby4. docker-ce5. docker-ee总结&#xff1a; Docker 架构…