1. 首页
  2. IT资讯

原生JS实现DOM爆炸效果

爆炸动效分享

 

前言

此次分享是一次自我组件开发的总结,还是有很多不足之处,望各位大大多提宝贵意见,互相学习交流。

分享内容介绍

通过原生js代码,实现粒子爆炸效果组件 组件开发过程中,使用到了公司内部十分高效的工程化环境,特此打个广告: 新浪移动诚招各种技术大大!可以私聊投简历哦!

 

 

效果预览

原生JS实现DOM爆炸效果

 

 

效果分析

 

  • * 点击作为动画开始的起点,自动结束
  • * 每次效果产生多个抛物线粒子运动的元素,方向随机,展示内容不一样,有空间上Z轴的大小变化
  • * 需求上可以无间隔点击,即第一组动画未结束可播放第二组动画
  • * 动画基本执行时长一致

 

由以上四点分析后,动画实现有哪些实现方案呢?

  • css操作态变换(如focus)使子元素执行动画

`不可取,效果可多次连点,css状态变换与需求不符`

 

  • Js 控制动画开始,事先写好css动画预置,通过class 包含选择器切换动画 例如: .active .items{animation:xxx …;}

`不可取,单次执行动画没有问题,但是存在效果的固定,以及无法连续执行动画`

 

  • 事先写好大量动画,隐藏大量dom元素,动画开始随机选取dom元素执行自己唯一的动画keyframes

`实现层面来说,行得通,但是评论列表长的时候,dom数量巨大,且css大量动画造成代码量沉重、无随机性`

  • 抛弃css动画,使用canvas 绘制动画

`可行,但是canvas维护成本略高,且自定义功能难设计,屏幕适配也有一定成本`

  • js做dom创建,生成随机css @keyframes

`可行,但是创建style样式表,引发css重新渲染页面,会导致页面的性能下降,且抛物线css的复杂度不低,暂不作为首选`

  • js 刷帧 做dom渲染

`可行,但是刷帧操作会造成性能压力`

 

 

结论

canvas虽说可行,但由于其开发弊端 本次分享不以canvas为分享内容,而是使用最后一种 js刷帧的dom操作

 

 


 

组件结构

 

由截图分享,动画可以分为两个模块,首先,随机发散的粒子具有共性:抛物线动画,淡出,渲染表情

 

而例子数量变多之后则为截图中的效果

 

但是,由于性能原因,我们需要做到粒子的掌控,实现资源再利用,那么还需要第二个模块,作为粒子的管控组件

 

所以: 此功能可使用两个模块进行开发: partical.js 粒子功能 与 boom.js 粒子管理

 


 

实现 Partical.js

1. 前置资源:抛物线运动的物理曲线需要使用Tween.js提供的速度函数

 

若不想引入Tween.js 可以使用以下代码

  1. /** Tween.js
  2. * t: current time(当前时间);
  3. * b: beginning value(初始值);
  4. * c: change in value(变化量);
  5. * d: duration(持续时间)。
  6. * you can visit ‘缓动函数速查表’ to get effect
  7. */
  8. const Quad = {
  9. easeIn: function(t, b, c, d) {
  10. return c * (t /= d) * t + b;
  11. },
  12. easeOut: function(t, b, c, d) {
  13. return -c *(t /= d)*(t-2) + b;
  14. },
  15. easeInOut: function(t, b, c, d) {
  16. if ((t /= d / 2) < 1) return c / 2 * t * t + b;
  17. return -c / 2 * ((–t) * (t-2) – 1) + b;
  18. }
  19. }
  20. const Linear = function(t, b, c, d) {
  21. return c * t / d + b;
  22. }

 

2. 粒子实现

实现思路:

希望在粒子管控组件时,使用new partical的方式创建粒子,每个粒子存在自己的动画开始方法,动画结束回调。

由于评论列表可能存在数量巨大的情况,我们希望只全局创建有限个数的粒子,那么则提供呢容器移除粒子功能以及容器添加粒子的功能,实现粒子的复用

partical_style.css

  1. //粒子充满粒子容器,需要容器存在尺寸以及relative定位
  2. .BoomPartical_Holder{
  3. position: absolute;
  4. left:0;
  5. right:0;
  6. top:0;
  7. bottom:0;
  8. margin:auto;
  9. }

