亲宝软件园·资讯

展开

js 逐步递增数字动画

我就是胖虎 人气:0

背景

可视化大屏项目使用最多的组件就是数字组件,展示数据的一个变化,为了提高视觉效果,需要给数字增加一个滚动效果,实现一个数字到另一个数字逐步递增的滚动动画。

先上一个思维导图:

实现类似滚轮的效果,容器固定,数字向上滚动

先列举所有的可能的值形成一个纵向的列表,然后固定一个容器,匀速更改数字的偏移值。

下面来介绍一下这种方案的实现,元素值从0到9一共十个值,每个数字占纵向列表的10%,所以纵向偏移值依次为为0%  —> -90%

实现:

<ul>
  <li>
    <span>0123456789</span>
  </li>
</ul>
ul{
  margin-top: 200px;
}
ul li{
  margin:0 auto;
  width: 20px;
  height: 30px;
  text-align: center;
  border:2px solid rgba(221,221,221,1);
  border-radius:4px;
}
ul li span{
  position: absolute;
  color: #fff;
  top: 30%;
  left: 50%;
  transform: translate(-50%,0);
  transition: transform 500ms ease-in-out;
  writing-mode: vertical-rl;
  text-orientation: upright;
  letter-spacing: 17px;
}
let spanDom = document.querySelector('span')
let start = 0
setInterval(() =>{
  start++
  if(start>9){
    start = 0
  }
  spanDom.style.transform = `translate(-50%,-${start*10}%)`
}, 1000)

上述代码存在一个问题,当我们从9到0的时候,容器偏移从-90%直接到了0%。 但是由于设定了固定的过渡动画时间,就会出现一个向反方向滚动的情况,为了解决这个问题,可以参考无缝滚动的思路

<ul>
  <li>
    <span>01234567890</span>
  </li>
</ul>
let spanDom = document.querySelector('span')
let start = 0
var timer = setInterval(fn, 1000);
function fn() {
  start++
  clearInterval(timer)
  timer = setInterval(fn,start >10 ? 0 : 1000);
  if(start>10){
    spanDom.style.transition = `none`
    start = 0
  }else{
    spanDom.style.transition = `transform 500ms ease-in-out`
  }
  spanDom.style.transform = `translate(-50%,-${start/11*100}%)`
}

利用两个元素实现滚动

仔细看动图的效果,事实上在在视口只有两个元素,一个值之前的值,一个为当前的值,滚动偏移值只需设置translateY(-100%)

具体思路:

下面是调整后的代码结构:

 <div className={styles.slider}>
   {[prev, cur].map((item, index) => (
      <span key={index} className={`${styles['slider-text']} ${playing && styles['slider-ani']} ${(prev === 0 && cur === 0 && index ===0) && styles['slider-hide']}`}>
        {item}
      </span>
    ))}
  </div>
const { value} = props
const [prev, setPrev] = useState(0)
const [cur, setCur] = useState(0)
const [playing, setPlaying] = useState(false)

  const play = (pre, current) => {
    setPrev(pre)
    setCur(current)
    setPlaying(false)
    setTimeout(() => {
      setPlaying(true)
    }, 20)
  }

  useEffect(() => {
    if (!Number.isNaN(value)) {
      play(cur, value)
    } else {
      setPrev(value)
      setCur(value)
    }
  }, [value])

.slider {
  display: flex;
  flex-direction: column;
  height: 36px;
  margin-top: 24%;
  overflow: hidden;
  text-align: left;
}

.slider-text {
  display: block;
  height: 100%;
  transform: translateY(0%);
}

.slider-ani {
  transform: translateY(-100%);
  transition: transform 1s ease;
}
.slider-hide {
  opacity: 0;
}

实现多个滚轮的向上滚动的数字组件

利用H5的requestAnimationFrame()API实现数字逐步递增的动画效果

实现一个数字的逐渐递增的滚动动画,并且要在指定时间内完成。要看到流畅的动画效果,就需要在更新元素状态时以一定的频率进行,JS动画都是通过在很短的时间内不停的渲染/绘制元素做到的,所以计时器一直都是Javascript动画的核心技术,关键就是刷新的间隔时间,刷新时间需要尽量短,这样动画效果才能显得更加流畅,不卡顿;同时刷新间隔又不能太短,需要确保浏览器有能力渲染动画 。
大多数电脑显示器的刷新频率是 60Hz,即每秒重绘 60次。因此平滑动画的最佳循环间隔是通常是 1000ms/60,约等于16.6ms

计时器对比

requestAnimationFrame实现滚动动画思路

动画开始,记录开始动画的时间 startTimeRef.current

const startTimeRef = useRef(Date.now());
const [t, setT] = useState(Date.now());

之后每一帧动画,记录从开始动画经过了多长时间,计算出当前帧的所到达的数字应该是多少,即currentValue

useEffect(() => {
    const rafFunc = () => {
      const now = Date.now();
      const t = now - startTimeRef.current;
      if (t >= period) {
        setT(period);
      } else {
        setT(t);
        requestAnimationFrame(rafFunc);
      }
    };
    let raf;
    if (autoScroll) {
      raf = requestAnimationFrame(rafFunc);
      startTimeRef.current = Date.now();
    } else {
      raf && cancelAnimationFrame(raf);
    }
    return () => raf && cancelAnimationFrame(raf);
}, [period, autoScroll]);

const currentValue = useMemo(() => ((to - from) / period) * t + from, [t, period, from, to]);

针对当前每个数字位上的数字进行比较,如果有变化,进行偏移量的变化,偏移量体现在当前数字位上的数字与下一位数字之间的差值,这个变化每一帧都串起来形成了滚动动画

成果展示

加载全部内容

相关教程
猜你喜欢
用户评论