自定义轮播图Carousel组件
需求:要求0-1自定义轮播图组件,默认自动翻页,无限翻页,允许点击翻页,底部有动画进度条,且可配置轮播时间,可以操作Children.
架构:
React: ^18.0. Hooks. Css. FunctionComponent
React : ^18.0 //未使用其他第三方库
Hooks //进行状态管理 模块化设计
Css //进行样式分离,xxx.module.css. 局部作用域样式。防止Css全局污染
函数式编程.
需求解析:
可点击 + 可定义轮博时间 + 可无限轮播项 + 动画进度条 + 可配置轮播图单项内容 + 业务定制化
可点击:允许用户点击底部进度条进行对应索引翻页
可定义轮播时间:当前默认为3秒,可以根据业务来调节时间 3000ms = 3s
可无限轮播项:useEffect 进行监听 并进行相应的操作 实现无线轮播
动画进度条:底色 #0000001a 黑色+10%的透明度 固定宽度,动画颜色为 #FFFFFF 动态宽度
.css{
animation-name: progressBar; // 指定要绑定到选择器的关键帧的名称 name = progressBar
animation-fill-mode: forwards; // 指定动画在执行时间之外应用的值 forwards = 保留最后一个关键帧设置的样式值
animation-iteration-count: infinite; // 指定动画播放的次数 infinite = 播放无限次
animation-duration: 3s // 一个周期所需的时间长度 3s = 3秒
}
可配置轮播图单项内容:React.Children.map 和 React.cloneElement
需求解决:
一、import Carousel, { CarouselItem, CarouselInfo } from “./Carousel”;
import React, { useState, useEffect } from "react";
import style from "./carousel.module.css";
/**
* @param {children} children ReactNode
* @param {width} width 宽度
* @param {height} height 高度
* @param {styles} styles 样式
* @returns 轮播图 单项
*/
export const CarouselItem = ({
children = React.createElement("div"),
width = "100%",
height = "100%",
styles = {},
}) => {
return (
<div
className={style.carousel_item}
style={{ width: width, height: height, ...styles }}
>
{children}
</div>
);
};
/**
* @param {title} title 标题
* @param {describe} describe 描述
* @param {image} image 图片
* @returns 轮播图 主体
*/
export const CarouselInfo = ({ title = "", describe = "", image = "" }) => {
return (
<div className="carousel_info_container">
<div className="carousel_info_info">
<h1>{title}</h1>
<span>{describe}</span>
</div>
<div className="carousel_info_image_container">
<img src={image} alt="Jay" className="carousel_info_image" />
</div>
</div>
);
};
/**
* @param {children} children ReactNode
* @param {switchingTime} switchingTime 间隔时间 默认3秒 以毫秒为单位 3000ms = 3s
* @returns 轮播图 容器
*/
const Carousel = ({
children = React.createElement("div"),
switchingTime = 3000,
}) => {
const time = ((switchingTime % 60000) / 1000).toFixed(0); // 将毫秒转换为秒
const [activeIndex, setActiveIndex] = useState(0); // 对应索引
/**
* 更新索引
* @param {newIndex} newIndex 更新索引
*/
const onUpdateIndex = (newIndex) => {
if (newIndex < 0) {
newIndex = React.Children.count(children) - 1;
} else if (newIndex >= React.Children.count(children)) {
newIndex = 0;
}
setActiveIndex(newIndex);
replayAnimations();
};
/**
* 重置动画
*/
const replayAnimations = () => {
document.getAnimations().forEach((anim) => {
anim.cancel();
anim.play();
});
};
/**
* 底部加载条点击事件
* @param {index} index 跳转索引
*/
const onClickCarouselIndex = (index) => {
onUpdateIndex(index);
replayAnimations();
};
useEffect(() => {
const interval = setInterval(() => {
onUpdateIndex(activeIndex + 1);
}, switchingTime);
return () => {
if (interval) {
clearInterval(interval);
}
};
});
return (
<div className={style.container}>
<div
className={style.inner}
style={{ transform: `translateX(-${activeIndex * 100}%)` }}
>
{React.Children.map(children, (child) => {
return React.cloneElement(child, { width: "100%", height: "100vh" });
})}
</div>
<div className={style.loading}>
{React.Children.map(children, (child, index) => {
return (
<div
className={style.indicator_outer}
onClick={() => onClickCarouselIndex(index)}
>
<div
className={style.indicator_inside}
style={{
animationDuration: index === activeIndex ? `${time}s` : "0s",
backgroundColor: index === activeIndex ? "#FFFFFF" : null,
}}
/>
</div>
);
})}
</div>
</div>
);
};
export default Carousel;
二、import style from “./carousel.module.css”;
.container {
overflow: hidden;
}
.inner {
white-space: nowrap;
transition: transform 0.3s;
}
.carousel_item {
display: inline-flex;
align-items: center;
justify-content: center;
height: 200px;
color: #fff;
background-color: #312520;
}
.loading {
position: absolute;
bottom: 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 10px;
width: 100%;
}
.indicator_outer {
width: 90px;
height: 7px;
background-color: #0000001a;
margin-left: 20px;
border-radius: 5px;
}
.indicator_inside {
height: 100%;
border-radius: 5px;
animation-fill-mode: forwards;
animation-name: progressBar;
animation-iteration-count: infinite;
}
@keyframes progressBar {
0% {
width: 0%;
}
100% {
width: 100%;
}
}
三、 App.js
import "./App.css";
import React, { useState } from "react";
import JayOne from "./assets/1.jpeg";
import JayTwo from "./assets/2.jpeg";
import JayThree from "./assets/3.jpeg";
import JayFour from "./assets/4.jpeg";
import Carousel, { CarouselItem, CarouselInfo } from "./Carousel";
// 轮播图数据
const info = [
{
id: 1,
title: "Jay",
describe: "2000—11—07",
image: JayOne,
backgroundColor: "#425066",
},
{
id: 2,
title: "范特西",
describe: "2001—09—20",
image: JayTwo,
backgroundColor: "#1bd1a5",
},
{
id: 3,
title: "范特西PLUS",
describe: "2001—12—28",
image: JayThree,
backgroundColor: "#a78e44",
},
{
id: 4,
title: "八度空间",
describe: "2002—07—18",
image: JayFour,
backgroundColor: "#493131",
},
];
const App = () => {
return (
<Carousel>
{info?.map((item) => {
return (
<CarouselItem
key={item.id}
styles={{ backgroundColor: item.backgroundColor }}
>
<CarouselInfo
title={item.title}
describe={item.describe}
image={item.image}
/>
</CarouselItem>
);
})}
</Carousel>
);
};
export default App;