particle.js

  1. import “partical_style.css”;
  2. class Partical{
  3. // dom为装载动画元素的容器 用于设置位置等样式
  4. dom = null;
  5. // 动画开始时间
  6. StartTime = -1;
  7. // 当前粒子的动画方向,区别上抛运动与下抛运动
  8. direction = “UP”;
  9. // 动画延迟
  10. delay = 0;
  11. // 三方向位移值
  12. targetZ = 0;
  13. targetY = 0;
  14. targetX = 0;
  15. // 缩放倍率
  16. scaleNum = 1;
  17. // 是否正在执行动画
  18. animating = false;
  19. // 粒子的父容器,标识此粒子被渲染到那个元素内
  20. parent = null;
  21. // 动画结束的回调函数列表
  22. animEndCBList = [];
  23. // 粒子渲染的内容容器 slot
  24. con = null;
  25. constructor(){
  26. //创建动画粒子dom
  27. this.dom = document.createElement(“div”);
  28. this.dom.classList.add(“Boom-Partical_Holder”);
  29. this.dom.innerHTML = `
  30. <div class=”BoomPartical_con“>
  31. Boom
  32. </div>
  33. `;
  34. }
  35. // 在哪里渲染
  36. renderIn(parent) {
  37. // dom判断此处省略
  38. parent.appendChild(this.dom);
  39. this.parent = parent;
  40. // 此处为初始化 slot 容器
  41. !this.con && ( this.con = this.dom.querySelector(“.Boom-Partical_con”));
  42. }
  43. // 用于父容器移除当前粒子
  44. deleteEl(){
  45. // dom判断此处省略
  46. this.parent.removeChild(this.dom);
  47. }
  48. // 执行动画,需要此粒子执行动画的角度,动画的力度,以及延迟时间
  49. animate({ deg, pow, delay } = {}){
  50. // 后续补全
  51. }
  52. // 动画结束回调存储
  53. onAnimationEnd(cb) {
  54. if (typeof cb !== ‘function’) return;
  55. this.animEndCBList.push(cb);
  56. }
  57. // 动画结束回调执行
  58. emitEndCB() {
  59. this.dom.style.cssText += `;-webkit-transform:translate3d(0,0,0);opacity:1;`;
  60. this.animating = false;
  61. try {
  62. for (let cb of this.animEndCBList) {
  63. cb();
  64. }
  65. } catch (error) {
  66. console.warn(“回调报错:”,cb);
  67. }
  68. }
  69. // 简易实现slot功能,向粒子容器内添加元素
  70. insertChild(child){
  71. this.con.innerHTML = ;
  72. this.con.appendChild(child);
  73. }
  74. }

 

 

致此,我们先创建了一个粒子对象的构造函数,现在考虑一下我们实现了我们的设计思路吗?

 

  • * 使用构造函数new Partical( )粒子
  • * 粒子实力对象存在 animate 执行动画方法
  • * 有动画结束回调函数的存储和执行
  • * 设置粒子的父元素: renderIn 方法
  • * 父元素删除粒子: deleteEl 方法

 

为了更好的展示粒子内容,我们特意在constructor里创建了一个 Boom-Partical_con 元素用于模拟slot功能: insertChild方法,用于使用者展示不同的内容进行爆炸?

 

 

接下来考虑一下动画的实现过程,动画毫无疑问为抛物线动画,这种动画在代码中实现可以使用物理公式,

但是我们也可以通过速度曲线实现,想想上抛过程可以想成 由于重力影响 ,变成一个速度逐渐减小的向上位移的过程,

而下抛过程可以理解为加速过程;

则可对应为速度曲线的easeOut 与 easeIn,

而水平方向可以理解为匀速运动,则是 linear;

 

我们以水平向右为X正方向0度,顺时针方向角度增加;

则 小于 180度为向下, 大于180度为向上

假设方向为`四点钟`方向,夹角则为 `30` 度,

按照高中物理,大小为N的力:

` 在X轴的分量应为 cos(30) * N ` ` 在Y轴的分量应为 sin(30) * N`

 

原生JS实现DOM爆炸效果

力的分解图解

 

也就是说 我们可以知道一个方向上的力在XY轴的分量大小,

假设我们将 力 的概念 转化为 视图中 位移的概念,

我们将 力量1 记为 10vh的大小

于是我们可以定义全局变量

 

  1. const POWER = 10; // 单位 vh 力的单位转化比例
  2. const G = 5; // 单位 vh 重力值
  3. const DEG = Math.PI / 180;
  4. const Duration = .4e3; //假设动画执行时长400毫秒

 

