一、学习背景与效果
前些天翻大神blog,看到了张鑫旭大神写的一篇《canvas图形绘制之星空、噪点与烟雾效果》,深受启发。详细了研究了一下烟雾效果的代码,效果如下: 动态效果请点击。(!-- 去掉了原文中的背景图 --!)
效果图:
代码如下:
<!DOCTYPE html>
<html>
<head>
<title>canvas实现的烟雾缭绕效果</title>
<style>
.smoke {
height: 500px;
background: #fff;
background-size: cover;
position: relative;
}
.smoke canvas {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="main">
<h1>canvas实现的烟雾缭绕效果实例页面</h1>
<div id="body" class="light">
<div id="content" class="show">
<h3>展示</h3>
<div class="smoke">
<canvas id="smokeCanvas"></canvas>
</div>
</div>
</div>
</div>
<script>
// canvas烟雾缭绕效果
var canvasSmoke = function (canvas, options) {
var defaults = {
count: 30,
velocity: 2,
fps: 30,
url: 'smoke.png'
};
options = options || {};
// 参数合并
var params = {};
for (var key in defaults) {
params[key] = options[key] || defaults[key];
}
// 创建存储粒子的数组
var particles = [];
// 渲染的粒子数目
var particleCount = params.count;
// 每个方向的最大速度
var maxVelocity = params.velocity;
// 每秒多少帧
var targetFPS = params.fps;
// canvas元素
var eleCanvas = canvas;
if (!eleCanvas) {
return this;
}
// 画布的尺寸
var canvasWidth = eleCanvas.clientWidth;
var canvasHeight = eleCanvas.clientHeight;
eleCanvas.width = canvasWidth;
eleCanvas.height = canvasHeight;
// 创建图片对象
var imageObj = new Image();
// 一旦图像被下载,然后在所有的颗粒上设置图像
imageObj.onload = function () {
particles.forEach(function (particle) {
particle.setImage(imageObj);
});
};
// 烟雾图片地址
imageObj.src = params.url;
// 粒子实例方法
function Particle(context) {
// 设置初始位置
this.x = 0;
this.y = 0;
// 纵横速度
this.xVelocity = 0;
this.yVelocity = 0;
// 圆角大小
this.radius = 2;
// 存储上下文,绘制的时候需要
this.context = context;
// 绘制粒子的具体方法
this.draw = function () {
// 如果图片,则绘制
if (this.image) {
this.context.globalAlpha = this.alpha;
// 烟雾缭绕就看这里了
// 这是宽度,是动态的
var fillWidth = canvasWidth / 2,
fillHeight = fillWidth - fillWidth * (this.x / canvasWidth * this.y / canvasHeight);
this.context.drawImage(this.image, 0, 0, this.imageWidth, this.imageHeight, this.x, this.y, fillWidth,
fillHeight);
}
};
// 刷新粒子
this.update = function () {
// 改变粒子的
this.x += this.xVelocity;
this.y += this.yVelocity;
// 如果到了右边缘
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// 检测是否到了左边缘
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// 底边缘
if (this.y >= canvasHeight) {
this.yVelocity = -this.yVelocity;
this.y = canvasHeight;
}
// 是否上边缘
else if (this.y <= 0) {
this.yVelocity = -this.yVelocity;
this.y = 0;
}
// 越靠近边缘,透明度越低
// 纵向透明度变化要比横向的明显
this.alpha = (1 - Math.abs(canvasWidth * 0.5 - this.x) / canvasWidth) * (0.7 - Math.abs(canvasHeight *
0.5 - this.y) / canvasHeight);
};
// 设置粒子位置方法
this.setPosition = function (x, y) {
this.x = x;
this.y = y;
};
// 设置速度方法
this.setVelocity = function (x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function (image) {
this.imageWidth = image.width;
this.imageHeight = image.height;
this.image = image;
}
}
// 生成一个min,max大小之间的随机数
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// canvas上下文
var context;
// 初始化常见
function init() {
var canvas = eleCanvas;
if (canvas.getContext) {
// 绘图都需要这条语句
context = canvas.getContext('2d');
// 创建粒子,并设置他们的位置什么的,当然都是随机的
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// 随机位置
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(0, canvasHeight));
// 设置随机速度
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
}
}
}
// 初始化
init();
// 绘制方法
function draw() {
// 清除绘制
//context.fillStyle = "rgba(0, 0, 0, 0)";
context.clearRect(0, 0, canvasWidth, canvasHeight);
// 绘制所有粒子
particles.forEach(function (particle) {
particle.draw();
});
}
// 刷新
function update() {
particles.forEach(function (particle) {
particle.update();
});
}
// 开始绘制
if (context) {
setInterval(function () {
// 绘制前先更新位置什么的
update();
// 绘制
draw();
}, 1000 / targetFPS);
}
};
// IE9+烟雾效果走起
if ([].map) {
canvasSmoke(document.querySelector('#smokeCanvas'));
}
</script>
</body>
</html>
效果实现的好不好,关键的代码注释里面已经标出来了,就是这行:
fillHeight = fillWidth - fillWidth * (this.x / canvasWidth * this.y / canvasHeight);
大家进行试验的时候,可以试着修改一下fillHeight
的生成函数,产生的效果会很不同。为什么函数这样取值可以实现好的效果呢?
在坐标系中根据函数取点画图就可以明白,这个函数可以让烟雾图片的实例在面上更加连续的去展示。因为每个实例的取点都是伪随机的,所以很容易点都随机到一条线上,导致动画看起来不真实,即烟雾不是那么飘逸。按这个函数取值,可以大概率的保证烟雾实例的变化,在面上保证实例初始化后的连续性,让效果看着更加飘逸。
大神blog中的几个例子充分说明了,canvas绘图去实现动画效果好与差,与想象力紧密相关呀,数学具象化的能力很重要啊(努力提升中)。大家有时间可以多试试其他不同函数出现的效果,来加深影响。