效果查看:
http://hovertree.com/texiao/html5/47/
HTML5 Canvas情感粒子表情特效是一款比較有趣的生氣,開心,發怒,難受,傷心等表情動畫。canvas元素是HTML5新增的元素,允許腳本語言動態渲染繪制圖形,可以用canvas元素畫圖、合成圖像或者制作動畫。canvas最早在蘋果公司的Mac OS X Dashboard上被引入,後來被內置與Safari浏覽器內。之後在Firefox、Opera、Chrome的推動下,W3C將canvan納為HTML5的一部分。
練習題:
http://hovertree.com/shortanswer/bjae/rutxus63.htm
http://hovertree.com/tiku/bjaf/4tlxjl9k.htm
代碼如下:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>HTML5 Canvas情感粒子表情特效 - 何問起</title>
<script type="text/javascript" src="http://down.hovertree.com/jquery/jquery-1.10.2.min.js"></script>
<style>
html, body {
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
background-color: #000000;
}
.container {
width: 100%;
height: 100%;
margin: 0;
}
.hovertreeframe {
width: 168px;
overflow: hidden
}
.hewenqiplay {
width: 200px;
}
</style>
</head>
<body>
<div style="position:fixed;color:white">
Canvas實現豐富的表情包 <a href="http://hovertree.com/h/bjag/u908oa4r.htm" style="color:#123d90">原文</a>
<div class="hovertreeframe"><audio src="http://down.hovertree.com/hovertreesound/hovertreerenjian.mp3" autoplay="autoplay" controls="controls" class="hewenqiplay"></audio></div>
</div>
<div id="jsi-particle-container" class="container"></div>
<script>
var RENDERER = {
init : function(){
this.setParameters();
this.reconstructMethods();
this.createParticles();
this.render();
},
setParameters : function(){
this.$container = $('#jsi-particle-container');
this.width = this.$container.width();
this.height = this.$container.height();
this.context = $('<canvas />').attr({width : this.width, height : this.height}).appendTo(this.$container).get(0).getContext('2d');
this.particles = [];
},
reconstructMethods : function(){
this.render = this.render.bind(this);
},
createParticles : function(){
for(var i = 0, length = Math.round(this.width / 500 * this.height / 500 * 10); i < length; i++){
this.particles.push(new PARTICLE(this.width, this.height));
}
},
render : function(){
requestAnimationFrame(this.render);
this.context.clearRect(0, 0, this.width, this.height);
for(var i = 0, length = this.particles.length; i < length; i++){
this.particles[i].render(this.context);
}
this.checkCollision();
},
checkCollision : function(){
for(var i = 0, particleCount = this.particles.length; i < particleCount; i++){
var particle = this.particles[i];
for(var j = i + 1; j < particleCount; j++){
this.particles[j].checkCollision(particle);
}
}
}
};
var PARTICLE = function(width, height){
this.width = width;
this.height = height;
this.init();
};
PARTICLE.prototype = {
COLOR : 'hsl(%h, 80%, %l%)',
RADIUS : {MIN : 25, MAX : 35},
MASS_RATE : 0.0001,
VELOCITY : {MIN : 2, MAX : 4},
WALL_RESTITUTION : 1.0,
PARTICLE_RESTITUTION : 1.0,
MAX_FACE_INDEX : 4,
DELTA_SCALE : 0.01,
INIT_RADIAN : 220,
DELTA_RADIAN : {MIN : 4, MAX : 8},
DELTA_THETA : Math.PI / 10,
init : function(){
this.radius = this.createRandomValue(this.RADIUS);
this.mass = Math.round(Math.pow(this.radius, 3) * this.MASS_RATE);
this.x = this.createRandomValue({MIN : this.radius, MAX : this.width - this.radius});
this.y = this.createRandomValue({MIN : this.radius, MAX : this.height - this.radius});
this.previousX = this.x;
this.previousY = this.y;
this.radian = this.INIT_RADIAN;
this.deltaRadian = this.createRandomValue(this.DELTA_RADIAN) | 0;
this.color = this.COLOR.replace('%h', this.radian);
this.vx = this.createRandomValue(this.VELOCITY) * ((Math.random() > 0.5) ? 1 : -1);
this.vy = this.createRandomValue(this.VELOCITY) * ((Math.random() > 0.5) ? 1 : -1);
this.faceIndex = this.MAX_FACE_INDEX;
this.scale = 0;
this.theta = 0;
},
createRandomValue : function(range){
return range.MIN + Math.round((range.MAX - range.MIN) * Math.random());
},
moveParticle : function(){
this.previousX = this.x;
this.previousY = this.y;
this.x += this.vx;
this.y += this.vy;
if(this.x <= this.radius){
this.x = this.radius;
this.vx *= -this.WALL_RESTITUTION;
}else if(this.x >= this.width - this.radius){
this.x = this.width - this.radius;
this.vx *= -this.WALL_RESTITUTION;
}
if(this.y <= this.radius){
this.y = this.radius;
this.vy *= -this.WALL_RESTITUTION;
}else if(this.y > this.height - this.radius){
this.y = this.height - this.radius;
this.vy *= -this.WALL_RESTITUTION;
}
},
getParticleInfo : function(){
return {
x : this.x,
y : this.y,
previousX : this.previousX,
previousY : this.previousY,
vx : this.vx,
vy : this.vy,
radius : this.radius,
mass : this.mass,
color : this.COLOR.replace('%h', this.radian)
};
},
setParticleInfo : function(x, y, vx, vy){
this.previousX = this.x;
this.previousY = this.y;
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
if(this.radian > 0){
this.radian = Math.max(0, this.radian - this.deltaRadian);
}
},
checkCollision : function(particle){
if(this.radian == 0){
return;
}
var particle1 = this.getParticleInfo(),
particle2 = particle.getParticleInfo(),
dx = particle2.x - particle1.x,
dy = particle2.y - particle1.y,
distance = Math.sqrt(dx * dx + dy * dy);
if(distance > particle1.radius + particle2.radius){
return;
}
var angle = Math.atan2(dy, dx),
axis1 = {x : 0, y : 0},
axis2 = this.rotate(dx, dy, angle),
v1 = this.rotate(particle1.vx, particle1.vy, angle),
v2 = this.rotate(particle2.vx, particle2.vy, angle),
vSum = (v1.x - v2.x) * this.PARTICLE_RESTITUTION;
v1.x = ((particle1.radius - particle2.radius * this.PARTICLE_RESTITUTION) * v1.x + particle2.radius * v2.x * (1 + this.PARTICLE_RESTITUTION)) / (particle1.radius + particle2.radius);
v2.x = v1.x + vSum;
var vAbs = Math.abs(v1.x) + Math.abs(v2.x),
overlap = (particle1.radius + particle2.radius) - Math.abs(axis1.x - axis2.x);
if(axis1.x >= axis2.x){
axis1.x += Math.abs(overlap * v1.x / vAbs);
axis2.x -= Math.abs(overlap * v2.x / vAbs);
}else{
axis1.x -= Math.abs(overlap * v1.x / vAbs);
axis2.x += Math.abs(overlap * v2.x / vAbs);
}
axis1 = this.rotate(axis1.x, axis1.y, -angle);
axis2 = this.rotate(axis2.x, axis2.y, -angle);
v1 = this.rotate(v1.x, v1.y, -angle);
v2 = this.rotate(v2.x, v2.y, -angle);
this.setParticleInfo(particle1.x + axis1.x, particle1.y + axis1.y, v1.x, v1.y);
particle.setParticleInfo(particle1.x + axis2.x, particle1.y + axis2.y, v2.x, v2.y);
},
rotate : function(x, y, angle){
var sin = Math.sin(angle),
cos = Math.cos(angle);
return {x : x * cos + y * sin, y : y * cos - x * sin};
},
render : function(context){
this.moveParticle();
context.save();
var axis = this.getParticleInfo();
if(this.radian == 0){
var scale = 1 + 4 * Math.pow((1 - this.scale), 5);
context.translate(axis.x, axis.y);
context.scale(scale, scale);
context.rotate(Math.PI / 5 * Math.sin(this.theta));
context.translate(-axis.x, -axis.y);
context.globalAlpha = Math.max(0, this.scale);
if(this.scale > 0){
this.scale -= this.DELTA_SCALE;
this.theta += this.DELTA_THETA;
this.theta %= Math.PI * 2;
}else{
this.init();
}
}else if(this.scale < 1){
context.translate(axis.x, axis.y);
context.scale(this.scale, this.scale);
context.translate(-axis.x, -axis.y);
context.globalAlpha = this.scale;
this.scale = Math.min(1, this.scale + this.DELTA_SCALE * 5);
}
context.beginPath();
var gradient = context.createRadialGradient(axis.x, axis.y, 0, axis.x, axis.y, axis.radius);
gradient.addColorStop(0, axis.color.replace('%l', '60'));
gradient.addColorStop(1, axis.color.replace('%l', '30'));
context.fillStyle = gradient;
context.arc(axis.x, axis.y, axis.radius, 0, Math.PI * 2, true);
context.fill();
context.lineWidth = 2;
context.strokeStyle = 'hsl(0, 0%, 100%)';
context.fillStyle = 'hsl(0, 0%, 100%)';
switch(Math.min(this.MAX_FACE_INDEX, Math.floor(this.radian / (this.INIT_RADIAN / (this.MAX_FACE_INDEX + 1))))){
case 0:
context.beginPath();
context.moveTo(axis.x - 14, axis.y - 10);
context.lineTo(axis.x - 4, axis.y - 2);
context.moveTo(axis.x + 14, axis.y - 10);
context.lineTo(axis.x + 4, axis.y - 2);
context.moveTo(axis.x - 8, axis.y + 15);
context.quadraticCurveTo(axis.x, axis.y + 5, axis.x + 8, axis.y + 15);
context.stroke();
break;
case 1:
context.beginPath();
context.moveTo(axis.x - 15, axis.y - 8);
context.quadraticCurveTo(axis.x - 10, axis.y, axis.x - 5, axis.y - 8);
context.moveTo(axis.x + 15, axis.y - 8);
context.quadraticCurveTo(axis.x + 10, axis.y, axis.x + 5, axis.y - 8);
context.moveTo(axis.x - 8, axis.y + 15);
context.quadraticCurveTo(axis.x, axis.y + 5, axis.x + 8, axis.y + 15);
context.stroke();
break;
case 2:
context.beginPath();
context.moveTo(axis.x - 14, axis.y - 5);
context.lineTo(axis.x - 4, axis.y - 5);
context.moveTo(axis.x + 14, axis.y - 5);
context.lineTo(axis.x + 4, axis.y - 5);
context.moveTo(axis.x - 7, axis.y + 10);
context.lineTo(axis.x + 7, axis.y + 10);
context.stroke();
break;
case 3:
context.beginPath();
context.arc(axis.x - 10, axis.y - 5, 3, 0, Math.PI * 2, false);
context.arc(axis.x + 10, axis.y - 5, 3, 0, Math.PI * 2, false);
context.fill();
context.beginPath();
context.moveTo(axis.x - 7, axis.y + 5);
context.quadraticCurveTo(axis.x, axis.y + 15, axis.x + 7, axis.y + 5);
context.stroke();
break;
case 4:
context.beginPath();
context.moveTo(axis.x - 14, axis.y - 5);
context.quadraticCurveTo(axis.x - 10, axis.y - 15, axis.x - 6, axis.y - 5);
context.moveTo(axis.x + 14, axis.y - 5);
context.quadraticCurveTo(axis.x + 10, axis.y - 15, axis.x + 6, axis.y - 5);
context.moveTo(axis.x - 7, axis.y + 5);
context.quadraticCurveTo(axis.x, axis.y + 15, axis.x + 7, axis.y + 5);
context.stroke();
}
context.restore();
}
};
$(function(){
RENDERER.init();
});
</script>
</body>
</html>