实现滚动点赞墙

需要实现的效果如下:

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

纯css实现

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

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

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

<math xmlns="http://www.w3.org/1998/Math/MathML"> i t e m S h o w T i m e + ( itemShowTime + ( </math>itemShowTime+(oneCycleItemNum - <math xmlns="http://www.w3.org/1998/Math/MathML"> o n e S c r e e n I t e m N u m ) ∗ ( oneScreenItemNum) * ( </math>oneScreenItemNum)∗(itemShowTime / $oneScreenItemNum)

scss 复制代码
$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:

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)

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

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)


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如下:

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样式

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;
}
相关推荐
速盾cdn26 分钟前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学128 分钟前
CSS浮动
前端·css·css3
什么鬼昵称28 分钟前
Pikachu-csrf-CSRF(POST)
前端·csrf
golitter.1 小时前
Vue组件库Element-ui
前端·vue.js·ui
golitter.1 小时前
Ajax和axios简单用法
前端·ajax·okhttp
雷特IT2 小时前
Uncaught TypeError: 0 is not a function的解决方法
前端·javascript
长路 ㅤ   2 小时前
vite学习教程02、vite+vue2配置环境变量
前端·vite·环境变量·跨环境配置
亚里士多没有德7752 小时前
强制删除了windows自带的edge浏览器,重装不了怎么办【已解决】
前端·edge
micro2010142 小时前
Microsoft Edge 离线安装包制作或获取方法和下载地址分享
前端·edge
.生产的驴2 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript