HTML5 canvas创建粒子效果

原文地址:点击打开链接
Canvas为创建Web动态内容带来了激动人心的新方式。用Canvas,你可以在不用增加太多页面尺寸的条件下,仅仅用HTML和Javascript在web浏览器中创建引人瞩目的动画。
在这篇文章中,我将探索用Canvas创建粒子效果的几种方式。这些例子会绕着屏幕反弹并有重力效应。

Canvas标签

HTML5有很多标签和工具来让你创建一些神奇的交互内容并让这些内容动起来。这篇文章将把焦点放在Canvas标签以及你如何用JavaScript创建动态场景上。

2004年Canvas标签被引入的时候,它最初被设计使用于基于webkit内核的Safari浏览器。在它被火狐和Opera浏览器采纳后,它变成了web标准,现在你可以自信地在所有主流浏览器中使用。

Canvas经常被作为基于HTML的WEB游戏的基础,因为它包含了web页面中的一个独立区域,在这个区域中你可以绘画一些动画图形,响应用户输入,增添页面的交互性。

推荐:Zevan Rosser的视频教程

如果你喜欢看教程,最好的介绍文章之一是由Zevan Rosser引入的。我很感谢Zevan对Canvas出色的介绍让我开始了解Canvas。下面的很多例子以及Particle原型的扩展,都是基于他的介绍。

遵循规范

我们通过一步步的建立最终效果以及使用Canvas,你会发现每个例子都是一个在线的代码集合,我会把每个例子链接到可以在线编辑的例子的地址。你可以从Github上下载。

一个空的Canvas

因为Canvas被大多数的浏览器支持,没必要引入额外的插件来让他能正常工作。下面的代码足以告诉浏览器页面中有一个Canvas:
<canvas id="example" width="200" height="200">
  This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
你也可以使用JavaScript在页面中插入一个Canvas:
<script>
  var canvas = document.createElement("canvas");
  var context = canvas.getContext("2d");
  canvas.width = 200;
  canvas.height = 200;
  document.body.appendChild(canvas); 
</script>

你在页面中看不到任何东西!这是对的。下一步我们就在Canvas上画一个有颜色的矩形。

继续我们上面的JavaScript代码,我们可以充分的使用通过getContext()方法获取的context。这个context对象被用于在Canvas元素上的所有绘画。比如,你可以像这样画一个黑色的矩形:
context.fillRect(0, 0, canvas.width, canvas.height);

fillRect方法会在canvas上画一个背景色填充的矩形。它被设置为使用X,Y像素值作为起始坐标,然后画出形状。从这一点上来讲我们从左上角(00)坐标开始并画出了一个和canvas有同样宽和高的矩形。
这个例子可以在 examples/01-canvas.html的文件中找到。

引入一个粒子

我们有了一个被画于页面中的空的Canvas,现在是时候做更多的绘制了。我们以被我称为粒子的小矩形开始。
// Draw a square particle on the canvas
context.fillStyle = "white";
context.fillRect(300, 200, 10, 10);

当fillStyle属性没被指定的时候,其默认值为黑色。那么在告诉Canvas绘制一个矩形前,我们将其设置为白色。这个矩形这一次被绘制在了离左边300px,离上边200px的位置,10px长10px宽。

我们也可以绘制一些其他形状,Canvas有一个arc函数,可以绘制弯曲的形状,我们可以用该函数来绘制圆形粒子。
// Draw a circle particle on the canvas
context.beginPath();
context.fillStyle = "white";
// After setting the fill style, draw an arc on the canvas
context.arc(500, 200, 10, 0, Math.PI*2, true); 
context.closePath();
context.fill();

以上代码绘制了一个圆,我们绘制了一个路径并使用arc方法达到了此目的。arc方法的参数格式如下:
content.arc(x, y, radius, startAngle, endAngle, anticlockwise)

我们为arc方法提供了绘制开始点,圆的半径,并给出了开始和结束的角度来绘制。如果你想学习更多关于用JavaScript来绘制圆的内容,可以再这里找到很好的互动教程。
这个例子被放在examples/02-particle.html

让粒子移动

在Canvas上移动对象跟你之前移动HTML元素是有很大不同的。不是在页面上移动一个已经存在的对象,在Canvas上移动元素的解决方案是擦除并重新绘制元素。
为了说明这一点,我们开始移动粒子并多次的重新绘制它。

