摘要

       本文主要介绍一种WEB形式的烟花(fireworks)效果(图1所示),该效果基于Canvas实现,巧妙地运用了canvas绘图的特性,并加入了物理力作用的模拟,使整体效果非常绚丽、逼真。本文从本质上介绍了其实现原理,便于其他可视化爱好者能快速上手。本文从视觉渲染和运动轨迹模拟两个方面详细描述了该效果的实现原理及细节。

在线效果和代码在这里,可点击链接到Codepen查看

图1 - Canvas烟花效果截图

引言

“东风夜放花千树。更吹落、星如雨。”——青玉案·元夕。烟花的耀眼只是一瞬,但人们追求美的心却是永恒。本文所要介绍的效果,是我所见过的最美丽、最符合人的精神图像的作品。

网上还有一种类似于火星迸发的线条很细的模拟烟花效果,我觉得比本文这一款差多了。如果作为烟花爆竹出售,我感觉它们是一块钱和十块钱效果的差别,这是一个不恰当的比喻。

  烟花效果的逼真在于其颜色的多彩变幻和运行轨迹的合理性。从现象上看,一道光柱逐渐上升,到达一定高度时停止,同时出现多条弧形的流星效果并逐渐下降。随着烟花绽放的时间增加,其颜色逐渐暗淡,最终消逝不见。从本质上来看,它是一系列头部颜色最亮、尾部越来越暗,且轨迹越来淡的线条。从外表上来看,它爆炸前是一条直线,爆炸后变成多条曲线。

  本文所描述的实现方式巧妙地利用了Canvas绘图的技巧,通过合理的重绘和清除策略,完美地模拟了烟花绽放。

正文

  一、颜色渲染

  虽然其实质是线条的渲染,但对于烟花来讲,我们最直观的感觉是有一个实物飞上天,然后有很多小的实物因爆炸而散落下降。因此我们可在画布上根据轨迹不断绘制小圆点,圆点因连续运动而形成线条。再通过不断重绘画布,用带有一定透明度的背景色来渲染画布整个区域,并且保证新绘制的和原来绘制的图形都存在,即可达到轨迹颜色逐渐暗淡的效果。

  为了清晰描述这一过程,此处把该部分功能独立出来,我们单独来看看这个线条效果是怎么来的。

  如图2所示,为了方便查看,我们把线条放大,节奏放慢。可点击链接到Codepen查看源码

  

图2 - 线条渲染

//// In loop update
    context.fillStyle = "rgba(0, 0, 0, 0.05)";
    context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); //// draw on canvas
this.render = function(){
if(this.end)return;
var c = context;
c.save();
c.globalCompositeOperation = 'lighter'; var x = this.pos.x, y = this.pos.y, r = this.size;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + 1 + ")");
gradient.addColorStop(1, "rgba(0, 0, 0, " + 1 + ")"); c.fillStyle = gradient;
c.beginPath();
c.arc(x, y, r, 0, Math.PI * 2, true);
c.closePath();
c.fill(); c.restore();
};

  

  如代码标注部分(行2、10、13、19)所示,有四点主要内容:画圆、颜色径向渐变、绘图方式、重绘。

  1、画圆。  x, y代表运行轨迹的当前坐标。控制运动的代码部分只需要负责计算并更新x和y的值。渲染的代码只负责在该坐标点画圆并填充。

  2、颜色径向渐变。圆点的填充颜色,其中 createRadialGradient() 函数的第1、2个参数与第4、5个参数分别表示渐变开始和渐变结束的圆心坐标。在这里设置为相同的值,使颜色过渡效果更自然真实。

  3、绘图方式。Canvas的 globalCompositeOperation 属性决定下一次绘图时如何将一个源(新的)图像绘制到目标(已有)的图像上。此处设置为 'lighter' 使同时显示源图像和目标图像,否则会影响到循环中的画布重绘。

  4、重绘。使用 fillRect() 函数对整个画布进行清除,同时设置 fillStyle 属性为带一定不透明度的背景色。如此每一次清除画布将使原有的图形变得暗淡,随着时间流逝,而最终消失在夜幕背景下,或被最新的烟花轨迹覆盖。

  以上几点是此次烟花渲染的核心之处,它巧妙运用了Canvas绘图的特点,完美地用动画仿真了烟花的绽放。

  二、轨迹模拟

  烟花轨迹模拟的核心是模拟物体在物理力作用下的运行状态。在此我们主要考虑方向速度重力的影响。烟花在爆炸时向四面八方散开,体现在2D画布上就是0~360度随机方向。我们假想在爆炸的一瞬间冲击力最大,速度最大,然后受阻力影响速度逐渐减小。此外我们需要考虑重力的影响,使其最终大致呈自由落体运动。

  为了纯粹演示运动轨迹,我们把该部分逻辑独立出来,用d3.js模拟整个爆炸过程。

  如图3所示,可点击链接到Codepen查看源码

图3 - 轨迹模拟

图4 - 轨迹瞬间

  接下来我们直接看核心源码:

//// 创建模型列表
function create(){
//// 初始化
circles = [];
for (var i = 0; i < count; i++) {
var particle = new Circle(objs[i], pos[0], pos[1]);
var angle = Math.random() * Math.PI * 2; var speed = Math.cos(Math.random() * Math.PI / 2) * 15; particle.vel.x = Math.cos(angle) * speed;
particle.vel.y = Math.sin(angle) * speed; particle.size = 10; particle.gravity = 0.2;
particle.resistance = 0.92;
particle.shrink = Math.random() * 0.05 + 0.93; circles.push(particle);
}
} //// In Model Class
//// 模型参数更新
this.update = function(){
this.vel.x *= this.resistance;
this.vel.y *= this.resistance;
// gravity down
this.vel.y += this.gravity; this.pos.x += this.vel.x;
this.pos.y += this.vel.y; // shrink
this.size *= this.shrink; this.move();
};
//// 实体位置移动
this.move = function(){
this.object.attr('cx', this.pos.x)
.attr('cy', this.pos.y)
.attr('r', this.size)
;
}

  我们抽象出一个物体模型,它的主要属性是pos(当前位置)、vel(方向参数)、resistance(阻力作用参数)、gravity(重力系数),主要方法是update() 和 move()。可在Codepen查看完整源码。

  以上代码中, create() 是一个初始化所有爆炸物的方法,总数量count设置为80左右的随机数,objs是用d3.js添加的svg上的元素集合(在此我们使用圆形circle),模型类的每一个实例的‘object‘属性对应一个画布上的元素,在此根据分层思想把update()和move()分开,前者负责逻辑,后者负责效果。

  在create里初始化了模型的一系列参数,其中重要的是移动方向,我们逐条来分析:

  var angle = Math.random() * Math.PI * 2;    不难理解,就是角度取 0 ~ 360 度的随机一个方向。取值在0 ~ 6.28之间。

  var speed = Math.cos(Math.random() * Math.PI / 2) * 15;    其中括号内的取值在 0 ~ π/2 之间,我们知道在此区间上cos函数的值为正,值域在0 ~ 1之间,故speed的取值在0 ~ 15之间。

  particle.vel.x = Math.cos(angle) * speed;   区间0 ~ 2π上,cos函数的取值是-1 ~ 1之间,正负概率均等,参考图5。

  particle.vel.y = Math.sin(angle) * speed;  区间0 ~ 2π上,sin函数的取值是-1 ~ 1之间,正负概率均等。 

  因此,方向偏移变量vel分布在二维坐标轴的四个象限,各爆炸物的初始坐标离爆炸点的横纵距离随机分布在0 ~ 15之间。

图5 - 三角函数曲线

  在update()函数里,修改方向偏移变量,使力度逐渐衰减,并加入重力影响,然后修改物体的当前位置坐标,以及逐渐减小物体在视觉范围内的尺寸。

  

  this.vel.x *= this.resistance;  resistance 的赋值是0.92,故偏移量每次以92%的比例衰减。因此在效果图中我们看到,物体完全自由落体前移动速度有越来越慢的缓冲效果,类似于cubicOut缓冲。
  this.vel.y *= this.resistance;  

  this.vel.y += this.gravity;   gravity是重力系数,所以运动时需要每次沿y轴下方适当偏移。当vel变量的衰减殆尽时,只受重力作用影响,就呈直线下降状态。(此处物理专业的朋友禁止较真!)

  this.pos.x += this.vel.x;     物体位置信息更新。
  this.pos.y += this.vel.y;

  this.size *= this.shrink;     物体尺寸大小衰减。

  this.move();         视图层移动。

  在这个简单的轨迹模拟程序里,我们直接使用 setInterval() 函数来实现动画刷新,也可以使用 requestAnimationFrame() 函数显得更正式些。

  至此,我们的物体物理运动轨迹模拟完成,如图3所示,尽管没有什么渲染的成分,看起来依然有烟花绽放的感觉。

总结

   本文介绍了一种基于Canvas的烟花效果实现方式,该方式巧妙地利用了Canvas渲染的特性,其动画受浏览器性能的影响较小,并加入物理力作用效果,使动画整体看起来很逼真,效果很绚丽。本文详细介绍了其原理及实现细节,有助于其他有相似需求的开发者能迅速使用并改进,实现更完美的作品。

其它

  在此声明,本文所述的方法非本人原创,也没有找到原作者,在此鸣谢!

  基于以上内容的介绍,有兴趣的开发者可以使用d3js框架实现整套的完整效果。

  若您发现本文所述有失偏驳之处,或有待改进之处,或您有其它想法、意见及建议,请在评论区留言,谢谢!

