惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
GbyAI
GbyAI
SecWiki News
SecWiki News
Project Zero
Project Zero
C
Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
P
Privacy International News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Scott Helme
Scott Helme
A
Arctic Wolf
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy & Cybersecurity Law Blog
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
The Hacker News
The Hacker News
T
Tenable Blog
雷峰网
雷峰网
有赞技术团队
有赞技术团队
V
V2EX
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
T
Threatpost
AWS News Blog
AWS News Blog
L
LINUX DO - 热门话题
Application and Cybersecurity Blog
Application and Cybersecurity Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
SegmentFault 最新的问题
月光博客
月光博客
Spread Privacy
Spread Privacy
S
Secure Thoughts
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
Forbes - Security
Forbes - Security
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
The Last Watchdog
The Last Watchdog
Y
Y Combinator Blog
I
Intezer
博客园 - 【当耐特】
B
Blog RSS Feed
Attack and Defense Labs
Attack and Defense Labs
I
InfoQ
博客园 - 叶小钗
Cyberwarzone
Cyberwarzone
V2EX - 技术
V2EX - 技术
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
H
Help Net Security
C
CERT Recently Published Vulnerability Notes

博客园 - Liaofy

前端新范式:用 AI 提效开发,用 E2E 保证迭代质量 把SPA的加载速度提升一倍,有没有搞头? 使用mumu模拟器抓包 andriod app 如何在本地给 vue-router4 扩展功能? Vue3 路由优化,使页面初次渲染效率翻倍 【eslint 插件开发】禁用 location 跳转外部链接 vue 项目使用 charles 代理线上页面到本地后显示404 避免使用单个数字作为对象的key 高扩展弹出层组件设计实现(H5&小程序) 轻松搞懂POST与PUT的区别 vite(or rollup) 实现 webpackChunkName webapp项目架构畅想(vue3).md vite试玩:老项目启动从6分钟到秒开 修剪AST树减少webapck打包尺寸 【uniapp】改善中大型uniapp小程序项目开发体验 vscode 调试 webpack 打包 5分钟,搞清 Unicode 与 UTF-8 的区别 deAsync.js 真正让异步变同步 多个请求间断失败,如何优雅处理?
盘的转——使用缓动函数完成动画
Liaofy · 2021-09-03 · via 博客园 - Liaofy

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

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

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

  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)
}

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

完/