Taro React组件使用(11) —— RuiNoticebar 公告栏

1. 需求分析

  1. 用于循环播放展示一组消息通知;
  2. 通知消息渲染完成,获取消息的长度和盒子的长度;
  3. 使用【taro react】---- 获取元素的位置和宽高等信息异步获取内容和盒子的宽高信息;
  4. 通过 CSS3 的 animation 实现内容的移动;
  5. 注意:第一次移动和第二次移动的长度不相同,因此需要监听第一次动画完成 onAnimationEnd。

2. 异步获取元素的宽高信息

import { createSelectorQuery } from '@tarojs/taro';
function isWindow(val){
  return val === window
}

export const getRect = (elementRef) => {
  const element = elementRef
  // 判断传入元素是否是window窗口,是window窗口,直接获取窗口的宽高
  if (isWindow(element)) {
    const width = element.innerWidth
    const height = element.innerHeight

    return {
      top: 0,
      left: 0,
      right: width,
      bottom: height,
      width,
      height,
    }
  }
  // 是元素,同时可以获取元素的宽高等信息
  if (element && element.getBoundingClientRect) {
    return element.getBoundingClientRect()
  }
  // 都不满足,返回默认值
  return {
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    width: 0,
    height: 0,
  }
}

export const getRectByTaro = async (element) => {
  // 元素存在,判断使用对应环境获取元素信息
  if (element) {
    if(process.env.TARO_ENV === "h5"){
      // H5环境使用元素的获取元素信息方法
      return Promise.resolve(getRect(element))
    } else if(process.env.TARO_ENV === "weapp"){
      // 微信小程序环境调用 boundingClientRect 获取元素信息
      return new Promise((resolve) => {
        createSelectorQuery()
          .select(`.${element.props.class.split(' ').filter(item => item).join('.')}`)
          .boundingClientRect(resolve).exec()
      })
    }
  }
  // 返回默认值
  return Promise.resolve({
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    width: 0,
    height: 0,
  })
}

3. 监听内容加载获取盒子和内容元素信息

  1. 初始化加载如果又内容,就初始化滚动参数;
  2. 使用 setTimeout,实现先渲染,后获取渲染后的元素信息;
  3. 判断盒子和内容是否都渲染完成,没有渲染就直接返回;
  4. 通过 getRectByTaro 获取盒子和内容的宽度;
  5. 判断 scrollable 是否允许进行消息滚动;
  6. 判断内容和盒子的宽度,如果内容小于盒子,则不进行消息滚动;
  7. 如果可以消息滚动,则记录盒子和内容的宽度,计算动画滚动的时间;
  8. 设置第一次滚动的className;
  9. 不能滚动,就清除滚动动画的className。
  // 初始化加载如果又内容,就初始化滚动参数
  useEffect(() => {
    initScrollWrap(content)
  },[content])
  // 初始化设置函数
  const initScrollWrap = (value) => {
    let timer = setTimeout(async () => {
      clearTimeout(timer)
      if(!wrapRef.current || !contentRef.current){
        return
      }
      const wrapObj = await getRectByTaro(wrapRef.current)
      const wrapW = wrapObj.width
      const offsetObj = await getRectByTaro(contentRef.current)
      const offsetW = offsetObj.width
      const canScroll = scrollable == null ? offsetW > wrapW : scrollable
      if (canScroll) {
        setWrapWidth(wrapW)
        setOffsetWidth(offsetW)
        setAnimationDuration(offsetW / speed)
        setAnimationClass('rui-play')
      } else {
        setAnimationClass('')
      }
    },0)
  }

4. 监听第一次结束设置后续动画

  1. 第一次动画结束,将判断第一次动画的变量设置为 false;
  2. 异步设置计算动画的时间;
  3. 设置无限循环移动的动画className。
  // 第一次运动结束后设置新的时间和动画
  const onAnimationEnd = () => {
    setFirstRound(false)
    let timer = setTimeout(() => {
      setAnimationDuration((offsetWidth + wrapWidth) / speed)
      setAnimationClass('rui-play-infinite')
      clearTimeout(timer)
    }, 0)
  }

5. CSS3 移动动画

