这期我们来完善上一期的动画库。在 Animation 类中的 constructor 的参数,我们发现其他的参数都用上了。但是 timingFunction 我们是还没有使用上的。这里我们就来一起处理这个问题。

timingFucntion 这个逻辑主要是用在 Animation 的 run 方法中。如果大家还记得之前的一篇讲解 CSS 动画的文章,里面我们了解到三次贝塞尔曲线。

在三次贝塞尔曲线里面,它的 y 轴就是 progress(进度),而 x 轴就是 time(时间)。一条三次贝塞尔曲线就会从 [0, 0] 坐标到 [1, 1] 坐标移动的轨迹。

我们的 timingFunction 也是这样一个函数,传入我们的时间进度,从而获得一个三次贝塞尔曲线的移动轨迹的进度

TimingFunction

TimingFunction 是一个关于 0 ~ 1 的 time(时间)的函数,通过三次贝塞尔计算返回一个 0~1 的 progress(进度)。

在 CSS 里面我们就有几个库,就比如 linear 这个 timingFunction。那么这里我们也去尝试写一些与三次贝塞尔曲线比较接近的 timingFunction。

Linear

首先我们来实现 Linear 这种动画曲线。这种曲线是相对比较好实现的。它就是一个自身不变的,一比一的 timingFunction。所以代码也是非常的简单:

export let linear = v => v;

实现三次贝塞尔函数

linear 种其实没有任何的缓动的效果的,如果我们想实现三次贝塞尔曲线中的 ease、ease-in、ease-out 这样的动画效果的话,我们就需要用到三次贝塞尔曲线中的 “牛顿积分法“ 去求一个时间点的进度值。

所以这里我们需要先实现三次贝塞尔曲线的函数来进行计算。

首先我们可以看看三次贝塞尔曲线的网站:

我们可以看到三次贝塞尔曲线是通过用 4 个参数来进行计算的。而这四个参数就是决定我们动画曲线的效果。

这里我们也不去详细的分析和推论出三次贝塞尔曲线的计算方式了,我们可以直接从 C++ 的库中把这个函数的代码取出来,然后转换这个代码成 JavaScript 然后直接使用即可。

export function cubicBezier(p1x, p1y, p2x, p2y) {const ZERO_LIMIT = 1e-6;// Calculate the polynomial coefficients,// implicit first and last control points are (0,0) and (1,1).const ax = 3 * p1x - 3 * p2x + 1;const bx = 3 * p2x - 6 * p1x;const cx = 3 * p1x;const ay = 3 * p1y - 3 * p2y + 1;const by = 3 * p2y - 6 * p1y;const cy = 3 * p1y;function sampleCurveDerivativeX(t) {// `ax t^3 + bx t^2 + cx t` expanded using Horner's rulereturn (3 * ax * t + 2 * bx) * t + cx;}function sampleCurveX(t) {return ((ax * t + bx) * t + cx) * t;}function sampleCurveY(t) {return ((ay * t + by) * t + cy) * t;}// Given an x value, find a parametric value it came from.function solveCurveX(x) {let t2 = x;let derivative;let x2;// https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation// first try a few iterations of Newton's method -- normally very fast.// http://en.wikipedia.org/wikiNewton's_methodfor (let i = 0; i < 8; i++) {// f(t) - x = 0x2 = sampleCurveX(t2) - x;if (Math.abs(x2) < ZERO_LIMIT) {return t2;}derivative = sampleCurveDerivativeX(t2);// == 0, failure/* istanbul ignore if */if (Math.abs(derivative) < ZERO_LIMIT) {break;}t2 -= x2 / derivative;}// Fall back to the bisection method for reliability.// bisection// http://en.wikipedia.org/wiki/Bisection_methodlet t1 = 1;/* istanbul ignore next */let t0 = 0;/* istanbul ignore next */t2 = x;/* istanbul ignore next */while (t1 > t0) {x2 = sampleCurveX(t2) - x;if (Math.abs(x2) < ZERO_LIMIT) {return t2;}if (x2 > 0) {t1 = t2;} else {t0 = t2;}t2 = (t1 + t0) / 2;}// Failurereturn t2;}function solve(x) {return sampleCurveY(solveCurveX(x));}return solve;
}

