爆炸动效分享

前言

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

分享内容介绍

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

效果预览

效果分析

  • 点击作为动画开始的起点,自动结束
  • 每次效果产生多个抛物线粒子运动的元素,方向随机,展示内容不一样,有空间上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 可以使用以下代码

     * Tween.js* t: current time(当前时间);* b: beginning value(初始值);* c: change in value(变化量);* d: duration(持续时间)。* you can visit 'http://easings.net/zh-cn' to get effect
        *const Quad = {easeIn: function(t, b, c, d) {return c * (t /= d) * t + b;},easeOut: function(t, b, c, d) {return -c *(t /= d)*(t-2) + b;  },easeInOut: function(t, b, c, d) {if ((t /= d / 2) < 1) return c / 2 * t * t + b;return -c / 2 * ((--t) * (t-2) - 1) + b;}}const Linear = function(t, b, c, d) { return c * t / d + b; }
  1. 粒子实现
    实现思路:
    希望在粒子管控组件时,使用new partical的方式创建粒子,每个粒子存在自己的动画开始方法,动画结束回调。
    由于评论列表可能存在数量巨大的情况,我们希望只全局创建有限个数的粒子,那么则提供呢容器移除粒子功能以及容器添加粒子的功能,实现粒子的复用

    partical_style.css

         //粒子充满粒子容器,需要容器存在尺寸以及relative定位.Boom-Partical_Holder{position: absolute;left:0;right:0;top:0;bottom:0;margin:auto;}

    particle.js

     import "partical_style.css";class Partical{// dom为装载动画元素的容器 用于设置位置等样式dom = null;// 动画开始时间StartTime = -1;// 当前粒子的动画方向,区别上抛运动与下抛运动direction = "UP";// 动画延迟delay = 0;// 三方向位移值targetZ = 0;targetY = 0;targetX = 0;// 缩放倍率scaleNum = 1;// 是否正在执行动画animating = false;// 粒子的父容器,标识此粒子被渲染到那个元素内parent = null;// 动画结束的回调函数列表animEndCBList = [];// 粒子渲染的内容容器 slotcon = null;constructor(){//创建动画粒子domthis.dom = document.createElement("div");this.dom.classList.add("Boom-Partical_Holder");this.dom.innerHTML = `<div class="Boom-Partical_con">Boom</div>`;}// 在哪里渲染renderIn(parent) {// dom判断此处省略parent.appendChild(this.dom);this.parent = parent;// 此处为初始化 slot 容器!this.con && ( this.con = this.dom.querySelector(".Boom-Partical_con"));}// 用于父容器移除当前粒子deleteEl(){// dom判断此处省略this.parent.removeChild(this.dom);}// 执行动画,需要此粒子执行动画的角度,动画的力度,以及延迟时间animate({ deg, pow, delay } = {}){// 后续补全}// 动画结束回调存储onAnimationEnd(cb) {if (typeof cb !== 'function') return;this.animEndCBList.push(cb);}// 动画结束回调执行emitEndCB() {this.dom.style.cssText += `;-webkit-transform:translate3d(0,0,0);opacity:1;`;this.animating = false;try {for (let cb  of this.animEndCBList) {cb();}} catch (error) {console.warn("回调报错:",cb);}}// 简易实现slot功能,向粒子容器内添加元素insertChild(child){this.con.innerHTML = '';this.con.appendChild(child);}}
    

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

  • 使用构造函数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

也就是说 我们可以知道一个方向上的力在XY轴的分量大小,
假设我们将 力 的概念 转化为 视图中 位移的概念,
我们将 力量1 记为 10vh的大小
于是我们可以定义全局变量

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

由此 我们补全 animate方法

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

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

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

然后执行刷帧操作 raf

raf(){// 正在执行动画    this.animating = true;// 动画开始时间this.StartTime = +new Date();let StartTime = this.StartTime;// 获取延时let delay = this.delay;// 动画会在延时后开始,也就是真正开始动画的时间let StartTimeAfterDelay = StartTime + delaylet animate = () => {// 获取从执行动画开始经过了多久let timeGap = +new Date() - StartTimeAfterDelay;// 大于0 证明过了delay时间if (timeGap >= 0) {// 大于Duration证明过了结束时间if (timeGap > Duration) {// 执行动画结束回调this.emitEndCB();return;}// 设置应该设置的位置的样式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)};`;}requestAnimationFrame(animate);}animate();
}

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

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

// 水平方向为匀速,所以使用Linear
moveX(currentDuration) {// 此处 * 2 是效果矫正后的处理,可根据自己的需求修改水平位移速度return Linear(currentDuration, 0, this.targetX, Duration) * 2;
}// 缩放 使用了easeOut曲线, 可根据需求自行修改
scale(currentDuration) {return Quad.easeOut(currentDuration, 1, this.scaleNum, Duration);
}// 透明度 使用了easeIn速度曲线,保证后消失
opacity(currentDuration) {return Quad.easeIn(currentDuration, 1, -1, Duration);
}// 竖直方向上位移计算
moveY(currentDuration) {let direction = this.direction;if (direction === 'UP') {// G用于模拟上抛过程的重力// 如果是上抛运动if (currentDuration < Duration / 2) {// 上抛过程 我们使用easeOut速度逐渐减小,我们让动画在一半时移到最高点return Quad.easeOut(currentDuration, 0, this.targetY + G, Duration / 2);}// 上抛的下降过程,从最高点下降return this.targetY + G - Quad.easeIn(currentDuration - Duration / 2, 0, this.targetY / 2, Duration / 2);}// 下抛运动直接easeInreturn Quad.easeIn(currentDuration, 0, this.targetY, Duration);
}

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

export default Partical; 

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

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

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

粒子管理 Boom.js

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

对于Boom.js的功能需求为

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

可达到效果:

  • 不关心业务,业务使用者传入每个粒子slot内容数组
  • 粒子组件可复用
  • 易于维护(可能是哈哈哈)
于是粒子管理器构架为:
import Partical from "partical.js";class Boom{// 实例化的粒子列表particalList = [];// 单次生成的粒子个数particalNumbers = 6;// 执行动画的间隔时间boomTimeGap = .1e3;boomTimer = 0;// 用户插入粒子的slot 的内容childList = [];// 默认旋转角度rotate = 120;// 默认的粒子发散范围spread = 180;// 默认随机延迟范围delayRange = 100;// 默认力度power = 3;// 此次执行粒子爆炸的是那个容器con = null;constructor({ childList , container , boomNumber , rotate , spread , delayRange , power} = {}){this.childList = childList || [];this.con = container || null;this.particalNumbers = boomNumber || 6;this.rotate = rotate || 120;this.spread = spread || 180;this.delayRange = delayRange || 100;this.power = power || 3;this.createParticals(this.particalNumbers);}setContainer(con){this.con = con;}// 创建粒子 存入内存数组中createParticals(num){for(let i = 0 ; i < num ; i++){let partical = new Partical();partical.onAnimationEnd(()=>{partical.deleteEl();});this.particalList.push(partical)}}// 执行动画boom(){// 限制动画执行间隔let lastBoomTimer = this.boomTimer;let now = +new Date();if(now - lastBoomTimer < this.boomTimeGap){// console.warn("点的太快了");return;}this.boomTimer = now;console.warn("粒子总数:" , this.particalList.length)let boomNums = 0;// 在内存列表找,查找没有执行动画的粒子let unAnimateList = this.particalList.filter(partical => partical.animating == false);let childList = this.childList;let childListLength = childList.length;let rotate = this.rotate;let spread = this.spread;let delayRange = this.delayRange;let power = this.power;// 每有一个未执行动画的粒子,执行一次动画for(let partical of unAnimateList){if(boomNums >= this.particalNumbers) return ;boomNums++;let r = Math.random();// 设置粒子父容器partical.renderIn(this.con);// 随机选择粒子的slot内容partical.insertChild(childList[Math.floor(r * childListLength)].cloneNode(true));// 执行动画,在输入范围内随机角度、力度、延迟partical.animate({deg: (r * spread + rotate) % 360,pow: r * power + 1,delay: r * delayRange,});}// 如果粒子树木不够,则再次创建,防止下次不够用if(boomNums < this.particalNumbers){this.createParticals(this.particalNumbers - boomNums);}}
}export default Boom;
使用demo

let boomChildList = [];for(let i = 0 ; i < 10; i++){let tempDom = document.createElement("div");tempDom.className = "demoDom";tempDom.innerHTML = i;boomChildList.push(tempDom);
}let boom = new Boom({childList: boomChildList,boomNumber: 6,rotate: 0,spread: 360,delayRange: 100,power: 3,
});

代码资源

源码网盘链接

组件效果预览

结尾

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

原生JS实现DOM粒子爆炸效果相关推荐

  1. 原生js实现canvas气泡冒泡效果

    说明: 本文章主要分为ES5和ES6两个版本 ES5版本是早期版本,后面用ES6重写优化的,建议使用ES6版本. 1, 原生js实现canvas气泡冒泡效果的插件,api丰富,使用简单 2, 只需引入 ...

  2. html制作翻页效果代码,使用原生JS实现滚轮翻页效果的示例代码

    一.滚轮事件 当用户通过鼠标滚轮与页面交互.在垂直方向上滚动页面时,就会触发mousewheel事件,这个事件就是实现全屏切换效果需要用到的.在IE6, IE7, IE8, Opera 10+, Sa ...

  3. 用原生JS实现3D轮播效果

    用原生JS实现3D轮播效果 实现思路: 先实现无缝轮播效果 添加3D效果 完善代码 增加自动轮播效果 效果如下: 视图中显示3张图片,并通过CSS的透视和旋转实现3D效果,当无任何操作时,图片自动循环 ...

  4. animate用法 js原生_原生js实现jq的animate效果.js

    原生js实现jq的animate效果(有一定bug未修复,有时间补上) //原生js实现jq的animate效果 /** *@param obj 执行动画的元素 *@param css JSON数值对 ...

  5. 史上最简单的原生JS实现轮播图效果

    原生JS实现轮播图效果 上篇文章我们说到了怎样利用原生JS实现Tab切换的效果,现在我们来说一下Tab切换的"升级版".如何利用原生JS实现轮播图效果.如图: HTML代码: &l ...

  6. 原生JS中DOM节点相关API合集

    原生JS中DOM节点相关API合集 节点属性 Node.nodeName //返回节点名称,只读 Node.nodeType //返回节点类型的常数值,只读 Node.nodeValue //返回Te ...

  7. Android 粒子爆炸效果,可以给任意 view 添加该效果

    ViewExplosion 项目地址:835127729/ViewExplosion 简介:Android 粒子爆炸效果,可以给任意 view 添加该效果 更多:作者   提 Bug   官网    ...

  8. three.js 实现图片粒子爆炸特效

    大家好,这里是 CSS 魔法使--alphardex. 以下是最终实现的效果图 撒,哈吉马路由! 准备工作 笔者的three.js模板:点击右下角的fork即可复制一份 世界同步 在我的上一篇博文中, ...

  9. 原生js 实现 文字向上翻动 效果, jquery效果实现的总是在一段时间后 速度变快

    https://blog.csdn.net/yunchong_zhao/article/details/106387004   之前曾经用jquery 做过一个类似的  但是 随着时间的进行 发动翻动 ...

最新文章

  1. MyBatis3 用log4j在控制台输出 SQL
  2. cesium 3dtiles 加载本地数据_Meteva笔记:加载本地观测数据
  3. Selenium Webdriver原理终于搞清楚了
  4. iOS应用代码注入防护
  5. linux strace 用法
  6. 微信小程序学习笔记(五)
  7. Wannafly挑战赛17 - 求值2 (逆元 + 杨辉三角公式)
  8. c++中的enum类型
  9. BZOJ3743 : [Coci2014]Kamp
  10. mybatis学习(26):插入功能(插入数据)
  11. STL源码剖析 map
  12. 「ubuntu」在Ubuntu Server 16.04 LTS下安装VMware Tools(转)
  13. 房贷利率有没有套路?用Python版解读!
  14. xp系统打印机服务器win7连接不了,xp连不上win7的打印机,win7连接打印机
  15. 对组件、模块、子系统、系统、框架、架构 定义浅析
  16. 大连新知源09年3月RHCE考试通过率90.9% —— 注重能力培养
  17. HTML入门学习线路图
  18. c语言注释不能嵌套什么意思,C语言嵌套注释问题
  19. 如何关闭台式计算机,台式机如何关闭显示器
  20. 项目风险管理十大黄金法则!高质量项目管理必杀技!

热门文章

  1. python 二次平滑_Python 指数平滑
  2. 【背井离乡,为何仍要打拼北上广?】
  3. 电脑按Fn加F1~F12才有用的解决方法
  4. GMT PST Beijing 时间
  5. java c语言语法对比_c语言和java语法有区分吗?_后端开发
  6. Spring中bean的含义是什么啊?
  7. 面试必问_你在开发过程中有没有遇到什么棘手的问题2,是怎么解决的、
  8. 字符串排序思路,或者叫做两个字符串比较大小
  9. Leap Motion架构
  10. 英语基础语法(10) 直接引语和间接引语