一、js 滚动事件 scroll
-
鼠标移入停止自动滚动,且可以手动上下滚动,可以随时滚动到顶部或底部;鼠标移出自动滚动。
-
滚动数据根据需要改为图片或html元素,或者从接口读取数据组装元素。
-
实现首尾衔接的思路,分为每条数据固定高度、每条数据不固定高度。如果每条数据固定高度,计算一条数据的高度,每滚动一条数据的高度,就把第一条数据 shift 删除, push 放到末尾;如果每条数据不固定高度,元素渲染完成后,动态计算并保留每条数据外层元素的高度和边距,假设为 height,滚动距离达到 height,则 shift 这条数据并 push 末尾。但首尾衔接,在手动滚动到顶部或底部,回不到数据最初始的状态。
-
横向滚动,将 clientHeight,scrollHeight,scrollTop 改为 对应横向属性值。
-
渲染数据变化时,或者在模态框中渲染,或依据一些外在条件变化而需要重新渲染时,必须清除所有定时器。
-
封装组件使用,需考虑:依赖项,滚动方向,速率,每条数据渲染形式。
-
实际开发如果出现滚动停不下来,或者滚动速度加快,排查是否有地方没有及时清除定时器。
代码
import { useState, useEffect, useRef } from 'react'
// 定时器
let intervalTimer, timeoutTimer
const Index = () => {
// 滚动容器
const domRef = useRef()
// 滚动数据
const [list, setList] = useState([])
useEffect(() => {
// 模拟数据
const arr = new Array()
for(let i = 0; i < 30; i++) {
const obj = {
key: i,
value: `第 ${i + 1} 条数据`
}
arr.push(obj)
}
setList(arr)
}, [])
useEffect(() => {
if(domRef.current) {
// 开始滚动
autoScroll()
// 鼠标移入,清除滚动
domRef.current.onmouseenter = () => {
window.clearInterval(intervalTimer)
window.clearTimeout(timeoutTimer)
}
// 鼠标离开,重新滚动
domRef.current.onmouseleave = () => {
autoScroll()
}
}
}, [domRef])
const autoScroll = () => {
if(timeoutTimer) {
window.clearTimeout(timeoutTimer)
}
timeoutTimer = setTimeout(() => {
// 容器高度
const clientH = domRef.current && domRef.current.clientHeight || 0
// 滚动高度
const scrollH = domRef.current && domRef.current.scrollHeight || 0
if(intervalTimer) {
window.clearInterval(intervalTimer)
}
// 无滚动
if(scrollH == clientH) return
intervalTimer = setInterval(() => {
if(domRef.current) {
const scrollTop = domRef.current.scrollTop
const distance = scrollH - clientH
// 计算滚动距离
if(scrollTop >= distance) {
// 滚动距离大于可滚动高度时,回到顶部
domRef.current.scrollTop = -1
}
domRef.current.scrollTop += 1
}
}, 40)
}, 2000)
}
return (
<div ref={domRef} style={{width: 300, height: 500, overflow: 'auto'}}>
{
list.map(item => <li key={item.key}>{item.value}</li>)
}
</div>
)
}
export default Index
二、css
-
鼠标移入停止自动滚动,不能手动滚动;鼠标移出自动滚动。
-
主要利用 animation 动画属性,定义 keyframes 动画,动态改变元素 y 坐标。
-
translateY 移动的距离值,是根据每条元素的高度和容器高度来计算的,比如容器高度500,总共30条数据,每条数据高度22,translateY = 22 * 30 - 500 = 160,再根据实际展示效果微调。
代码
import { useState, useEffect } from 'react'
import styles from './index.less'
const Index = () => {
// 滚动数据
const [list, setList] = useState([])
useEffect(() => {
// 模拟数据
const arr = new Array()
for (let i = 0; i < 30; i++) {
const obj = {
key: i,
value: `第 ${i + 1} 条数据`
}
arr.push(obj)
}
setList(arr)
}, [])
return (
<div className={styles.wrap}>
{
list.map(item => <li key={item.key} className={styles.list}>{item.value}</li>)
}
{/* 把数据重复渲染一次,效果即是首尾衔接,对应的位移高度需要加上容器高度,即 translatY(-(160 + 500)) = translatY(-660) */}
{/* {
list.map(item => <li key={item.key} className={styles.list}>{item.value}</li>)
} */}
</div>
)
}
export default Index
.wrap {
width: 500px;
height: 500px;
overflow: hidden;
.list {
width: 300px;
height: 22px;
position: relative;
-webkit-animation-name: vscroll;
-moz-animation-name: vscroll;
-o-animation-name: vscroll;
animation-name: vscroll;
-webkit-animation-duration: 5s;
-moz-animation-duration: 5s;
-o-animation-duration: 5s;
animation-duration: 5s;
-webkit-animation-direction: normal;
-moz-animation-direction: normal;
-o-animation-direction: normal;
animation-direction: normal;
-webkit-animation-iteration-count: infinite;
-moz-animation-iteration-count: infinite;
-o-animation-iteration-count: infinite;
animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
-moz-animation-timing-function: linear;
-o-animation-timing-function: linear;
animation-timing-function: linear;
}
}
.wrap:hover .list {
-webkit-animation-play-state: paused;
}
@-webkit-keyframes vscroll {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-160px);
transform: translateY(-160px);
}
}
@-moz-keyframes vscroll {
0% {
-webkit-transform: translateY(0);
-moz-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-160px);
-moz-transform: translateY(-160px);
transform: translateY(-160px);
}
}
@-o-keyframes vscroll {
0% {
-webkit-transform: translateY(0);
-o-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-160px);
-o-transform: translateY(-160px);
transform: translateY(-160px);
}
}
@keyframes vscroll {
0% {
-webkit-transform: translateY(0);
-moz-transform: translateY(0);
-o-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-160px);
-moz-transform: translateY(-160px);
-o-transform: translateY(-160px);
transform: translateY(-160px);
}
}
三、插件(react-fast-marquee、animejs)
示例:react-fast-marquee
-
鼠标移入停止自动滚动,不能手动滚动;鼠标移出自动滚动。
-
无法点击单个元素触发事件,marquee 把点击作为了开始和暂停滚动的属性,需要修改源码使用。
-
marquee 只能左右滚动,竖向滚动需要自定义样式覆盖以及编写动画(黑科技:把容器旋转90°,但样式写起来比较麻烦)。
-
使用时需要根据需求重写或覆盖样式。
关键代码
import Marquee from 'react-fast-marquee'
<Marquee
paruseOnHover
direction='left'
speed={8}
className='mymarquee'
>
{
list.map(item => <li key={item.key}>{item.value}</li>)
}
</Marquee>
// 去除默认的半透明背景
.marquee-container .overlay::before, .marquee-container .overlay::after {
background: none;
}
// 竖向滚动
.mymarquee .marquee {
// 默认 flex 布局,根据实际作更改
display: block !important;
// 改为竖向
animation: vscroll var(--duration) linear var(--delay) var (--iteration-count);
}
// 竖向滚动动画
@keyframes vscroll {
0% {
transform: translateY(0%)
}
100% {
transform: translateY(-100%)
}
}