摘要

       本文主要介绍一种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. Java Web项目(Extjs)报错六

    1.Java Web项目(Extjs)报错六 具体报错如下: usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ...

  2. Conditional Random Fields (CRF) 初理解

    1,Conditional Random Fields

  3. Linux查询一台机器的IP地址和其对应的域名

    Linux查询一台机器的IP地址和其对应的域名 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ nslookup > 127.0.0.1 Server: ...

  4. C# 利用Newtonsoft.Json进行Json序列化与反序列化

    我们可以通过DataContractJsonSerializer类来序列化一个对象为json字符串. public class JsonConvert<T> { public static ...

  5. freemarker.template.TemplateException:Error executing macro:mainSelect

    1.错误描述 freemarker.template.TemplateException:Error executing macro:mainSelect require parameter:id i ...

  6. FtpHelper ftp操作类库

    FtpHelper ftp操作类库 using System; using System.Collections.Generic; using System.Linq; using System.Te ...

  7. python version 2. required,which was not found in the registry 解决方案

    不能在注册表中识别python2.7 新建一个register.py 文件 import sys from _winreg import * # tweak as necessary version ...

  8. 谈下spring下的 aop日志记录

    在我们开发当中  我们需要对系统用户行为和 系统异常信息有个统一记录  以便后期的 用户行为分析和bug修复   当我们有这个需求时  我们的通常采取方式很多 1.比如我们定义一个规范 开发一个接口 ...

  9. Educational Codeforces Round 36 (Rated for Div. 2) E. Physical Education Lessons

    提供两种思路 一种线段树区间更新 另一种用map维护连续的区间,也是题解的思路 第二种很难写(我太渣,看了别人的代码,发现自己写的太烦了) #include<iostream> #incl ...

  10. 使用pyh生成HTML文档

    title: 使用pyh生成HTML文档 tags: [python3, 爬虫,pyh] date: 2018-03-09 21:01:34 categories: Python keywords: ...