如果想详细了解这段代码的逻辑和推算过程,可以去了解代码备注中的链接。

实现动画效果库

好,我们有了 cubicBezier 这个函数,我们就可以通过使用它,实现 ease、ease-in、ease-out、ease-in-out 等动画曲线了。

我们可以通过三次贝塞尔曲线的官网,取得每个常用的动画效果的 4 个参数值。然后传入 cubicBezier 这个函数即可获得我们想要的进度值。

每个动画效果的方法实现如下:

Ease

export let ease = cubicBezier(0.25, 0.1, 0.25, 1);

Ease In

export let easeIn = cubicBezier(0.42, 0, 1, 1);

Ease Out

export let easeOut = cubicBezier(0, 0, 0.58, 1);

Ease In Out

export let easeInOut = cubicBezier(0.42, 0, 0.58, 1);

最后我们把所有这些动画曲线的函数都放入一个 ease.js 的 JavaScript 文件即可。这样我们 timingFunction 库就完成了。

使用 TimingFunction

有了 timingFunction 的库,我们就可以在 animation-demo.js 中使用这些动画函数来给我们的元素加入动画效果。

在我们使用这个之前,我们 animation.js 中的 timingFunction 和 template 参数都是没有给予默认值的。为了防止不必要的出错,我们可以给他们一个默认值。

我们只需要在 Animation Class 里面加入两行赋予参数默认值的逻辑即可:

constructor(object, property, startValue, endValue, duration, delay, timingFunction, template) {timingFunction = timingFunction || (v => v);template = template || (v => v);this.object = object;this.property = property;this.startValue = startValue;this.endValue = endValue;this.duration = duration;this.timingFunction = timingFunction;this.delay = delay;this.template = template;
}

接下来我们回到 animation-demo.js,并且通过 ease.js 来引入 ease 动画函数。

import {ease} from './ease.js';

然后在 Timeline 添加 Animation 的参数,传入 ease 这个 timingFunction。

tl.add(new Animation(document.querySelector('#el').style,'transform',0,500,2000,0,ease,v => `translate(${v}px)`)
);

我们这个 ease 动画函数实现的动画与 CSS 中 transition 的 ease 是否是一样的呢?为了证明我们实现了 CSS 中一样的 ease 动画效果,我们建立多一个 div 并且给它赋予 CSS 的 transition。这样我们就可以直观看到他们之间是否是一样的效果了。

首先在 animation.html 中加入这个 HTML 和 CSS 的代码:

