注册
web

实现滚动点赞墙

需要实现的效果如下:



需要将用户点赞的信息,一条一条的展示在页面顶部,这样的效果有多种实现方式,下面一一来了解一下吧~


纯css实现



scss如下:(如果要将scss改为less,将$改为@就可以了)


当移动到第8行结束的时候,同屏出现的两行(第9行和第10行),就需要结束循环,重头开始了


这是一个上移的动画,动画执行的时间就是8s


itemShowTime+(itemShowTime + (itemShowTime+(oneCycleItemNum - oneScreenItemNum)∗(oneScreenItemNum) * (oneScreenItemNum)(itemShowTime / $oneScreenItemNum)


$itemHeight: 60px; // 单个item的高度

$itemShowTime: 2s; // 单个item从完整出现到消失的时长
$oneCycleItemNum: 8; // 单个循环上移的item条数
$oneScreenItemNum: 2; // 同屏出现的item条数(不能大于 oneCycleItemNum)

$oneCycleItemTime: calc($itemShowTime + ($oneCycleItemNum - $oneScreenItemNum) * ($itemShowTime / $oneScreenItemNum));

@keyframes dynamics-rolling {
from {
transform: translateY(0);
}
to {
transform: translateY(-$itemHeight * $oneCycleItemNum);
}
}
.container {
height: 600px;

animation: dynamics-rolling $oneCycleItemTime linear infinite;
.div {
line-height: 60px;
}
}

.visibleView {
width: 100%;
height: 120px;
overflow: hidden;
background-color: skyblue;

}
.box {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}

简单的demo:



import React, { useEffect, useRef, useState } from 'react'
import styles from './index.module.scss'

const dataSource = new Array(50).fill(0).map((_, index) => index + 1)

export default function CycleScrollList() {
const [data, setData] = useState(dataSource.slice(0, 10))

return (
<div className={styles.box}>
<div className={styles.visibleView}>
<div className={styles.container}>
{
data.map((item, index) => (
<div key={ index } className={styles.div}>{ item }</div>
))
}
</div>
</div>
</div>

)
}

setInterval监听


css动画是定时的,所以可以定时更新列表内容,但是会有很明显的抖动,效果不太友好,应该是定时器的时间还不能太准




import React, { useEffect, useRef, useState } from 'react'
import styles from './index.module.scss'

const dataSource = new Array(50).fill(0).map((_, index) => index + 1)


export default function CycleScrollList() {
const [data, setData] = useState(dataSource.slice(0, 10))
const nextIndex = useRef(10) // 持续从 dataSource 拿数据的下一个 index

useEffect(() => {
const timer = setInterval(() => {
replaceData()
},4900)

return () => {
clearInterval(timer)
}
}, [])


const replaceData = () => {
let newData = []
if (nextIndex.current-5 < 0) {
newData = [...dataSource.slice(nextIndex.current-5) ,...dataSource.slice(0, nextIndex.current + 5)]
} else {
newData = [...dataSource.slice(nextIndex.current-5, nextIndex.current + 5)]
}
// 使用当前的后半份数据,再从 dataSource 中拿新数据
console.log(newData)
const nextIndexTemp = nextIndex.current + 5
const diff = nextIndexTemp - dataSource.length
if (diff < 0) {
nextIndex.current = nextIndexTemp
} else {
// 一轮数据用完,从头继续
nextIndex.current = diff
}
setData(newData)
}

return (
<div className={styles.box}>
<div className={styles.visibleView}>
<div className={styles.container}>
{
data.map((item, index) => (
<div key={ index } className={styles.div}>{ item }</div>
))
}
</div>
</div>
</div>

)
}

IntersectionObserver监听


监听第5个元素


如果第五个元素可见了,意味着不可见时,需要更换数据了


如果第五个元素不可见了,立刻替换数据


替换的数据如下:



使用IntersectionObserver监听元素,注意页面卸载时,需要去除绑定


tsx如下:



import React, { useEffect, useRef, useState } from 'react'
import styles from './index.module.scss'

const dataSource = new Array(50).fill(0).map((_, index) => index + 1)
const ITEM_5_ID = 'item-5'

export default function CycleScrollList() {
const [data, setData] = useState(dataSource.slice(0, 10))

const intersectionObserverRef = useRef<IntersectionObserver | null>()
const item5Ref = useRef<HTMLDivElement | null>(null)

const nextIndex = useRef(10) // 持续从 dataSource 拿数据的下一个 index
const justVisible5 = useRef<boolean>(false) // 原来是否为可视

useEffect(() => {
intersectionObserverRef.current = new IntersectionObserver((entries) => {
entries.forEach((item) => {
if (item.target.id === ITEM_5_ID) {
// 与视图相交(开始出现)
if (item.isIntersecting) {
justVisible5.current = true
}
// 从可视变为不可视
else if (justVisible5.current) {
replaceData()
justVisible5.current = false
}
}
})
})
startObserver()

return () => {
intersectionObserverRef.current?.disconnect()
intersectionObserverRef.current = null
}
}, [])

const startObserver = () => {
if (item5Ref.current) {
// 对第五个 item 进行监测
intersectionObserverRef.current?.observe(item5Ref.current)
}
}

const replaceData = () => {
let newData = []
if (nextIndex.current-5 < 0) {
newData = [...dataSource.slice(nextIndex.current-5) ,...dataSource.slice(0, nextIndex.current + 5)]
} else {
newData = [...dataSource.slice(nextIndex.current-5, nextIndex.current + 5)]
}
// 使用当前的后半份数据,再从 dataSource 中拿新数据
console.log(newData)
const nextIndexTemp = nextIndex.current + 5
const diff = nextIndexTemp - dataSource.length
if (diff < 0) {
nextIndex.current = nextIndexTemp
} else {
// 一轮数据用完,从头继续
nextIndex.current = diff
}
setData(newData)
}

return (
<div className={styles.box}>
<div className={styles.visibleView}>
<div className={styles.container}>
{
data.map((item, index) => (
index === 4 ?
<div id={ ITEM_5_ID } ref={ item5Ref } key={ index } className={styles.div}>{ item }</div>
:
<div key={ index } className={styles.div}>{ item }</div>
))
}
</div>
</div>
</div>

)
}

scss样式


$itemHeight: 60px; // 单个item的高度

$itemShowTime: 3s; // 单个item从完整出现到消失的时长
$oneCycleItemNum: 5; // 单个循环上移的item条数
$oneScreenItemNum: 3; // 同屏出现的item条数(不能大于 oneCycleItemNum)

$oneCycleItemTime: calc($itemShowTime + ($oneCycleItemNum - $oneScreenItemNum) * ($itemShowTime / $oneScreenItemNum));

@keyframes dynamics-rolling {
from {
transform: translateY(0);
}
to {
transform: translateY(-$itemHeight * $oneCycleItemNum);
}
}
.container {
height: 600px;

animation: dynamics-rolling $oneCycleItemTime linear infinite;
.div {
line-height: 60px;
}
}

.visibleView {
width: 100%;
height: 120px;
overflow: hidden;
background-color: skyblue;

}
.box {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}

作者:0522Skylar
来源:juejin.cn/post/7278244755825442853

0 个评论

要回复文章请先登录注册