完整demo 链接:github.com/webfamer/re…
轮播效果一
实现思路:主要使用CSSTransition 实现淡入淡出效果,效果变换的同时更新一下列表的数据,从而表现出轮播图的效果
主要代码如下:
animatiom.css
//定义淡入淡出效果供cssTransiton组件使用
.tableScale-enter {
opacity: 0;
transform: scale(0.9);
}
.tableScale-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.tableScale-exit {
opacity: 1;
}
.tableScale-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}
index.tsx
import { waitTime } from '@/utils/utils';
import './animate.less';
//自定义假数据
const personInfoData = [{
subjectName: '课目一',
finalTime: '11:10.12',
violationTime: 2,
tRank: 1,
},
{
subjectName: '课目二',
finalTime: '12:11.12',
violationTime: 3,
tRank: 2,
},
{
subjectName: '课目三',
finalTime: '13:11.10',
violationTime: 0,
tRank: 3,
}]
const PersonDetail = () => {
const [visible, setvisible] = useState(true); //触发cssTransiotion动画效果
const [subjectInfo, setSubjectInfo] = useState<IsubjectResponseList>({}); //存储单条展示的数据
//最主要的函数 控制淡入淡出并且更换显示数据
const groupChangeTimer = () => {
let count = 1;
//当数据只有1条时不轮播
if (personInfoData.length === 1) return;
let timer = setInterval(async () => {
if (personInfoData.length > 0 &&count < personInfoData.length)
{
setSubjectInfo(personInfoData[count]); //更新展示的数据
}
setvisible(false);
await waitTime(1000); //延迟函数,淡入后延迟1s淡出
setvisible(true);
count = count >= personInfoData.length - 1 ? 0 : count + 1;
}, 5000); //5s轮换一次
return timer;
};
useEffect(() => {
let timer = groupChangeTimer();
return () => {
clearInterval(timer);
};
}, []);
return (
<div>
<CSSTransition
in={visible} //显示隐藏控制的变量
timeout={300}
classNames="tableScale" //对应animate.css的类名
unmountOnExit
>
<div className={styles.tableContent}>
<div className={styles.tableItem}>
<label>课目用时</label>
<span>{subjectInfo?.finalTime}</span>
</div>
<div className={styles.tableItem}>
<label>判罚次数</label>
<span>{subjectInfo?.violationTime}</span>
</div>
<div className={styles.tableItem}>
<label>排名</label>
<span>{subjectInfo.tRank}</span>
</div>
</div>
</CSSTransition>
</div>
)
}
utils.ts
export const waitTime = (time: number = 500) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
轮播效果二
实现思路:
不间断滚动思路:准备两个div,里面填充一样的内容,当滚动的距离等于第一个盒子的高度(offsetHeight,与height的区别是多了border)时,重置回0,从头开始滚动。
定时停止重复滚动实现:当盒子向上滚动的距离达到需要的距离后,停止定时器,隔5s后又重新启动定时器,实现了该效果。
代码如下:
index.less
.tableContent {
height: 712px;
overflow: hidden;
.table {
.tableItem {
height: 89px;
background: rgba(79, 173, 223, 0.14);
}
}
}
.hiddenTableItem { //把填充的项设置成透明
opacity: 0;
}
data.ts
//自定义假数据
export const rankData = [{
delegation: '代表队1',
tRank: 1,
staffName: '队员1',
score: 98,
departmentName: '杭州代表队'
}, {
delegation: '代表队2',
tRank: 1,
staffName: '队员1',
score: 96,
departmentName: '温州代表队'
}, {
delegation: '代表队3',
tRank: 1,
staffName: '队员1',
score: 94,
departmentName: '嘉兴代表队'
}, {
delegation: '代表队4',
tRank: 1,
staffName: '队员1',
score: 86,
departmentName: '湖州代表队'
}, {
delegation: '代表队5',
tRank: 1,
staffName: '队员1',
score: 84,
departmentName: '衢州代表队'
}, {
delegation: '代表队6',
tRank: 1,
staffName: '队员1',
score: 82,
departmentName: '绍兴代表队'
}, {
delegation: '代表队7',
tRank: 1,
staffName: '队员1',
score: 80,
departmentName: '金华代表队'
}, {
delegation: '代表队8',
tRank: 1,
staffName: '队员1',
score: 70,
departmentName: '丽水代表队'
}, {
delegation: '代表队9',
tRank: 1,
staffName: '队员1',
score: 66,
departmentName: '义乌代表队'
}, {
delegation: '代表队10',
tRank: 1,
staffName: '队员1',
score: 64,
departmentName: '嘉兴代表队'
}, {
delegation: '代表队11',
tRank: 1,
staffName: '队员1',
score: 62,
departmentName: '诸暨代表队'
}, {
delegation: '代表队12',
tRank: 1,
staffName: '队员1',
score: 60,
departmentName: '湖州代表队'
}]
index.tsx
import { rankData } from './data'
import styles from './index.less';
import { useEffect, useState, useRef } from 'react';
const PersonRank = () => {
const [listData, setListData] = useState<IallRank[]>([]); //存储列表数据
const tableDiv = useRef<HTMLDivElement>(null); //盒子1
const tableDiv2 = useRef<HTMLDivElement>(null); //盒子2
const tableContainer = useRef<HTMLDivElement>(null); //主盒子
const timerRef = useRef<NodeJS.Timer | null>(null); //存储定时器
/**处理数据,补全成8的倍数**/
useEffect(() => {
if (rankData) {
//这里对假数据进行处理,因为我需要8条数据一版,所以我处理成8的倍数
const personAllRank = rankData;
if (personAllRank?.length > 0) {
if (personAllRank.length % 8 !== 0) {
let restNum = 8 - (personAllRank.length % 8);
for (let i = 0; i < restNum; i++) {
personAllRank.push({
delegation: '',
tRank: '',
score: '',
departmentName: '',
staffName: '',
});
}
}
setListData(rankData);
}
}
}, [rankData]);
/**滚动函数**/
const startScroll = () => {
let count = 0;
let value = 0;
//把第一个盒子的内容复制给第二个盒子,方便实现无缝滚动
setTimeout(() => {
if (tableDiv.current && tableDiv2.current)
tableDiv2.current.innerHTML = tableDiv.current.innerHTML;
}, 1000);
function scrollTable() { //最主要的滚动函数
timerRef.current = setInterval(() => {
count -= 4; //4像素滚动
if (tableContainer.current && tableDiv.current) {
tableContainer.current.style.transform = `translateY(${count}px)`;
value = Number(
//正则匹配获取translateY的值
tableContainer.current.style.transform.match(/-?[0-9]+/g),
);
//向上滚动的距离等于第一个盒子高度时,重置
if (parseInt(String(value)) <= -tableDiv.current?.offsetHeight) {
count = 0;
}
}
//这里设置的8条数据滚动一版,8条数据的高度正好是712
//8条数据滚完时,暂停5s,然后继续滚动
if (parseInt(String(value)) % 712 === 0 && timerRef.current) {
clearInterval(timerRef.current);
setTimeout(() => {
scrollTable();
}, 5000);
}
}, 10);
}
scrollTable();
//存储一下定时器,方便其他地方调用
return timerRef.current;
};
/**初始化时开始滚动函数**/
useEffect(() => {
let timer = setTimeout(() => {
startScroll();
}, 5000);
return () => {
clearInterval(timer);
};
}, [tableDiv.current]);
return (
<div className={styles.personRank}>
<div className={styles.tableTitle}>
<span>排名</span>
<span>姓名</span>
<span style={{ width: 300 }}>所属单位</span>
<span style={{ marginLeft: 30, width: 164 }}>总分</span>
<span>荣誉</span>
</div>
<div className={styles.tableContent}>
<div ref={tableContainer}>
<div
className={styles.table}
ref={tableDiv}
>
//补全的数据是空数据,这里判断没有delegation这个字段就设置成透明
{listData.map((item, index) => {
return (
<div
className={`${styles.tableItem} ${item.delegation === '' ? styles.hiddenTableItem : ''
}`}
key={index}
>
<span>{item.tRank}</span>
<span>{item.staffName}</span>
<span style={{ width: 300 }}>{item.departmentName}</span>
<span style={{ marginLeft: 30, width: 164 }}>
{item.score}
</span>
</div>
);
})}
</div>
<div className={styles.table} ref={tableDiv2}></div>
</div>
</div>
</div>
);
}
轮播效果三
实现思路:给列表每一项都设置上动画,动画内容为从右边进入,从左边退出,并且动画结束时,保留结束时候的状态,这时我们在把原来数组项数据,一条一条替换,替换的时候加一点延迟,就能实现这种效果。
代码如下:
index.css
@keyframes fadeInOut {
0% {
transform: translateX(100%);
}
10% {
transform: translateX(0);
}
90% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
.tableContent {
height: 712px;
overflow: hidden;
.table {
.tableItem {
height: 89px;
background: rgba(79, 173, 223, 0.14);
border: 1px solid #173048;
}
.fadeInOut {
animation: fadeInOut ease-in-out 10s;
animation-fill-mode: forwards;
}
.hideTable {
opacity: 0;
}
}
}
index.tsx
import styles from './index.less';
import { getGroupingData } from '@/utils/utils';
import { defalutData } from './data' //导入默认数据
const PersonRank = () => {
const [storeData, setStoreData] = useState([]); //列表数据
const listGroupData = useRef([[]]); //8个一组把数据分组
const timerRef = useRef<Nodejs.Timer>(null);
const Firstflag = useRef<boolean>(false); //首次加载判断
//把数据划分成8个一组
useEffect(() => {
const filterData = defalutData.filter(
(item) => item.score,
);
if (filterData.length % 8 !== 0) {
let restNum = 8 - (filterData.length % 8);
for (let i = 0; i < restNum; i++) {
filterData.push({
staffName: '',
departmentName: '',
tRank: '',
score: '',
staffId: String(i),
});
}
}
//存储划分后的分组
const tournameGroupData = getGroupingData(filterData, 8);
listGroupData.current = tournameGroupData;
}, []);
//首次加载后标记一下,反正多个定时器,导致动画效果混乱
useEffect(() => {
Firstflag.current = true;
}, []);
//开始进行滚动轮播
useEffect(() => {
reduceGroupData(listGroupData.current);
}, []);
const startAnimation = (data: IhitResponseList[]) => {
let count = 0;
if (data && data.length > 0) {
//每0.25s更新一条数据
let timer = setInterval(async () => {
setStoreData((val) => {
let newVal = JSON.parse(JSON.stringify(val));
if (data[count]) {
newVal[count] = data[count];
}
return newVal;
});
count++;
if (count > 7) {
//当数据大于8条时停止
clearInterval(timer);
count === 0;
}
}, 250);
}
};
const reduceGroupData = (groupArr: IhitResponseList[][]) => {
let count = 0;
if (groupArr.length > 0) {
//第一次加载的时候进行一下动画
if (Firstflag.current) {
startAnimation(groupArr[count]);
}
//如果数据只有一组,就不用进行数据源切换
if (groupArr.length === 1) return;
if (timerRef.current) {
clearInterval(timerRef.current); //每次进来先清除一下定时器,防止多个定时器影响
}
//每10s替换一下列表数据源
timerRef.current = setInterval(() => {
count = count === groupArr.length - 1 ? 0 : count + 1;
startAnimation(groupArr[count]);
}, 10000);
}
return (
<div className={styles.personRank}>
<div className={styles.tableTitle}>
<span>排名</span>
<span>姓名</span>
<span style={{ width: 300 }}>所属单位</span>
<span>得分</span>
</div>
<div className={styles.tableContent}>
<div>
<div className={styles.table}>
{storeData.map((item, index) => {
return (
//补全的空数据条数直接设置成透明隐藏
<div
className={`${styles.tableItem} ${item.staffName ? '' : styles.hideTable
} ${styles.fadeInOut}`}
key={item?.staffId}
>
<span className={styles.rankLogoBg}>{item?.tRank}</span>
<span>{item.staffName}</span>
<span style={{ width: 300 }}>{item.departmentName}</span>
<span>{item.score}</span>
</div>
);
})}
</div>
</div>
</div>
</div>
);
};
}
data.ts
export const defalutData = [{
delegation: '代表队1',
tRank: 1,
staffName: '队员1',
score: 98,
staffId: '100',
departmentName: '杭州代表队'
}, {
delegation: '代表队2',
tRank: 2,
staffName: '队员2',
score: 96,
staffId: '101',
departmentName: '温州代表队'
}, {
delegation: '代表队3',
tRank: 3,
staffName: '队员3',
score: 94,
staffId: '102',
departmentName: '嘉兴代表队'
}, {
delegation: '代表队4',
tRank: 4,
staffName: '队员4',
score: 86,
staffId: '103',
departmentName: '湖州代表队'
}, {
delegation: '代表队5',
tRank: 5,
staffName: '队员5',
score: 84,
staffId: '104',
departmentName: '衢州代表队'
}, {
delegation: '代表队6',
tRank: 6,
staffName: '队员6',
score: 82,
staffId: '105',
departmentName: '绍兴代表队'
}, {
delegation: '代表队7',
tRank: 7,
staffName: '队员7',
score: 80,
staffId: '106',
departmentName: '金华代表队'
}, {
delegation: '代表队8',
tRank: 8,
staffName: '队员8',
score: 70,
staffId: '107',
departmentName: '丽水代表队'
}, {
delegation: '代表队9',
tRank: 9,
staffName: '队员9',
score: 66,
staffId: '108',
departmentName: '义乌代表队'
}, {
delegation: '代表队10',
tRank: 10,
staffName: '队员11',
score: 64,
staffId: '109',
departmentName: '嘉兴代表队'
}, {
delegation: '代表队11',
tRank: 11,
staffName: '队员12',
score: 62,
staffId: '110',
departmentName: '诸暨代表队'
}, {
delegation: '代表队12',
tRank: 12,
staffName: '队员13',
score: 60,
staffId: '111',
departmentName: '湖州代表队'
}]
utils.ts
//将数组划分为n个一组
export const getGroupingData = (data: any[], val: number) => {
let groupData: any[] = [];
for (let i = 0; i < data.length; i += Number(val)) {
const newData = data.slice(i, i + Number(val));
groupData.push(newData);
}
return groupData;
};
轮播效果四
实现思路:
轮播实现:给div动态的加上样式,让其左中右排列,然后改变样式,实现轮播
无缝轮播:将第0份复制一下放到最后,然后当从最后一份切换到第0份到时候,马上让他跳到第1份(从最后1分切换到第0份第时候,取消过渡效果)
代码如下:
index.less
.flexCenter {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 120px;
}
.transiton {
transition: all 1s ease-in-out;
}
.center {
position: relative;
width: 100%;
left: 0;
}
.right {
position: absolute;
left: 100%;
width: 100%;
top: 0;
}
.left {
position: absolute;
left: -100%;
width: 100%;
top: 0;
}
index.tsx
import {personData} from './data' //引入默认数据
import styles from './index.less';
const Person = () => {
//列表数据
const [staffInfo, setStaffInfo] = useState<IstaffInfoResponses[]>([
{
teamName: '',
staffInfoList: [],
},
]);
const [step, setStep] = useState(0); //控制div样式
//样式判断函数
const getClassName = (index: number) => {
if (index <= step - 1) {
return styles.left;
} else if (index === step) {
return styles.center;
} else if (index >= step + 1) {
return styles.right;
}
};
//轮播函数
const startSwiper = () => {
let count = 1;
let timer = setInterval(async () => {
console.log(count,'切换count')
setStep(count);
//无缝切换,缩短0-1的切换间隔
if (count === 0) {
count = 1;
setTimeout(() => {
setStep(1);
}, 500);
}
//到最后一个到时候切换到第0个
count = count > personData.length - 1 ? 0 : count + 1;
}, 3000);
return timer;
};
//启动切换函数
useEffect(() => {
let timer = startSwiper();
return () => {
clearInterval(timer);
};
}, []);
//对数据进行处理,把第一份复制到最后
useEffect(() => {
setStaffInfo([...personData,personData[0]])
}, []);
//根据数据结构渲染div
return (
<div className={`page-container ${styles.personBg}`}>
{staffInfo.map((staffInfoItem, index) => {
return (
<div
dataSet={index}
//当step ==0,也就是从最后一个刚切换到第0个这一过程,不添加过渡函数
className={`${getClassName(index)} ${styles.flexCenter} ${
step === 0 ? '' : styles.transiton
}`}
>
<div className={styles.title}>
<p>{staffInfoItem?.teamName}</p>
<img src={line} alt="" />
</div>
<div className={styles.personContent}>
{staffInfoItem?.staffInfoList?.map((item) => {
return (
<div
className={`${styles.personBox} ${
staffInfoItem?.staffInfoList?.length > 8
? styles.ninePersonBox
: styles.eightPersonBox
}`}
>
<div>
<img
src={`${item.picture ? item.picture : headLogo}`}
alt=""
style={{ objectFit: 'contain' }}
/>
</div>
<div className={styles.info}>
<div className={styles.infoItem}>
<span className={styles.point}></span>
<span>
{item?.staffName} {item?.age}岁
</span>
</div>
<div className={styles.infoItem}>
<span className={styles.point}></span>
<span>{item?.departmentName}</span>
</div>
</div>
</div>
);
})}
</div>
</div>
);
})}
</div>
);
}
export default Person;