前一段时间一直有大屏相关的业务需求,大屏嘛,顾名思义,花里胡哨,华而不实(bushi,我发现甲方爸爸特别喜欢滚来滚去的列表展示样式,基本上每个界面都要有个滚动列表,要首尾衔接滚动,要丝滑,要自动配界面巴拉巴拉…之前刷到过的都是定义两个定时器,看着很复杂,看不懂,干脆自己写个算了,周末闲来无事,试试就逝世。
支持自定义滚动样式,包括首尾衔接滚动,来回滚动(向下滚到底后向上滚动),滚动速度等
组件本身代码:
/**
* @effect: 滚动组件,支持来回滚动
* @Date:2023-02-25 16:25:45
*/
import React, { useState, useEffect } from 'react';
import './index.less';
import _ from 'lodash';
const CycleShow=(props)=> {
const { styles, children, viewHeight, itemHeight, speed, listLength = children.props.children.length, isNoseToTail, moveHeight = itemHeight * listLength - viewHeight } = props;
const [cycleStart, setcycleStart] = useState(0);
let timer = null;
let chushu = isNoseToTail ? (((moveHeight * 2) / speed) + ( 50 / speed)) : ((moveHeight * 2) / speed);
const _running = (value) => {
value = value + 1;
let tmp = value % chushu;
setcycleStart(tmp);
timer = requestAnimationFrame(() => { _running(tmp); });
};
useEffect(() => {
_running(1);
return () => {
timer && cancelAnimationFrame(timer);
};
}, []);
// 如果是首尾相连滚动,渲染两次防止出现空白区域
return (
<div className="cycleShow-wrapper" style={{ ...styles }}>
{isNoseToTail
?
<div className="noseTail-show"
style={
{ transform: `translateY(${-cycleStart * speed}px)` }
}
>
{children}
{children}
</div>
:
<div className="rollBack-show"
style={
cycleStart > ((moveHeight / speed) - 1) ?
{ transform: `translateY(${cycleStart * speed - (moveHeight * 2)}px)` } :
{ transform: `translateY(${-cycleStart * speed}px)` }
}
>
{children}
</div>}
</div >
);
}
export default CycleShow;
其中,requestAnimationFrame()方法是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。
但与setTimeout相比,requestAnimationFrame最大的优势是由浏览器来决定回调函数的执行机制,如果屏幕刷新频率是60Hz,那么回调函数每秒执行60次,如果屏幕刷新率是75Hz,那么回调函数每秒执行75次。这样可以保证回调函数在屏幕每一次的刷新间隔中只被执行一次,不会导致动画卡顿了。并且,当页面被隐藏或者最小化时,绘制任务会被浏览器暂停,requestAnimationFrame也会停止渲染,而setTimeout仍会执行动画任务,造成资源浪费。
requestAnimationFrame是有返回值的,返回一个整数,表示请求id。可以将此id传给cancelAnimationFrame()来取消该请求方法。
使用该组件时,需要传递一些参数:
名称 | 描述 | 是否必须 |
---|---|---|
styles | 自定义的样式 | 非必须 |
children | 滚动的的内容,可以是表格,可以是其他的,完全支持自定义 | 必须,传递的内容需要用Fragment将元素包裹起来 |
viewHeight | 视区高度,即滚动元素的整体高度 | 必须 |
itemHeight | 滚动的单个元素的高度 | 必须 |
speed | 滚动速度:每秒滚动多少px | 非必须,不传默认0.5 |
isNoseToTail | 滚动样式:true:首尾相连 false:从头到尾从尾到头 | 非必须 |
eg:
import React, { useState, useEffect, useRef, Fragment } from 'react';
import './index.less';
import CycleShow from '.';
import './cycleShow.less'
const defaultData = [{
id: 1,
name: '阿瓜1',
age: '18'
}, {
id: 2,
name: '阿瓜瓜2',
age: '18'
}, {
id: 3,
name: '阿瓜瓜瓜3',
age: '18'
}, {
id: 4,
name: '阿瓜瓜瓜4',
age: '18'
}, {
id: 5,
name: '阿瓜瓜瓜5',
age: '18'
}, {
id: 6,
name: '阿瓜瓜瓜6',
age: '18'
}
]
const ITEM_HEIGHT = 40;
const Test = (props) => {
const _randerChildren = () => {
return <Fragment>
{defaultData.map((item, idx) => {
return <div style={{ height: `${ITEM_HEIGHT}px` }} key={item.id} className={"item"}>{item.name}</div>
})}
</Fragment>
}
return (
<div className="test-wrapper" >
<CycleShow
isNoseToTail //滚动类型 true:首尾相连 false:从头到尾从尾到头
children={_randerChildren()} //子元素dom --必传
viewHeight={100} //视区高度,单位是px --必传
itemHeight={ITEM_HEIGHT} //每行元素的高度 --必传,建议viewHeight是itemHeight的倍数
speed={0.5} //滚动速度,即1000/60毫秒移动多少px
/>
</div>
);
}
export default Test;
最终效果:(感觉这个动图录制软件有点失帧,实际上比较流畅)
最后:记得样式文件中将超出隐藏起来哈:overflow: hidden;