由此 我们补全 animate方法

 

  1. // 执行动画 角度 , 力 1 ~ 10 ; 1 = 10vh
  2. animate({ deg, pow, delay } = {}) {
  3. this.direction = deg > 180 ? “UP” : “DOWN”;
  4. this.delay = delay || 0;
  5. let r = Math.random();
  6. this.targetZ = 0;
  7. this.targetY = Math.round(pow * Math.sin(deg * DEG) * POWER);
  8. this.targetX = Math.round(pow * Math.cos(deg * DEG) * POWER) * (r + 1);
  9. this.scaleNum = (r * 0.8) * (r < 0.5 ? -1 : 1);
  10. this.raf();
  11. }

 

 

animte的思路为:通过传入的角度和力度 计算目标终点位置(因为力最终转化为位移值,力越大,目标位移越大)

 

使用随机数计算此次动画的缩放值变化范围(-0.8 ~ 0.8)

 

然后执行刷帧操作 raf

 

  1. raf(){
  2. // 正在执行动画
  3. this.animating = true;
  4. // 动画开始时间
  5. this.StartTime = +new Date();
  6. let StartTime = this.StartTime;
  7. // 获取延时
  8. let delay = this.delay;
  9. // 动画会在延时后开始,也就是真正开始动画的时间
  10. let StartTimeAfterDelay = StartTime + delay
  11. let animate = () => {
  12. // 获取从执行动画开始经过了多久
  13. let timeGap = +new Date() – StartTimeAfterDelay;
  14. // 大于0 证明过了delay时间
  15. if (timeGap >= 0) {
  16. // 大于Duration证明过了结束时间
  17. if (timeGap > Duration) {
  18. // 执行动画结束回调
  19. this.emitEndCB();
  20. return;
  21. }
  22. // 设置应该设置的位置的样式
  23. this.dom.style.cssText += `;will-change:transform;-webkit-transform:translate3d(${this.moveX(timeGap)}vh,${this.moveY(timeGap)}vh,0) scale(${this.scale(timeGap)});opacity:${this.opacity(timeGap)};`;
  24. }
  25. requestAnimationFrame(animate);
  26. }
  27. animate();
  28. }

 

 

刷帧操作中判断了delay时间的处理以及结束的时间处理回调

 

那么揭晓来就剩下 moveX,moveY,scale,opacity的设置

 

  1. // 水平方向为匀速,所以使用Linear
  2. moveX(currentDuration) {
  3. // 此处 * 2 是效果矫正后的处理,可根据自己的需求修改水平位移速度
  4. return Linear(currentDuration, 0, this.targetX, Duration) * 2;
  5. }
  6. // 缩放 使用了easeOut曲线, 可根据需求自行修改
  7. scale(currentDuration) {
  8. return Quad.easeOut(currentDuration, 1, this.scaleNum, Duration);
  9. }
  10. // 透明度 使用了easeIn速度曲线,保证后消失
  11. opacity(currentDuration) {
  12. return Quad.easeIn(currentDuration, 1, -1, Duration);
  13. }
  14. // 竖直方向上位移计算
  15. moveY(currentDuration) {
  16. let direction = this.direction;
  17. if (direction === ‘UP’) {
  18. // G用于模拟上抛过程的重力
  19. // 如果是上抛运动
  20. if (currentDuration < Duration / 2) {
  21. // 上抛过程 我们使用easeOut速度逐渐减小,我们让动画在一半时移到最高点
  22. return Quad.easeOut(currentDuration, 0, this.targetY + G, Duration / 2);
  23. }
  24. // 上抛的下降过程,从最高点下降
  25. return this.targetY + G – Quad.easeIn(currentDuration – Duration / 2, 0, this.targetY / 2, Duration / 2);
  26. }
  27. // 下抛运动直接easeIn
  28. return Quad.easeIn(currentDuration, 0, this.targetY, Duration);
  29. }

 

 

至此,partical.js 结束,文件末尾加一行

 

    export default Partical; 

 

 

此时 我们的partical.js输出一个构造函数:

 

  • * new 的时候创建了粒子元素,
  • * 使用onAnimtionEnd可以实现动画结束的回调函数
  • * insertChild可以向粒子内渲染使用者自定义的dom
  • * renderIn 可以设置粒子父元素
  • * deleteEl 可以从父元素删除粒子
  • * animate 可以执行刷帧,渲染计算位置,触发回调

于是对于粒子来说,只剩下在执行animte的时候 传入的力的大小,方向,以及延迟时间

 


 

粒子管理 Boom.js

之所以叫Boom是因为一开始组件名叫Boom,其实叫ParticalController更好一些,哈哈?

 

