安装组件:
npm install taro-marquees --save
yarn add taro-marquees
思路:
文本循环滚动是由多个元素组成。
其中有一个元素是始终被隐藏的,它的作用就是让我们获取一下要滚动字符串的宽度。
//元素实例
<View>我是文本</View>
<View>我是文本</View>
<View>我是文本</View>
<View id={'text-dom'}>我是文本</View>
通过下面的代码块我们可以看到rect为空的时候一直在反复获取元素的属性。
真相只有一个:在使用Taro.createSelectorQuery时需要有延迟才可以获取到,我们并不知道渲染完成后多久才可以获取到,所以这是唯一的解决办法
有的同学说了什么不用类组件中的生命周期?
哎呀~~我都有尝试,都存在渲染延迟获取问题,意思就是这个方法不一定在渲染完成后就一定可以获取到元素的属性。
简单点儿:页面渲染完成你也获取不到属性!
我说的是自动执行,并非用户触发!除非使用定时器延迟获取
在这里我测试了长度为70px字符串的滚动速度,这个速度是根据自身调的,看着舒服就行。width = 70px; duration = 5000;这两个值都有了我们求一下1px的速度即可,只要有了1px的速度,之后来了字符串就自动获取宽度,然后计算滚动时长。
又有的小伙伴发话了,为什么不用字符串length计算?
length的长度与width的更为精准的时width,字符 ’ 和 我 这两个字符的length都是1那个 我 这个字符串明显就比 ’ 宽,所以懂我的意思了吧。
//事件实例
const [width,setWidth] = useState(0);
const [time,setTime] = useState(0);
const getDomWidth = () =>{
Taro.createSelectorQuery().select(`#text-dom-${ids}`).boundingClientRect((rect) => {
if (!rect) {
setTimeout(() => {
getTextWidth();
}, 500)
} else {
setWidth(rect.width);
// 1px的速度 = (width:70px = s:5000 = s/width=71.42857142857143)
setTime(rect.width * 71.42857142857143 / 1000)
}
}).exec();
}
上面有了字符串宽度、滚动事件,接下来我们将除有id的元素外的其他元素做css悬浮,这就的注意一下,组件外层需要有 width 和 position: ‘relative’,要不然组件就起飞了。
我们将除有ID外的元素做悬浮,给组件内包裹元素的层也做悬浮然后tarnsition赋值滚动时间,做悬浮,设置left为-(width * (curIndex + 1)),
这里的curIndex是每执行一次滚动的时间curIndex就加1,然后乘上字符串宽持续叠加就是实现滚动了,那这样文字就滚没了。
我们还需要给文字加上left
第一个元素的Left:width * curIndex
第二个元素的Left:width * curIndex
第三个元素的Left:width * curIndex
完整代码:
import { Text, View } from "@tarojs/components"
import Taro, { useDidHide, useDidShow } from "@tarojs/taro";
import { useEffect, useMemo, useState } from "react"
const Marquees = ({
text,
fontStyle,
ids = `${new Date().getTime()}-${Math.ceil(Math.random() * 100)}`,
suffix = ' '
}) => {
const [width, setWidth] = useState(null);
const [curIndex, setCurIndex] = useState(0);
const [time, setTime] = useState(null);
const [count, setCount] = useState(0);
const [render, setRender] = useState(false);
let timer = null;
/** 获取文字宽 */
const getTextWidth = () => {
Taro.createSelectorQuery().select(`#text-dom-${ids}`).boundingClientRect((rect) => {
if (!rect) {
setTimeout(() => {
getTextWidth();
}, 500)
} else {
setWidth(rect.width);
// 1px的速度 = (width:70px = s:5000 = s/width=71.42857142857143)
setTime(rect.width * 71.42857142857143 / 1000)
}
}).exec();
}
useEffect(() => {
clearTimeout(timer)
setWidth(null);
setCurIndex(0);
setCount(0);
setTime(null);
setRender(false);
getTextWidth();
}, [text])
useDidHide(() => {
clearTimeout(timer)
setWidth(null);
setCurIndex(0);
setCount(0);
setTime(null);
setRender(false);
})
useDidShow(() => {
clearTimeout(timer)
setWidth(null);
setCurIndex(0);
setCount(0);
setTime(null);
setRender(false);
getTextWidth();
})
useEffect(() => {
if (time) {
timer = setTimeout(() => {
setCurIndex(curIndex + 1);
setCount(1);
}, count === 0 ? 0 : time * 1000);
}
return () => {
clearTimeout(timer);
}
}, [curIndex, time])
return <>
{useMemo(() => {
if (!time) return <View />
setRender(true);
if (!render) return <View />
return <View
style={{
width: '100px',
display: 'flex',
transition: `${time}s linear`,
position: 'absolute',
left: `-${width * (curIndex + 1)}PX`,
}}
>
<View style={{
...fontStyle, ...{
position: 'absolute',
display: 'inline-block',
float: 'left',
whiteSpace: 'nowrap',
left: `${width * curIndex}PX`
}
}}>
<Text decode={true}>{text}{suffix}</Text>
</View>
<View style={{
...fontStyle, ...{
position: 'absolute',
display: 'inline-block',
float: 'left',
whiteSpace: 'nowrap',
left: `${width * (curIndex + 1)}PX`
}
}}>
<Text decode={true}>{text}{suffix}</Text>
</View>
<View style={{
...fontStyle, ...{
position: 'absolute',
display: 'inline-block',
float: 'left',
whiteSpace: 'nowrap',
left: `${width * (curIndex + 2)}PX`
}
}}>
<Text decode={true}>{text}{suffix}</Text>
</View>
</View>
}, [width, curIndex, time, count])}
<View
id={`text-dom-${ids}`}
style={{ opacity: '0', whiteSpace: 'nowrap', display: "inline-block" }}
>
<Text decode={true}>{text}{suffix}</Text>
</View>
</>
}
export default Marquees