一、場景描述
在很多網站的頁面中都有輪播圖,所以我想利用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 & setIntervalvar testTimeFunction = function () {setTimeout(() => {setInterval(() => {count += 1;console.log("count輸出了" + count + "次");}, 3000);}, 3000);};testTimeFunction();
運行結果如下圖所示:
3.2 react中的ref,利用ref獲取dom元素,并對dom元素的class進行操作
import React, { useState, useRef } from "react”;const divRef = useRef<HTMLDivElement>(null);
//tsx部分
<div ref={divRef}><buttononClick={() => {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。
//跳轉到上一張圖的按鈕事件。
<buttonclassName="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,};});}}
>
//跳轉到下一張圖的按鈕事件。
<buttonclassName="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的變化,只要變化就停止計時器,然后重新開啟。 — 這里我用了異步
//開啟計時器,設置preIndex是當前index的前一個index(index-1),nextIndex是index+1const 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進行重新渲染。
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.
<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
<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 &¤tIndex.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": ""
}`}
>
五、遇到的問題
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的值都變成NaNsetTimeout(()=>{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