对于Boom.js的功能需求为

 

  • 创建粒子
  • 执行粒子动画,赋予动画力、角度、延时
  • 设置粒子容器

 

可达到效果:

 

  • 不关心业务,业务使用者传入每个粒子slot内容数组
  • 粒子组件可复用
  • 易于维护(可能是哈哈哈)

于是粒子管理器构架为:

 

  1. import Partical from “partical.js”;
  2. class Boom{
  3. // 实例化的粒子列表
  4. particalList = [];
  5. // 单次生成的粒子个数
  6. particalNumbers = 6;
  7. // 执行动画的间隔时间
  8. boomTimeGap = .1e3;
  9. boomTimer = 0;
  10. // 用户插入粒子的slot 的内容
  11. childList = [];
  12. // 默认旋转角度
  13. rotate = 120;
  14. // 默认的粒子发散范围
  15. spread = 180;
  16. // 默认随机延迟范围
  17. delayRange = 100;
  18. // 默认力度
  19. power = 3;
  20. // 此次执行粒子爆炸的是那个容器
  21. con = null;
  22. constructor({ childList , container , boomNumber , rotate , spread , delayRange , power} = {}){
  23. this.childList = childList || [];
  24. this.con = container || null;
  25. this.particalNumbers = boomNumber || 6;
  26. this.rotate = rotate || 120;
  27. this.spread = spread || 180;
  28. this.delayRange = delayRange || 100;
  29. this.power = power || 3;
  30. this.createParticals(this.particalNumbers);
  31. }
  32. setContainer(con){
  33. this.con = con;
  34. }
  35. // 创建粒子 存入内存数组中
  36. createParticals(num){
  37. for(let i = 0 ; i < num ; i++){
  38. let partical = new Partical();
  39. partical.onAnimationEnd(()=>{
  40. partical.deleteEl();
  41. });
  42. this.particalList.push(partical)
  43. }
  44. }
  45. // 执行动画
  46. boom(){
  47. // 限制动画执行间隔
  48. let lastBoomTimer = this.boomTimer;
  49. let now = +new Date();
  50. if(now – lastBoomTimer < this.boomTimeGap){
  51. // console.warn(“点的太快了”);
  52. return;
  53. }
  54. this.boomTimer = now;
  55. console.warn(“粒子总数:” , this.particalList.length)
  56. let boomNums = 0;
  57. // 在内存列表找,查找没有执行动画的粒子
  58. let unAnimateList = this.particalList.filter(partical => partical.animating == false);
  59. let childList = this.childList;
  60. let childListLength = childList.length;
  61. let rotate = this.rotate;
  62. let spread = this.spread;
  63. let delayRange = this.delayRange;
  64. let power = this.power;
  65. // 每有一个未执行动画的粒子,执行一次动画
  66. for(let partical of unAnimateList){
  67. if(boomNums >= this.particalNumbers) return ;
  68. boomNums++;
  69. let r = Math.random();
  70. // 设置粒子父容器
  71. partical.renderIn(this.con);
  72. // 随机选择粒子的slot内容
  73. partical.insertChild(childList[Math.floor(r * childListLength)].cloneNode(true));
  74. // 执行动画,在输入范围内随机角度、力度、延迟
  75. partical.animate({
  76. deg: (r * spread + rotate) % 360,
  77. pow: r * power + 1,
  78. delay: r * delayRange,
  79. });
  80. }
  81. // 如果粒子树木不够,则再次创建,防止下次不够用
  82. if(boomNums < this.particalNumbers){
  83. this.createParticals(this.particalNumbers – boomNums);
  84. }
  85. }
  86. }
  87. export default Boom;

 

使用demo

 

  1. let boomChildList = [];
  2. for(let i = 0 ; i < 10; i++){
  3. let tempDom = document.createElement(“div”);
  4. tempDom.className = “demoDom”;
  5. tempDom.innerHTML = i;
  6. boomChildList.push(tempDom);
  7. }
  8. let boom = new Boom({
  9. childList: boomChildList,
  10. boomNumber: 6,
  11. rotate: 0,
  12. spread: 360,
  13. delayRange: 100,
  14. power: 3,
  15. });

 

 

代码资源

 

源码链接​pan.baidu.com

 

 

组件效果预览

原生JS实现DOM爆炸效果

结尾

可能效果中实现的思维还有不妥和欠缺,欢迎各位大大提出宝贵意见,互相交流、学习!

本文来自投稿,不代表程序员编程网立场,如若转载,请注明出处:http://www.cxybcw.com/198430.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code