一摘能、走馬燈的作用
走馬燈是一種常見(jiàn)的網(wǎng)頁(yè)交互組件际跪,可以展示多張圖片或者內(nèi)容怜姿,通過(guò)自動(dòng)播放或者手動(dòng)切換的方式,讓用戶能夠方便地瀏覽多張圖片或者內(nèi)容砌创。
本次實(shí)現(xiàn)的不是輪播圖而是像傳送帶一樣的無(wú)限滾動(dòng)的形式虏缸。
二、需求梳理
走馬燈可設(shè)置一下屬性:
- 滾動(dòng)速度
- 滾動(dòng)方向
- 一屏要顯示項(xiàng)的個(gè)數(shù)
- 容器的寬度
- 要展示的數(shù)據(jù)
- 自定義展示項(xiàng)
三嫩实、實(shí)現(xiàn)思路
3.1 首先確定一下我們的dom元素
wrap>list>item*n
- 最外層wrap用于限制顯示區(qū)域的寬度刽辙,超過(guò)寬度就隱藏。
- list 用于滾動(dòng)顯示數(shù)據(jù)甲献,所以我們的動(dòng)畫(huà)加在這個(gè)元素上宰缤。
- item 用于放置展示項(xiàng)。
3.2 實(shí)現(xiàn)無(wú)限滾動(dòng)的動(dòng)畫(huà)
我們用keyframes關(guān)鍵幀動(dòng)畫(huà)來(lái)做晃洒。
但是要滾動(dòng)多少距離才能實(shí)現(xiàn)無(wú)限滾動(dòng)呢慨灭?
1.計(jì)算動(dòng)畫(huà)滾動(dòng)距離
從上面的圖中我們可以看到當(dāng)list的寬度<wrap的寬度(containerWidth)時(shí),會(huì)出現(xiàn)滾動(dòng)后出現(xiàn)空白的情況球及。那么第二張圖氧骤,list的寬度>=wrap的兩倍,就能在向左滾動(dòng)完list的一半后桶略,不會(huì)出現(xiàn)空白语淘,而且為了給人一種無(wú)限滾動(dòng)的效果圃伶,list的前后兩部分?jǐn)?shù)據(jù)要保持一致啃奴。
所以滾動(dòng)的距離 = 展示數(shù)據(jù)的個(gè)數(shù) * 每項(xiàng)的寬度,而為了無(wú)限滾動(dòng)效果,我們還需要對(duì)原始數(shù)據(jù)進(jìn)行處理烫饼。
分為以下幾種情況:
- 數(shù)據(jù)個(gè)數(shù)>= 一屏展示個(gè)數(shù)(showNum)
此時(shí)重復(fù)兩次原始數(shù)據(jù)就能得到滾動(dòng)數(shù)據(jù)
- 數(shù)據(jù)個(gè)數(shù)< 一屏展示個(gè)數(shù)
首先我們要保證沒(méi)有空白,那要如何填充呢互婿?只填充到=showNum,行不行呢饱搏?
我們可以看一下:
比如說(shuō)原始數(shù)據(jù)為[1,2,3],填充完再進(jìn)行重復(fù)則為 [1,2,3,1,1,2,3,1]颅筋,這樣會(huì)出現(xiàn)1這一項(xiàng)連續(xù)出現(xiàn)了宙暇。
所以最好的方式是直接填充原始數(shù)據(jù)直到>=showNum,所以最終我們得到的滾動(dòng)數(shù)據(jù)是[1,2,3,1,2,3 ,1,2,3,1,2,3]
2.插入動(dòng)畫(huà)
因?yàn)槲覀兊膭?dòng)畫(huà)是根據(jù)傳入的變量得來(lái)的议泵,所以不能直接寫(xiě)在樣式文件里占贫,我們通過(guò)在useEffect里插入樣式表對(duì)象的方式來(lái)實(shí)現(xiàn)。
四先口、完整代碼
組件代碼
import { ReactElement, useEffect } from "react";
import * as React from "react";
import "./index.less";
import { ItemProps } from "./demo";
interface Props {
Item: (item: ItemProps) => ReactElement;
showNum: number;
speed: number;
containerWidth: number;
data: Array<any>;
hoverStop?: boolean;
direction?: "left" | "right";
}
const fillArray = (arr: any[], length: number): any[] => {
const result: any[] = [];
while (result.length < length) {
result.push(...arr);
}
return result.concat(result);
};
function AutoplayCarousel({
Item,
showNum,
speed,
containerWidth,
data,
hoverStop = false,
direction = "left"
}: Props) {
const showData = fillArray(data, showNum);
const length = showData.length;
const itemWidth = containerWidth / showNum;
useEffect(() => {
// 創(chuàng)建一個(gè)新的樣式表對(duì)象
const style = document.createElement("style");
// 定義樣式表的內(nèi)容
let start = "0";
let end = `-${(itemWidth * length) / 2}`;
if (direction === "right") {
start = end;
end = "0";
}
style.innerText = `
@keyframes templates-partner-moving {
0% {
transform: translateX(${start}px);
}
100% {
transform: translateX(${end}px);
}
}
`;
if (hoverStop) {
style.innerText += `.list:hover {
/*鼠標(biāo)經(jīng)過(guò)后型奥,動(dòng)畫(huà)暫停*/
animation-play-state: paused !important;
}`;
}
// 將樣式表插入到文檔頭部
document.head.appendChild(style);
// 組件卸載時(shí)清除樣式表
return () => document.head.removeChild(style) as any;
}, []);
return (
<div style={{ width: `${containerWidth}px` }} className="wrap">
<div
className="list"
style={{
width: `${itemWidth * length}px`,
animation: `templates-partner-moving ${
(length / showNum / 2) * speed
}s infinite linear`
}}
>
{showData.map((item) => (
<div style={{ width: `${itemWidth}px` }}>
<Item {...item} />
</div>
))}
</div>
</div>
);
}
export default AutoplayCarousel;
demo代碼
import React from "react";
import AutoplayCarousel from "./index";
const data = new Array(5).fill(0).map((item, index) => {
return { num: index };
});
console.log("data", data);
export interface ItemProps {
num: number;
}
const itemStyle = {
border: "1px solid #ccc",
background: "#fff",
height: "50px",
color: "red",
marginRight: "15px"
};
function Demo() {
const Item = (item: ItemProps) => {
return <div style={itemStyle}>{item.num}</div>;
};
return (
<AutoplayCarousel
Item={Item}
containerWidth={500}
showNum={5}
speed={8}
data={data}
/>
);
}
export default Demo;
樣式代碼
* {
margin: 0;
padding: 0;
}
.wrap {
overflow: hidden;
.list {
position: relative;
top: 0px;
left: 0px;
height: 100%;
display: flex;
}
}