首先,我们建立在X、Y轴上的起始坐标位置作为变量:
var posX = 20,
    posY = 100;

然后画一个粒子,调整位置并重复:
// Draw shapes on the canvas using an interval of 30ms
setInterval(function() {
  posX += 1;
  posY += 0.25;

  // Draw a circle particle on the canvas
  context.beginPath();
  context.fillStyle = "white";
  // After setting the fill style, draw an arc on the canvas
  context.arc(posX, posY, 10, 0, Math.PI*2, true); 
  context.closePath();
  context.fill();
}, 30);

这个例子在examples/03-movement-a.html

因为我们只是在原来的Canvas的顶部简单的重复绘制粒子,我们创建了一条粒子线。这的确不是我们想要的,那么让我们每次都擦除画布。调整setInterval函数的头部来包含这些线:
setInterval(function() {
  // Erase canvas
  context.fillStyle = "black";
      context.fillRect(0,0,canvas.width, canvas.height);
...

这会导致Canvas每次都重新绘制,创建出粒子穿过画布的效果。这个例子在examples/03-movement-b.html
基本的想法就是,我们可以在Canvas上绘制粒子,当我们想让粒子运动的时候我们就重新绘制空的画布,调整粒子的位置并重新绘制粒子。

我们可以扩展这项技术来做更多有趣的效果。

物理学&随机性

在前面的示例中,我们每次调整粒子的位置是通过其每次在绘制的时候增加X和Y的坐标值来实现的。因为我们每次都增加相同的值,所以粒子在一条直线上运动。
通过引入速率和重力作用来重新计算X和Y的值,我们可以调整粒子运动的方式。X和Y描述了一个粒子的位置,粒子的速率则描述了粒子在X和Y坐标运动的速度改变率。我们将其描述为VX和VY:
// Initial velocities
var vx = 10,
    vy = -10,
    gravity = 1;

初始化的速率意味着每一次粒子被绘制的时候,它会水平移动10px,向下移动10px。因为我们用变量来设置这些值,我们可以通过setInterval函数来调整它们的位置:
posX += vx;
posY += vy;
vy += gravity;

每一次粒子被绘制的时候,我们将vx的值增加到其X坐标上,对应的vy增加到Y坐标上。
粒子在Y轴上以负值开始,沿屏幕向上运动。因为我们将gravity值增加到Y速率上,离子运动速度逐渐变慢直到为0,然后持续增加Y轴运动速率。
你可以在 examples/04-gravity-a.html看到以上的例子。

弹跳

添加一点逻辑,我们可以让粒子进行反弹而不是直接从屏幕上跌落。与其简单的调整Y轴坐标位置,我们可以测试,如果粒子到了一定高度我们就让粒子进行反弹。
if (posY > canvas.height * 0.75) {
  vy *= -0.6;
  vx *= 0.75;
  posY = canvas.height * 0.75;
}

这样,我们就可以测试来看看,如果离子的高度比canvas的高度的75%高的话会则么样。如果是这样,它会将Y轴的速率乘以-0.6.这会有两个效果,一个是将粒子从向下运动变为往上运动,同时其运动速率也减少40%。如果一个粒子在Y轴上的运动速率为10,此时它会变为-6,。
下次粒子被绘制的时候,会往上运动6px。
同时,X轴的速率减少25%。这会减慢粒子在屏幕上横向运动的速度。
因为我们仍然使用gravity来调整粒子在Y轴上的速率,负值将会再次接近0并变为正直,从而有了反弹效果。
你可以在 examples/04-gravity-b.html看到例子。

随机性

目前为止,我们创建了很多粒子,让他们移动,并添加了一些重力方程。接下来我们将扩展我们的例子,生成更多的粒子,并给他们的运动增添随机性。

生成粒子

目前为止我们操作的都是一个粒子,我们需要扩展我们的代码来生成更多的粒子。
要达成我们的目的,我们需要创建一个叫做Particle的函数以便我们在需要的时候调用,每次调用我们都会生成一个粒子。他会作为粒子生成器允许我们生成很多粒子,粒子独立的移动,并在不影响其他粒子的情况下移除粒子。

但是首先,我们想在这个粒子函数中重用一些设置,因此我们将设置项置为一个对象:
var particles = {},
    particleIndex = 0,
    settings = {
      density: 20,
      particleSize: 10,
      startingX: canvas.width / 2,
      startingY: canvas.height / 4,
      gravity: 0.5,
      maxLife: 100
    };

我们初始化了一个空的particles对象,来记录生成的粒子们,然后设置一了些我们会在函数中用到的值。
这些设置在后面我们增添更多的交互性以及允许在移动的过程中做出改变时会很有用,那么现在,我们先给出生成一个粒子的构造函数:
function Particle() {
  // Establish starting positions and velocities
  this.x = settings.startingX;
  this.y = settings.startingY;

  // Random X and Y velocities
  this.vx = Math.random() * 20 - 10;
  this.vy = Math.random() * 20 - 5;

  // Add new particle to the index
  particleIndex ++;
  particles[particleIndex] = this;
  this.id = particleIndex;
  this.life = 0;
}

这个函数取settings中的值(使用this)来设置它的的X,Y坐标值。X,Y轴上的速度使用JavaScript的Math.random()方法来生成。在起始位置和速度生成后,然后它增加游标值(particleIndex),然后利用该游标值将该粒子对象放置进particles对象。
与其用数组来盛放粒子集合,用一个对象的键值对来管理会更简单,因为后面用它来删除粒子会更简单。
最后我们将粒子的life值置为0.这是一个我们后面用来决定粒子什么时候小时的计数器,以免整个屏幕被粒子塞满。

绘制大量的粒子

目前为止,我们已经有了一个包含很多粒子的对象。我们需要创建一个方法在屏幕上绘制这些粒子,然后根据设置的速度值来更新它们的位置。
为了在屏幕上绘制粒子,我们可以扩展Particle函数,来增加一个draw方法:
Particle.prototype.draw = function() {
  this.x += this.vx;
  this.y += this.vy;

  // Adjust for gravity
  this.vy += settings.gravity;

  // Age the particle
  this.life++;

  // If Particle is old, remove it
  if (this.life >= settings.maxLife) {
    delete particles[this.id];
  }

  // Create the shapes
  context.clearRect(settings.leftWall, settings.groundLevel, canvas.width, canvas.height);
  context.beginPath();
  context.fillStyle="#ffffff";
  context.arc(this.x, this.y, settings.particleSize, 0, Math.PI*2, true); 
  context.closePath();
  context.fill();
}

这个方法做了几件事情。首先,它通过增加粒子在某个位置上的速度来调整粒子的速度。因为速度值是随机生成的,导致粒子在两个坐标轴上都随机的运动。

Y轴上会像以前一样被重力调整速度。然后我们会增加粒子的life值来记录粒子的年龄。当粒子的年龄比maxLife大的时候,他会被移除。
如果它没有被移除,我们会在canvas上绘制该粒子。

这样做的结果就是一个看起来随机的喷射状的粒子,在屏幕上因重力效果而下落。
效果可以在examples/05-multiple.html看到。

借助 gravity example earlier中的很多想法,我们可以为我们的例子增添墙来让粒子做反弹。
在settings中添加一些墙被放置的位置的值:
settings = {
  ...
  groundLevel: canvas.height * 0.75,
  leftWall: canvas.width * 0.25,
  rightWall: canvas.width * 0.75
};
我们在settings中添加了一些额外的值。一个地面,左边的墙和右边的墙代表着画布向下3/4,从左边1/4到右边3/4的位置。当绘制粒子的时候我们可以检测粒子的位置值是否大于这些边界值,粒子撞墙的时候我们就让粒子反弹。
让我们增添几行代码到draw方法中检测与这些墙的碰撞:
Particle.prototype.draw = function() {
  ...
  // Bounce off the ground
  if ((this.y + settings.particleSize) > settings.groundLevel) {
    this.vy *= -0.6;
    this.vx *= 0.75;
    this.y = settings.groundLevel - settings.particleSize;
  }

  // Determine whether to bounce the particle off a wall
  if (this.x - (settings.particleSize) <= settings.leftWall) {
    this.vx *= -1;
    this.x = settings.leftWall + (settings.particleSize);
  }

  if (this.x + (settings.particleSize) >= settings.rightWall) {
    this.vx *= -1;
    this.x = settings.rightWall - settings.particleSize;
  }

  // Adjust for gravity
  this.vy += settings.gravity;

  // Age the particle
  ...
}

这里有三个比较。首先,检测粒子的Y坐标值是否比地面的最大水平值大,在这种情形下,粒子的坐标值从画布的顶部开始算起,一个很大的值会意味着离屏幕顶部很远。
我们也将粒子的尺寸计算在内,因为Y值是从左上角开始记起的。通过加上粒子的尺寸,我们阻止了粒子的下面会超出墙的情形。

绘制墙

因为这里涵盖粒子的反弹效果,我们通过绘制墙来让效果看起来更好。调整setInterval函数来增添一下内容:
setInterval(function() {
  ...
  // Draw a left, right walls and floor
  context.fillStyle = "white";
  context.fillRect(0, 0, settings.leftWall, canvas.height);
  context.fillRect(settings.rightWall, 0, canvas.width, canvas.height);
  context.fillRect(0, settings.groundLevel, canvas.width, canvas.height);
  ...

我们以当初设置的墙的位置绘制了三个白色的矩形。

以上的例子可以在examples/06-walls.html看到。

调整设置

因为我们用了一个settings对象来存放所有设置,我们可以很容易的调整他们并在canvas上看到效果。如果你进到后面的任何一个例子中查看,这是一种很不错的方式来体验和查看值的改变时如何影响效果的。

我们可以更深一步,使用一个很棒的叫做dat.gui的插件来使的改变设置变得更容易。
使用dat.gui只需要简单地引入JavaScript插件,并告诉他我么还要设置的值。首先,我们从一个CDN来调用该文件:
<script src="http://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>

引入之后,我们可以初始化它并告诉它我们要使用的设置:
var gui = new dat.GUI();

一个gui对象被new dat.GUI初始化,这样每个变量设置都可以被增添。使用如下的结构来添加:
gui.add(object, 'property', values)

这种情形下,object是一个包含之前设置的值的JavaScript对象。对象内的一个属性被指定,最后一个可能使用的值的范围也被指定。在我们的例子中我们制定一系列的设置值如下:
gui.add(settings, 'density', 0, 5 );
gui.add(settings, 'particleSize', 1, 50 );
gui.add(settings, 'gravity', -2, 2 );
gui.add(settings, 'startingY', 0, canvas.height * 0.75 )
gui.add(settings, 'groundLevel', 0, canvas.height );
gui.add(settings, 'leftWall', 0, canvas.width * 0.4);
gui.add(settings, 'rightWall', canvas.width * 0.6, canvas.width);
gui.add(settings, 'blur', 0.1, 0.9);

遍历这个列表我们可以发现变量的项反映在settings对象中。粒子的密度值在0-5之间,其他的设置包含了粒子的尺寸,墙的位置和其他设置值。
更多的例子可以在dat.gui官网看到。

进一步探索

模糊化

如果你有仔细阅读你会发现前面的代码有个blur设置。你可以调整绘制在画布上的颜色的透明度。

用RGBA,我们可以调整alpha部分产生绘制在场景上的轻微透明的块。
如果alpha值很小,它需要更多的重绘来完全隐藏粒子,结果就是像以前版本的模糊粒子滑过。

the settings example的例子中设置布blur的值,来看看它是如何改变场景的外观的。

使用背景图片


与其在平坦的背景上绘制普通的粒子,我们可以使用背景图片来绘制更有趣的场景。
目前为止,我们都是通过在canvas上绘制黑盒子来重绘场景,使替代使用clearRect来擦除画布的方案是可能的:
context.clearRect(0, 0, canvas.width, canvas.height);

这会维持透明的背景,并允许任何在canvas后面的内容来展示。
你可以添加背景图片并展示层绘画效果:
fountain_small

可以在 fountain example live here查看效果。

在这个例子中,我引入了亚利桑那大学的 Garry Forger拍摄的一张很棒的图片,亚历山大·伯杰纪念喷泉

引发的想法

画布是一个伟大的方式来在web页面中创建独立的动画内容,而不需要任何额外的插件或下载。虽然浏览器不是100%支持,现阶段,我们可以自信地开始使用这项技术,寻找方法可以为我们的项目添加额外的价值。
我希望这个介绍画布和粒子的文章会激发你自己的一些想法。我期待着看你能创建什么很棒的东西并分享出来。
如果你希望学习更多关于canvas的东西, The Expressive Web网站对于该主题有很棒的概述和介绍。


阅读更多
文章标签: 前端
个人分类: web前端 算法
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