<style>:root {--bg-color: #0f0e18;--sub-bg-color: #2d2f42;--purple-color: fuchsia;--blue-color: aqua;}body {background: var(--bg-color);}.buttons {color: var(--purple-color);}.purple {color: var(--purple-color);}.blue {color: var(--blue-color);}.box {width: 100px;height: 100px;background-color: aqua;margin-bottom: 1rem;}.ease-box {background-color: fuchsia !important;}button {background: transparent;color: var(--blue-color);border: 1px solid aqua;padding: 0.5rem 1rem;cursor: pointer;transition: all 400ms ease;}button:hover {background: var(--blue-color);color: #333;}
</style><body><div class="box" id="el"></div><div class="box ease-box" id="el2"></div><button id="pause-btn">Pause</button><button id="resume-btn">Resume</button><script src="./main.js"></script>
</body>

然后我们回到 animation-demo.js,给 el2 这个元素加入 transition 的属性。

document.querySelector('#el2').style.transition = 'transform 2s ease';
document.querySelector('#el2').style.transform = 'translateX(500px)';

然后我们来看一下效果:

蓝色的盒子就是我们自己编写的 ease 动画,而紫色的盒子就是 CSS 中的 ease 动画。

我们可以说基本上他们就是一致的,只不过 C++ 和 CSS 中三次贝塞尔曲线的计算有一点的差异导致动画有微小的不一样。但是大致上是一摸一样的。

重点是我们 JavaScript 实现的动画,是可以随时突发暂停,也可以随时触发继续播放的。但是 CSS 的动画是无法达到一样的功能的,它只能一播放就播放到结束。


实现重置

最后我们就去把 Timeline 中的重置功能也给实现了。其实这个功能的逻辑非常简单,它所需要用到的函数我们都有了。

  • 首先需要先暂停这个动画
  • 重置 startTime 开始时间为当前时间
  • 重置 PAUSE_TIME 为 0
  • 重置 PAUSE_START 为 0
  • 重置 ANIMATIONnew Set()
  • 重置 START_TIMESnew Map()
  • 重置 TICK_HANDLER 为 null
reset() {this.pause();this[PAUSE_TIME] = 0;this[PAUSE_START] = 0;this[ANIMATIONS] = new Set();this[START_TIMES] = new Map();this[TICK_HANDLER] = null;
}

就这样我们就完成了一个比较完善的动画库了。


对时间轴加入状态管理

虽然说我们的动画库和时间线的功能已经是非常完善了。但是其实里面还有一些存在的问题的。比如,我们没有调用 pause 就直接调用了 resume,这有可能会出现一些问题。

所以我们要给这个库安排一个状态管理,让这个类更具有健壮性。

初始化时

首先在 Timeline 实例化的时候,我们注入一个 Initiated 的状态,代表我们 Timeline 是在 初始化完毕 状态了。

constructor() {this.state = 'Initiated';this[ANIMATIONS] = new Set();this[START_TIMES] = new Map();
}

开始时

在 Timeline 的 start 方法执行的时候,我们就可以判断这个 Timeline 是否被初始化了,如果没有直接断开不继续执行。当然我们这里也可以直接抛一个错误,不过这个根据我们 API 设计风格而定即可。

如果可以执行 Timeline 的启动的话,我们就可以把状态改为 Started。这样我们就让这个 Timeline 变成一个开始之后的状态

start() {if (!this.state === 'Initiated') return;this.state = 'Started';/* ... */
}

暂停时

暂停状态的判断与开始状态一样,先判断 Timeline 是否已经进入了开始状态,如果没有就直接推出执行。

如果可以执行,就把状态更新为 Paused(已暂停)。

pause() {if (!this.state === 'Started') return;this.state = 'Paused';/* ... */
}

恢复时

先判断是否在暂停状态,如果不是就直接停止执行,否则更新状态为 Started(开始状态)。

resume() {if (!this.state === 'Paused') return;this.state = 'Started';/* ... */
}

重置时

重置是可以随时执行的,并不需要任何的前天条件的。这里我们只需要把状态更新为 Initiated 即可。

reset() {this.pause();this.state = 'Initiated';/* ... */
}

最后的添加时是不需要任何的拦截和状态更变的。所以 add 方法我们就可以不用动它了。


最后

在管理上来说,我们其实应该把 animation 相关的文件都放入一个新的项目里面的,但是这里我们就不详细做这部分了。同学自行进行整理即可。

这样我们就把 animation library(动画库)完整的实现了。

下一篇文章我们就会去把这个 animation 库与我们的 carousel(轮播图)结合来使用。最终用这个做一个完整的自定义组件库。

我是来自《技术银河》的三钻,一位正在重塑知识的技术人。下期再见。


⭐️ 三哥推荐

开源项目推荐

Hexo Theme Aurora

最近更新到了版本 1.3.0
谢谢各位的支持和持续的反馈和建议~

最近博主在全面投入开发一个可以 “迈向未来的” Hexo 主题,以极光为主题的博客主题。

如果你是一个开发者,做一个个人博客也是你简历上的一个亮光点。而如果你有一个超级炫酷的博客,那就更加是亮上加亮了,简直就闪闪发光。

如果喜欢这个主题,可以在 Github 上给我点个

用 JavaScript 实现三次贝塞尔动画库 - 前端组件化相关推荐

  1. 用 JavaScript 实现手势库 — 手势动画应用【前端组件化】

    前端<组件化系列>目录 「一」用 JSX 建立组件 Parser(解析器) 「二」使用 JSX 建立 Markup 组件风格 「三」用 JSX 实现 Carousel 轮播组件 「四」用 ...

  2. 用 JavaScript 实现手势库 — 事件派发与 Flick 事件【前端组件化】

    前端<组件化系列>目录 「一」用 JSX 建立组件 Parser(解析器) 「二」使用 JSX 建立 Markup 组件风格 「三」用 JSX 实现 Carousel 轮播组件 「四」用 ...

  3. 用 JavaScript 实现手势库 — 支持多键触发【前端组件化】

    前端<组件化系列>目录 「一」用 JSX 建立组件 Parser(解析器) 「二」使用 JSX 建立 Markup 组件风格 「三」用 JSX 实现 Carousel 轮播组件 「四」用 ...

  4. 用 JavaScript 实现手势库 - 实现监听逻辑【前端组件化】

    在之前的文章中我们一起实现了一个轮播图的基本效果,我们可以用鼠标去把它来回拖拽.效果上它已经是一个可以做到无尽轮播的轮播图功能了. 但是我们会发现,我们鼠标在图片上任何的动作都会触发到拖拽,并且对图片 ...

  5. 前端组件化的三种方案:

    1.预编译语言 在开发阶段,用更完善的第三方程序语言,生成html,css,js代码. 缺点:css有sass,less:js有CoffeeScript,TypeScripty:HTML呢? 优点:如 ...

  6. react-native系列(13)动画篇:Animated动画库和LayoutAnimation布局动画详解

    动画概念了解 流畅.有意义的动画对于APP户体验来说是非常重要的,用RN开发实现动画有三种方法: requestAnimationFrame:称为帧动画,原理是通过同步浏览器的刷新频率不断重新渲染界面 ...

  7. JavaScript动画库:Anime.js

    前言 今天简单学习一下JavaScript动画库:Anime.js.官网的介绍是: Anime.js (/ˈæn.ə.meɪ/) 是一个轻量的JavaScript 动画库, 拥有简单而强大的API. ...

  8. 11 个非常受欢迎的 JavaScript 动画库

    我在网上寻找好用整洁的Javascript动画库时,我发现许多开发者推荐的一些动画库.经过我的研究,我收集了11个最好的动画库供你的使用学习,并且也可以应用到你的程序中. 1.Three.js 地址: ...

  9. 1.GSAP(专业的Web/JavaScript动画库)

    GSAP(专业的Web/JavaScript动画库) 这是第一篇博客,文章内容是关于GSAP动画库的使用,编写于2020年10月21日17时09分(v1.0.0). 前言 GSAP的全名是GreenS ...

最新文章

  1. cic曲线是什么_贝塞尔曲线基本用法
  2. 被快乐×××的许晴-----平淡的生活中寻找快乐
  3. util.Date与sql.Date的相互转换以及区别
  4. 浅谈android的selector,背景选择器
  5. php 判断update返回为0_PHP empty函数判断0返回真还是假
  6. 如何在springboot中使用PageHelper分页插件
  7. 矩阵论复习-过渡矩阵、生成子空间、表示矩阵、度量矩阵、酉空间、内积
  8. 【Flink】Flink 的状态描述符 StateDescriptor operator state key state
  9. pythondis功能_python 使用 Dis 模块进行代码性能剖析
  10. java web 中的servlet讲解
  11. android ndk下载安装教程,NDK安装教程20180605
  12. linux中孚软件,中孚主机监控与审计系统
  13. 第三方支付操作流程,有何优势,又存在哪些风险?
  14. 商务与经济统计_电子商务类北大核心期刊目录大汇总!(推荐收藏!)
  15. 世界好疯狂呢,神奇小子,开源了他的自动驾驶软件
  16. scp在命令行中带密码远程拷贝文件
  17. 孝感市小学生机器人编程比赛_小学生获机器人大赛一等奖 编程是语文老师教的...
  18. 关于CSS的一些语法知识
  19. Day4-C语言计时器
  20. 清华大学-中国人民银行金融研究所2023年联合培养博士研究生招生简章

热门文章

  1. 用python做猜数游戏(数学运算版)
  2. springboot4S店车辆管理系统
  3. 少轻狂电子图书制作教程之三:用软景HTML制造机生成网页文件
  4. android sdn,华为发布全球首个基于SDN架构的敏捷物联解决方案
  5. 翻转电平函数实现LED闪烁-STM32电控学习笔记05
  6. 微信企业号通讯录与消息发送
  7. 蜗轮蜗杆计算软件_TP蜗杆减速机锐琛,升降器企业
  8. Error:(22, 58) java: 无法访问com.fasterxml.jackson.databind.JavaType 找不到com.fasterxml.jackson.databind
  9. PAT -A-1007 Maximum Subsequence Sum 简单动态规划
  10. 一直寻求的,灵魂__摘自复旦大学陈果教授大学生活导论