JavaScript+Canvas 实现Bubble Effect
——————————————————
最近对网页的交互特效很感兴趣,于是自己动手做了一个气泡特效,程序根据鼠标的位置每隔一段时间创建新的气泡,实现起来其实很简单,只需要Canvas画图功能和定时器就可以实现,也就是说任何有绘图接口的编程语言都可以实现气泡特效,闲话不多说,先贴一张运行效果图片吧。
实际运行效果比GIF图片中的效果要更加流畅,下面有程序代码,也可以直接在github上下载代码。
接下来我会首先说明以下设计思路,然后分享一下实现过程中遇到的困难和解决过程,最后会把代码贴出来。
一、设计思路
1、动画原理。
说到用代码实现物体运动效果,有过编程经历的小伙伴们首先想到的一定是timer(定时器),每隔固定时间调用一次画图函数,但使用JavaScript实现动画效果不推荐使用setInterval函数!这个函数完全可以实现动画效果,但有可能受到很大的限制,如果动画效果稍微复杂一点,setInterval就有可能无法顺利实现动画,导致动画丢帧等问题,好在现在各大浏览器基本上都实现了requestAnimationFrame函数,这个函数会让系统自己决定什么执行画图函数,但会尽量快!requestAnimationFrame每执行一次就会先重画所有气泡,之后根据速度改变改变每个气泡的位置。
2、运动轨迹。
气泡特效最重要的一点是要模拟气泡运动的轨迹,这其中涉及到速度、方向,如果你仔细观察,会发现每个气泡上升的速度是一定的,而平移的速度会慢慢降到0,这样就基本上可以推断出气泡对象的属性:x\y(横纵坐标)、radius(半径)、vx\vy(水平移动速度和向上移动速度)、horizontal direction(水平运动方向)、color(颜色),最后还要判断气泡是否超出了边界,所以还有state(状态)属性。
3、鼠标响应及时间间隔。
要让气泡产生的位置根据鼠标的位置移动, 程序中定义了两个全局变量MouseX和MouseY, 气泡产生时就是根据这两个全局变量来确定初始位置,设立鼠标响应事件mousemove, 每当鼠标移动,触发事件,改变MouseX和MouseY的值就实现根据鼠标位置产生气泡。
时间间隔通过一个频率计数器来实现,每执行一次requestAnimationFrame,计数器加一,固定次数之后产生新汽泡。
二、实现细节
1、内存压力。
因为程序会不断的产生气泡,如果每次创建一个气泡就多创建一个对象,无疑会对内存造成巨大的压力。用数组管理气泡对象的好处不仅仅是便于索引,还可以为内存缓解压力!
每次重画气泡的时候都要判断气泡的状态,如果气泡已经超出了画布的范围,那么就不会再画这个气泡,这就一意味着该气泡在数组中对应的位置空出来了!我们可以充分利用这一点,反正要遍历数组,那么在遍历的过程中记录下出界气泡的索引,然后在该索引处创建新的气泡,原来的对象没有引用指向就会自动由内存管理器处理,这样一来就解决了内存的问题。还有一个问题就是如果数组中所有气泡都在界内,此时需要扩充数组大小,从我几次的测试结果来看,数组大小一般稳定在26左右,运行结果良好。
三、代码如下:
html
<!DOCTYPE html>
<html>
<head>
<title>Bubbles</title>
<meta name="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<style>
*{
box-sizing: border-box;
margin: 0;
padding: 0;
}
body{
height: 100vh;
width: 100vw;
background-color: #000000;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="../js/canvas.js"></script>
</body>
</html>
JavaScript
var Container = document.querySelector("body");
var canvas = document.querySelector("#canvas");
var WIDTH = Container.clientWidth;
var HEIGHT = Container.clientHeight;
canvas.width = Container.clientWidth;
canvas.height = Container.clientHeight;
var ctx = canvas.getContext('2d');
var colors = ['#70f3ff','#44cef6','#871F78','#faff72','rgb(246,22,103)','#00e079','#b0a4e3','#f3d3e7','#ff2d51']
var bubbleArray = new Array(20);
const LEFT = -1;
const RIGHT = 1;
const RISING = 1;
const GONE = -1;
var MouseX = 0;
var MouseY = 0;
function setSize() {/*the size of canvas will be resized if the size of window is changed*/
Container = document.querySelector("body");
canvas = document.querySelector("#canvas");
canvas.width = Container.clientWidth;
canvas.height = Container.clientHeight;
}
window.addEventListener('resize',setSize,false);
var bubble = { /*气泡的原型,用来创建新的水泡*/
x:300,y:1000,radius:30,vx:10,vy:-3,
color:'#70f3ff',state:GONE,direction:1,
Draw:function(){
ctx.beginPath();
ctx.moveTo(this.x,this.y);
ctx.arc(this.x, this.y, Math.abs(this.radius), 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
},
/*
bubbles movements, check the position of the bubble first,
and change state if the bubble is beyond the boundary
*/
Move:function () {
if(this.y>=0){
this.x += this.vx*this.direction;
this.y += this.vy;
this.vx *= .95;
this.radius -= .001;
}else{
this.state = GONE;
}
}
};
/*
* create a new bubble,using bubble as it's protetype
*
* x: x coordinate of the center
* y: y coordinate of the center
* return: the bubble been created
*
* */
function NewBubble(x,y){
var b = Object.create(bubble);
b.x = x;
b.y = y;
b.state = RISING;
b.radius = Math.random()*10+5;
b.vx = Math.random()*5;
var flag = Math.floor(Math.random()*2); //for direction
var color = Math.floor(Math.random()*colors.length);
b.color = colors[color];
if(flag == 0){
b.direction = LEFT;
}else{
b.direction = RIGHT;
}
return b ;
}
/*
* draw bubbles,those bubbles of whom the state is RISING will
* be painted. and create a new bubble every fixed interval
*
* */
var iterator = 0;
var frequency = 10;
function DrawBubbles(){
ctx.clearRect(0,0,canvas.width,canvas.height);
var avaIndex = -1;
var flag = true;
for(var i=0;i<bubbleArray.length;i++){ /*moving and repaintng of bubbles*/
if(bubbleArray[i] == undefined || bubbleArray[i].state == GONE) {
avaIndex = i;
}else{
bubbleArray[i].Draw();
bubbleArray[i].Move();
}
}
if(iterator>=frequency){ //check if it's time to create a new bubble
if(avaIndex == -1){
bubbleArray[bubbleArray.length] = NewBubble(MouseX,MouseY);
bubbleArray[bubbleArray.length-1].Draw();
bubbleArray[bubbleArray.length-1].Move();
}else{
bubbleArray[avaIndex] = NewBubble(MouseX,MouseY);
bubbleArray[avaIndex].Draw();
bubbleArray[avaIndex].Move();
}
}else{
flag = false;
}
iterator = flag? 0 : iterator+1;
window.requestAnimationFrame(DrawBubbles);
}
canvas.addEventListener('mousemove',function (event) {
MouseX = event.clientX;
MouseY = event.clientY;
},true);
window.requestAnimationFrame(DrawBubbles);
本次分享到这里就结束了,希望你喜欢这个特效,如果有什么更好的想法和建议,请踊跃发言,也希望喜欢前端的小伙伴可以联系我,大家互相交流、学习、进步。