.rui-play-infinite{
    animation: rui-notice-bar-play-infinite linear infinite both running;
  }
  .rui-play{
    animation: rui-notice-bar-play linear both running;
  }
  @keyframes rui-notice-bar-play {
    to {
      transform: translate3d(-100%,0,0)
    }
  }

  @keyframes rui-notice-bar-play-infinite {
    to {
      transform: translate3d(-100%,0,0)
    }
  }

6. 完整 JSX 代码

import { View } from '@tarojs/components';
import { useAsyncState } from '@utils/event';
import { useEffect, useRef } from 'react';
import { getRectByTaro } from '@utils/use-client-rect';
import './index.scss'

const RuiNoticebar = (props) => {
  let {
    children,
    content,
    scrollable = null,
    speed = 50,
    delay = 1
  } = props;
  // 获取盒子和内容的宽度,设置移动的时间,是否是第一次移动,以及移动的class
  let wrapRef = useRef(null)
  let contentRef = useRef(null)
  let [wrapWidth, setWrapWidth] = useAsyncState(0)
  let [offsetWidth, setOffsetWidth] = useAsyncState(0)
  let [animationDuration, setAnimationDuration] = useAsyncState(0)
  let [firstRound, setFirstRound] = useAsyncState(true)
  let [animationClass, setAnimationClass] = useAsyncState('')
  // 初始化加载如果又内容,就初始化滚动参数
  useEffect(() => {
    initScrollWrap(content)
  },[content])
  // 初始化设置函数
  const initScrollWrap = (value) => {
    let timer = setTimeout(async () => {
      clearTimeout(timer)
      if(!wrapRef.current || !contentRef.current){
        return
      }
      const wrapObj = await getRectByTaro(wrapRef.current)
      const wrapW = wrapObj.width
      const offsetObj = await getRectByTaro(contentRef.current)
      const offsetW = offsetObj.width
      const canScroll = scrollable == null ? offsetW > wrapW : scrollable
      if (canScroll) {
        setWrapWidth(wrapW)
        setOffsetWidth(offsetW)
        setAnimationDuration(offsetW / speed)
        setAnimationClass('rui-play')
      } else {
        setAnimationClass('')
      }
    },0)
  }
  // 第一次运动结束后设置新的时间和动画
  const onAnimationEnd = () => {
    setFirstRound(false)
    let timer = setTimeout(() => {
      setAnimationDuration((offsetWidth + wrapWidth) / speed)
      setAnimationClass('rui-play-infinite')
      clearTimeout(timer)
    }, 0)
  }
  // 设置内容的动画参数
  const contentStyle = {
    animationDelay: `${firstRound ? delay : 0}s`,
    animationDuration: `${animationDuration}s`,
    transform: `translateX(${firstRound ? 0 : `${wrapWidth}px`})`,
  }
  return 
    
      
        { children }
        { content }
      
    
  
}
export default RuiNoticebar;

7. 完整 SCSS 代码

.rui-noticebar-temp-content{
  height: 80px;
  .rui-noticebar-wrap-content{
    flex: 1;
    height: 80px;
    line-height: 80px;
    overflow: hidden;
    position: relative;
  }
  .rui-noticebar-content{
    position: absolute;
    white-space: nowrap;
  }
  .rui-ellipsis {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap
  }
  .rui-play-infinite{
    animation: rui-notice-bar-play-infinite linear infinite both running;
  }
  .rui-play{
    animation: rui-notice-bar-play linear both running;
  }
  @keyframes rui-notice-bar-play {
    to {
      transform: translate3d(-100%,0,0)
    }
  }

  @keyframes rui-notice-bar-play-infinite {
    to {
      transform: translate3d(-100%,0,0)
    }
  }
}

8. 效果

Taro React组件使用(11) —— RuiNoticebar 公告栏_第1张图片

9. 使用实例

import { View, Text, Image } from '@tarojs/components';
import { RuiCustomWhite } from '@com/RuiCustom';
import RuiNoticebar from '@com/RuiNoticebar';
import Img from '@utils/icon/icon';

const NoticebarPage = (props) => {
  return 
    
    基础使用
    
      
    
    添加图标
    
      
      
        NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
        }/>
      
    
    自定义速度
    
      
      
        NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
        }/>
      
    
    停止滚动
    
      
      
        NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
        }/>
      
    
    children
    
      
        
          
            
            NutUI 是京东风格的移动端组件库。
          
        
      
    
  
}
export default NoticebarPage;

你可能感兴趣的:(taro,react.js,前端)