遇到一個效果,組件庫里沒有現成能用的組件,于是手搓了一個,傳入圖片url列表,和其他配置項即可直接使用。
輪播效果
實現思路
假設共有10張圖片輪流滾動,輪播圖展示3張圖片。給正在輪播的圖片綁定visible
類,輪播過的圖片綁定left
類,待輪播的下一個圖片綁定right
。其中,綁定了visible
類的圖片以此從左往右排開,綁定了left
類的在左側并且隱藏,綁定了right
的在右側并隱藏(分左右是為了實現在右側出現、在左側消失的動畫效果)。
.visible {transform: translateX(calc(var(--index) * @image-width * (1 - var(--overlap))));z-index: calc(var(--index) + 1);opacity: 1;}.left {transform: translateX(-30px);z-index: 1;opacity: 0;}.right {transform: translateX(calc(var(--number) * @image-width * (1 - var(--overlap))));z-index: 1;opacity: 0;}
初始時,給10張圖片分別綁定上不同的類,每次將這些類依次向后調整一格,最后的類挪到最前面。只有綁定了visible類的才會顯示。
圖片 | 圖片1 | 圖片2 | 圖片3 | 圖片4 | 圖片5 | 圖片6 | … | 圖片10 |
---|---|---|---|---|---|---|---|---|
初始時 | visible | visible | visible | right | left | left | … | left |
第二 | left | visible | visible | visible | right | left | … | left |
第三 | left | left | visible | visible | visible | right | … | left |
代碼
核心代碼
新建一個組件,編寫tsx代碼
import { useCallback, useEffect, useRef, useState } from 'react';
import './index.less';interface ImageBannerProps {imagesData: string[];visibleNumber?: number; // 可以看見的數量overlapRate?: number; // 重疊率,默認值0.6interval?: number; // 間隔時間imageSize?: string; // 尺寸
}const ImageBanner: React.FC<ImageBannerProps> = (props) => {const { visibleNumber = 3, overlapRate = 0.3, interval = 1000, imageSize = '40px' } = props;let { imagesData } = props;// 復制到長度>=5,輪播組件最小長度要求while (imagesData.length && imagesData.length < 5) {imagesData = imagesData.concat(imagesData);}let initClassList = Array.from({ length: visibleNumber - 1 },(_, index) => `visible-${index + 1}`,);initClassList = [...initClassList, 'right', 'visible-0'];// 例如當visibleNumber=3時,初始值為["visible-1", "visible-2", "right", "visible-0"]const [classList, setClassList] = useState<string[]>(initClassList); // 每個圖片的class,通過改變class更改樣式const imgListRef = useRef<HTMLDivElement>(null);const initialize = useCallback((classList: string[]) => {if (imgListRef.current) {const imgList = Array.from(imgListRef.current.children);for (let i = 0; i < imgList.length; i++) {if (classList[i].includes('visible')) {const match = classList[i].match(/visible-(\d+)/); // 提取數字imgList[i].className = 'imgBannerItem visible';if (match) {(imgList[i] as HTMLDivElement).style.setProperty('--index', match[1]);}} else {imgList[i].className = 'imgBannerItem ' + classList[i];}}}}, []);const next = useCallback(() => {setClassList((prev) => {const newClassList = [...prev];newClassList.unshift(newClassList.pop()!);initialize(newClassList);return newClassList;});}, [initialize]);useEffect(() => {if (imagesData.length < 1) {return;}let timer;if (imgListRef.current) {const imgList = imgListRef.current.children;for (let i = 0; i < visibleNumber - 1; i++) {imgList[i].className = 'imgBannerItem ' + classList[i];}imgList[visibleNumber].className = 'imgBannerItem ' + classList[visibleNumber];const fillLeft = Array(imagesData.length - visibleNumber - 1).fill('left'); // 填入class為leftconst newClassList = [...classList.slice(0, visibleNumber),...fillLeft,...classList.slice(visibleNumber),];setClassList(newClassList);initialize(newClassList);timer = setInterval(next, interval);}return () => {if (timer) {clearInterval(timer);}};}, []);if (imagesData.length < 1) {return null;}return (<divclassName="imgBannerContainer"ref={imgListRef}style={{'--number': visibleNumber,'--overlap': overlapRate,'--size': imageSize,} as React.CSSProperties}>{imagesData.map((item, idx) => (<img className="imgBannerItem" src={item} alt="" key={idx} />))}</div>);
};export default ImageBanner;
樣式代碼
less代碼
@image-width: var(--size);
@image-height: var(--size);.imgBannerContainer {width: 100%;height: 100%;position: relative;.visible {transform: translateX(calc(var(--index) * @image-width * (1 - var(--overlap))));z-index: calc(var(--index) + 1);opacity: 1;}.left {transform: translateX(-30px);z-index: 1;opacity: 0;}.right {transform: translateX(calc(var(--number) * @image-width * (1 - var(--overlap))));z-index: 1;opacity: 0;}
}.imgBannerItem {width: @image-width;height: @image-height;position: absolute;top: 0;transition: 0.3s;border-radius: 50%;border: 2px solid #fff;
}
使用
引入組件,傳入圖片列表進行使用
import ImageBanner from "../ImageBanner";const urlList = ["url1","url2","url3","url4","url5",
];const Index = () => {return ( <><div style={{width: 300, height: 40, marginLeft: 20}}><ImageBanner imagesData={urlList} /></div></>);
}export default Index;