• 盘的转——使用缓动函数完成动画


    这个活动搞一个转盘抽奖吧,简单弄下就行

    离下班还有半小时,产品发给我了一个小需求。

    一看我就笑了——转盘抽奖,这还不容易?无非:

    1. 请求数据;
    2. 等待结果返回,计算转盘停止时的角度 x,开始旋转;
    3. 等待旋转停止

    关键代码:

    const angle = await fetchData()
    const rotateElm = document.querySelect('.rotate-img')
    rotateElm(elm, angle)
    
    function rotateElm(elm, angle) {
      elm.style.transitionProperty = 'transform'
      elm.style.transitionDuration = '5s'
      elm.style.transitionTimingFunction = 'ease;
      elm.style.transform = 'rotate(' + 360 * 5 - angle + 'deg)'
    }
    

    简洁、快速的实现了需求,非常棒!

    发布测试,下班干饭!

    翌日。

    能不能立马转起来?

    每次点击都得停顿下才旋转,用户体验不好,能不能立马转起来?测试妹子如是说。

    “你看xx、yy,别人家都这样啊!”我心里有点不爽。但,咱可不能说不行,尤其在妹子面前。

    没问题的!

    给飞驰的汽车换轮子

    上面的方案,是等待异步结果返回了,才开始旋转,这是停顿的原因。解决思路也就明确了:

    1. 开始旋转,同时发送请求;
    2. 等待结果返回,设置停止角度;
    3. 等待旋转停止。

    显然,通过 CSS 已经无法满足需求,得祭出 JS 大法方可自由控制旋转动画。

    缓动函数

    使用 JS 实现动画是一个精细活儿,还好大神们已经实现了常用的缓动函数,respect!譬如下面这位,详见:

    function easeInQuad(x: number): number {
      return x * x;
    }
    

    用一张图来说时间 t 和 x 的关系:

    横坐标是时间 t,纵坐标是 x,曲线便是 easeInQuad 的返回值。其中 x 的取值范围是[0,1],easeInQuad 的结果范围也是[0,1]。

    原料都准备好了,菜炒得好不好吃就看各家的水平了。

    使用缓动函数完成动画

    动画是怎么动的?大白话就是不停的改变物体的状态(旋转角度、位置、大小等),它就动起来了。在 JS 的语境下,就得在每一帧(1000/60 ms)改一下元素的状态。那么如何得到元素在任意时刻的状态值(crrentV) 呢?

    P.S. 以下内容讨论的是线性的简单动画,注意标题

    线性动画四要素:开始状态(starV)、结束状态(endV)、持续时间(duration)以及如何动(easeInQuad(x))。

    转化成代码语言就是要:在 duration 毫秒内,动画初始值 startV 经过 easeInSine(x) 转换后,变成结束值 endV。所以,在某一帧 f 时:x = f * 1000 / (duration * 60)

    这一步貌似有点突然,怎么得出的,且听我慢慢道来。

    为了获得平滑的动画效果,我们会使用requestAnimationFrame来执行动画函数,回调函数执行次数通常是每秒60次,即 (1000/60)毫秒执行一次回调。

    假设某个动画的持续时间是 5000ms,那么应该执行 300 次:5000/count = 1000/60 => count = 300。又因为上述 x 的取值范围为[0,1],也就是说,x 从 0 开始,经过 300 次递增后变成了 1,那么每次递增的幅度为 1/300。任意一帧 f 对应的 x = f / 300,即 x = f * 1000 / (duration * 60)

    有了上面的推论,就很容易得到某一帧 f 对应的状态 currentV:crrentV = startV + (endV - startV) * easeInSine(x)

    由此,可以得到一个通用的缓动函数执行器:

    
    function tweenExcution ({
      startValue, 
      endValue, 
      duration,  
      infinite,
      easingFn, 
      stepCb
    }) {
      const perUpdateDistance = 1000 / (duration * 60)
      const diffValue = endValue - startValue
    
      let position = 0
      let prevState = 0
      let state = 0
      let maxDiffState = 0
      let shouldStop = false
    
      function step () {
        if (shouldStop) return
    
        if (position < 1) { // 按 easingFn 进行运动
          position += perUpdateDistance
          prevState = state
          state = startValue + diffValue * easingFn(position)
          stepCb(state)
          rAF(step)
        } else if (infinite) {
          if (maxDiffState == 0) {
            maxDiffState = state - prevState
          }
          state += maxDiffState
                
          stepCb(state)
          rAF(step)
        }
      }
    
      step()
    
      return {
        stop() {
          shouldStop = true
        },
        getState() {
          return state
        }
      }
    }
    
    function rAF(cb) {
      requestAnimationFrame(cb)
    }
    

    剩下的工作就很简单了,代码详见

    完/

  • 相关阅读:
    写给自己的2020年总结
    docker镜像与docker容器
    docker安装&docker简介
    windows 安装linux子系统
    typora设置图床
    让Mysql插入中文
    pip 换源
    Unity中如何将一个场景(Scene)的Light Settings复制给另一个场景
    Windows API开发
    【C#】判断字符串中是否包含指定字符串,contains与indexOf方法效率问题
  • 原文地址:https://www.cnblogs.com/fayin/p/15223407.html
Copyright © 2020-2023  润新知