WEB烟花效果——Canvas实现的更多相关文章

  1. 还在用canvas画格子吗?文字烟花效果更不错噢

    大家好,我是小丞同学,一名前端爱好者 欢迎访问博主的个人网站:一口奶盖 "在人间贩卖声音 等凑够满天星辰 放烟花给你看" 上次的烟花有些许平淡,这次来放大招了,让你的名字在天空绽放 ...

  2. Android开发——为EditText添加烟花效果的实现

    )什么时候发射烟花:监听EditText的文字改变,获取文字数量的变化以确定风的方向,还有获取光标的位置确定爆炸的位置.光标的位置没有具体的方法确定坐标,要通过反射自己计算. 2.  主要实现类 库里 ...

  3. 外媒速递:十大最佳心理学概念助你提升Web设计效果

    外媒速递是核子可乐精选的近日国外媒体的精彩文章推荐,希望大家喜欢! 本期给大家推荐的是帮助你提升Web设计效果的十大最佳心理学概念.改善企业云环境协作效率的九款卓越工具.选择移动应用开发工具时要考虑的 ...

  4. >炫酷的计时器效果Canvas绘图与动画<

    >炫丽的计时器效果Canvas绘图与动画< 虽然我是学习java的,但是因为最近使用html5的关系,多学习了一下前端知识. 现在,我要介绍的计时器是十分炫酷的,使用画布完成. 喜欢htm ...

  5. 『HTML5梦幻之旅』-缤纷多姿的烟花效果

    天花无数月中开,五采祥云绕绛台.堕地忽惊星彩散,飞空旋作雨声来.怒撞玉斗翻晴雪,勇踏金轮起疾雷.更漏已深人渐散,闹竿挑得彩灯回. ——明·瞿佑·<烟火戏> 记得每年过春节的那段时间,除了欣 ...

  6. 6 cocos2dx粒子效果,类图关系,系统原生粒子和自己定义粒子效果,粒子编译器软件,爆炸粒子效果,烟花效果,火焰效果,流星效果,漩涡粒子效果,雪花效果,烟雾效果,太阳效果,下雨效果

     1 粒子 演示样例 2 类图关系 3 系统原生粒子 CCParticleSystem 全部粒子系统的父类 CCParticleSystemPoint. CCParticleSystemQuad ...

  7. 【技术博客】在Unity3d中实现烟花效果

    在游戏开发中,我们经常需要用到类似烟花的效果.在Unity3d中,实现烟花效果的方法不止一种,我选用了Unity3d中新添加的粒子特效工具--visual effect graph来进行实现. 实现过 ...

  8. canvas制作的烟花效果

    最近感觉canvas挺有意思的,在业余时间没事研究了一下,参考过网上一些思路,话不多说,开始啦. github地址:https://github.com/aWhiteBear/fireworks 演示 ...

  9. canvas 实现烟花效果

    一:创建画布 <canvas width="600" height="600" id="canvas" style="bor ...

随机推荐

  1. Android常见Crash原因总结(二)

    Android平台程序崩溃大家都应该遇到过,force close和ANR应该是大家遇到较多的. 这里把Android平台程序崩溃的各种类型做一个简述和原因列举. 1.ANR(可见ANR): 发生场景 ...

  2. hi3531的i2c部分

    一.关于编译Hi3531 SDK: 之前编译SDK时编译到make uImage总出错,一是找不到.config文件,这个问题是必须先make menuconfig 然后保存.config文件. 二是 ...

  3. MFC中CFileDialog用法

    用CFileDialog选择了一个文件后,使用FILE::fopen打开文件错误,使用 的是相对地址,和王工调试了半天,怎么跟踪也没发现错误,原来如此......... CFileDialog文件选择 ...

  4. 在.Net Core中使用MongoDB的入门教程(二)

    在上一篇文章中,讲到了MongoDB在导入驱动.MongoDB的连接,数据的插入等. 在.Net Core中使用MongoDB的入门教程(一) 本篇文章将接着上篇文章进行介绍MongoDB在.Net ...

  5. 获取MySql每一列的数据类型和长度默认值等信息

    如何获取MySql表中各个列的数据类型? show columns from tablename 返回结果如下: id    int(11)     NO  PRI         auto_incr ...

  6. JDBCTemplate简化JDBC的操作(二)

    一.Spring对不同的持久化支持: Spring为各种支持的持久化技术,都提供了简单操作的模板和回调 ORM持久化技术 模板类 JDBC org.springframework.jdbc.core. ...

  7. STM32F4使用FPU+DSP库进行FFT运算的测试过程一

    测试环境:单片机:STM32F407ZGT6   IDE:Keil5.20.0.0  固件库版本:STM32F4xx_DSP_StdPeriph_Lib_V1.4.0 第一部分:使用源码文件的方式,使 ...

  8. 谈谈在.NET Core中使用Redis和Memcached的序列化问题

    前言 在使用分布式缓存的时候,都不可避免的要做这样一步操作,将数据序列化后再存储到缓存中去. 序列化这一操作,或许是显式的,或许是隐式的,这个取决于使用的package是否有帮我们做这样一件事. 本文 ...

  9. Emacs配置(考场必备)(Emacs)

    最近有几次离开自己一直坐着的座位,去别的机房考试了. 于是猛然想起来要记一记Emacs的简洁配置了. 算是把NOIP残存的记忆再拾一点起来...... 附上一些解释 (global-set-key [ ...

  10. Java 对IP请求进行限流.

    高并发系统下, 有三把利器 缓存 降级 限流. 缓存: 将常用数据缓存起来, 减少数据库或者磁盘IO 降级: 保护核心系统, 降低非核心业务请求响应 限流: 在某一个时间窗口内对请求进行限速, 保护系 ...