canvas动画实现瀑布

1.效果如下

开始的时候:

一段时间之后:

2.HTML,CSS代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href=""/>
    <style type="text/css" media="screen">
    html,body{
        margin: 0;
        padding: 0;
    }
    #waterFall {
        position: absolute;
        top: 0;
        left: 50%;
        margin-left: -100px;
    }
</style>
</head>
<body>
    <canvas id="waterFall">

    </canvas>
</body>
</html>

这里canvas的宽度在后面设置为200,所以margin-left:-100px目的是让canvas居中显示

3.JS代码

首先需要初始化canvas

 let _this = this
 function getWindowInfo(){
        let width=$(window).width()
        let height=$(window).height()
        return [width,height]
    }
    function initCanvas(){
        let info=getWindowInfo()
        let c =document.getElementById('waterFall')
        c.width = 200
        c.height = info[1]
        let ctx = c.getContext('2d')
        return ctx
    }
    function rand (rMi, rMa) {
        return ~~((Math.random() * (rMa - rMi + 1)) + rMi)
    }

声明三个函数:
第一个是获取窗口的宽度和高度。
第二个函数是用来初始化canvas的,返回的参数是canvas的2d画布,这里一开始是想把canvas的大小设置成窗口的大小的,但是在后面的调试中发现,那样画布太大了,卡顿明显。
第三个函数是用来取一个从rMi到rMa的随机数的。
let _this = this用来声明this指针留着以后用。

声明两个类,一个是整个画布的类(WaterFall)还有一个是每一个水柱的类(Particle)

这个动画的总体思路是:先在画布上方随机生成很多的水柱(其实就是一条比较粗的直线,当然要是细一点的话效果会更好),然后每个一段时间让水柱往下走一段距离,再来绘制,等到水柱到达低端的时候,把水柱的对象去除掉,在低端绘制一个小圆形来模拟水滴的落地。

Particles类
function Particle(ctx,ww,wh){
		// 声明颜色特征
        this.hue = rand(200, 220)
        this.saturation = rand(30, 60)
        this.lightness = rand(30, 60)
        // 声明水柱大小
        this.newWidth = rand(20, 40)
        this.newHeight = rand(1, 100)
        this.x = rand(10 + (this.newWidth / 2),  ww- 30 - (this.newWidth / 2))
        this.y = -this.newHeight
        this.gravity = .03
        this.vx = 0
        this.vy = 0
        this.ctx = ctx
        this.ww = ww
        this.wh = wh
        this.that = this
    }
    Particle.prototype = {
        render:function(){
            this.ctx.strokeStyle = 'hsla(' + this.hue + ', ' + this.saturation + '%, ' + this.lightness + '%, .06)'
            this.ctx.beginPath();
            this.ctx.moveTo(this.x, this.y);
            this.ctx.lineTo(this.x, this.y+this.newHeight);
            this.ctx.lineWidth = this.newWidth / 2;
            this.ctx.lineCap = 'round';
            this.ctx.stroke();
        },
        update:function(){
            this.vx+=this.vx
            this.vy+=this.gravity
            this.x+=this.vx
            this.y+=this.vy
        },
        remove:function(particles,i){
            if(this.y + this.newHeight > this.wh-10){
                this.ctx.beginPath()
                this.ctx.globalCompositeOperation='source-over'
                this.ctx.fillStyle = 'hsla(' + this.hue + ', ' + this.saturation + '%, ' + this.lightness + '%, .03)'
                this.ctx.arc(this.x + this.newWidth / 2, this.wh - 10 - _this.rand(0, 2), _this.rand(1, 10), 0, Math.PI * 2, false)
                this.ctx.fill()
                particles.splice(i,0)
                this.ctx.globalCompositeOperation='lighter'
            }
        },

    }

function Particle(ctx,ww,wh){}
这个里面给出来的是初始化时的变量
传入的参数分别是canvas的2d画布,ww是画布的宽度,wh是画布的高度
this.gravity = .03
这里声明了一个重力,相当于水柱下落的快慢,y坐标:每次绘制都会在原来的基础上叠加一个0.03
类里面一共有三个方法:
render
用来渲染水柱。
update
更新水柱所在位置。
remove
绘制水柱落到低端时候的小圆形,同时将水柱对象销毁。
函数传入两个参数:
第一个是保存有水柱对象的数组。
第二个是水柱对象在数组中位置。
这里需要注意的是:用到了this.ctx.globalCompositeOperation='source-over'这个属性是用用来确定画布上将要画上去的东西和已经在画布上东西之间的关系,source-over的意思是将要画上去的对象会完全覆盖掉已经在画布上的对象,若将要放在画布上的对象是透明的,可以很明显看出来透明的轮廓。lighter的属性是指叠加,即使将要放在画布上的对象是透明的,那部分透明的也会和已经在画布上的对象颜色叠加,从而看不出来透明的轮廓。

画布对象(WaterFall)
function WaterFall(ctx,ww,wh){
		// 这个参数用来确定水柱的多少
        this.particleRate = 3
        // 保存水柱的数组
        this.particles=[]
        this.ctx = ctx
        this.ww = ww
        this.wh = wh
        this.that = this
    }
    WaterFall.prototype={
        createParticles:function () {
            var i = this.particleRate;
            while (i--) {
                this.particles.push(new _this.Particle(this.ctx,this.ww,this.wh));
            }
        },
        updateParticles:function () {
            var i = this.particles.length;
            while (i--) {
                var p = this.particles[i];
                p.update(i);
            }
        },

        renderParticles:function () {
            var i = this.particles.length;
            while (i--) {
                var p = this.particles[i];
                p.render();
            }
        },
        removeParticles:function(){
            var i = this.particles.length;
            while (i--) {
                var p = this.particles[i];
                p.remove(this.particles,i);
            }
        },
        clearCanvas:function () {
            this.ctx.globalCompositeOperation = 'destination-in';
            this.ctx.fillStyle = 'rgba(250,250,250,.03)';
            this.ctx.fillRect(0, 0, 200, this.wh);
            this.ctx.globalCompositeOperation = 'lighter';
        },

        loop:function () {
            this.clearCanvas()
            this.createParticles()
            this.updateParticles()
            this.renderParticles() 
            this.removeParticles()
            window.requestAnimaFrame(()=>{this.that.loop()})    
        }
    }

createParticles
创建水柱的类(Particles)。
updateParticles
利用水柱自身声明的类去更新给一个水柱。
renderParticles
绘制每一个水柱
removeParticles
对每个水柱进行判断是否该移除这个水柱
clearCanvas
每次绘制之前对画布叠加进一个基础白色颜色,这样做的目的是让上面的水柱剧集的地方显得更加白,而不是更加蓝,更接近现实,this.ctx.fillStyle = 'rgba(250,250,250,.03)';这个里面的参数0.03可以调节来实现更加白或是有点白。
loop
函数来实现循环

运行函数,启动循环
$(window).ready(()=>{
        let ww = $(window).width()
         // 强制给画布200的宽度,要是全屏的话太大啦
        ww = 200
        let wh = $(window).height()
        let ctx = initCanvas()
        let waterfall = new WaterFall(ctx,ww,wh)       
        window.requestAnimationFrame(()=>{waterfall.loop()})
    })
    var requestAnimaFrame = function (callback) {
        return window.requestAnimationFrame(callback) ||
        window.webkitRequestAnimationFrame(callback) ||
        window.mozRequestAnimationFrame(callback) ||
        window.oRequestAnimationFrame(callback) ||
        window.msRequestAnimationFrame(callback) ||
        function (callback) {
            setInterval(callback, 1000 / 60);
        }
    };

说明:这里还定义了一个函数我们没有使用到,因为我的chrome浏览器支持window.requestAnimationFrame,这个函数的作用是让浏览器兼容使用,使用方式就是把window.requestAnimationFrame(function)替换成requestAnimationFrame(function)()。
当然还有比较完善的浏览器兼容方案:

function setupRAF () {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
            window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
        };

        if (!window.requestAnimationFrame) {
            window.requestAnimationFrame = function(callback, element) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() {
                    callback(currTime + timeToCall);
                }, timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };
        };

        if (!window.cancelAnimationFrame) {
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
        };
    };

这段代码的使用方法就是setupRAF(),直接运行一下这个,然后window.requestAnimationFrame也不用去修改。

4.完整代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href=""/>
    <style type="text/css" media="screen">
    html,body{
        margin: 0;
        padding: 0;
    }
    #waterFall {
        position: absolute;
        top: 0;
        left: 50%;
        margin-left: -100px;
    }
</style>
</head>
<body>
    <canvas id="waterFall">

    </canvas>
</body>
</html>
<script type="text/javascript" src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8" async defer>
    let _this = this
    function getWindowInfo(){
        let width=$(window).width()
        let height=$(window).height()
        return [width,height]
    }
    function initCanvas(){
        let info=getWindowInfo()
        let c =document.getElementById('waterFall')
        c.width = 200
        c.height = info[1]
        let ctx = c.getContext('2d')
        return ctx
    }

    function rand (rMi, rMa) {
        return ~~((Math.random() * (rMa - rMi + 1)) + rMi)
    }
    function Particle(ctx,ww,wh){
        this.hue = rand(200, 220)
        this.saturation = rand(30, 60)
        this.lightness = rand(30, 60)
        this.newWidth = rand(20, 40)
        this.newHeight = rand(1, 100)
        this.x = rand(10 + (this.newWidth / 2),  ww- 30 - (this.newWidth / 2))
        this.y = -this.newHeight
        this.gravity = .03
        this.vx = 0
        this.vy = 0
        this.ctx = ctx
        this.ww = ww
        this.wh = wh
        this.that = this
    }
    Particle.prototype = {
        render:function(){
            this.ctx.strokeStyle = 'hsla(' + this.hue + ', ' + this.saturation + '%, ' + this.lightness + '%, .06)'
            this.ctx.beginPath();
            this.ctx.moveTo(this.x, this.y);
            this.ctx.lineTo(this.x, this.y+this.newHeight);
            this.ctx.lineWidth = this.newWidth / 2;
            this.ctx.lineCap = 'round';
            this.ctx.stroke();
        },
        update:function(){
            this.vx+=this.vx
            this.vy+=this.gravity
            this.x+=this.vx
            this.y+=this.vy
        },
        remove:function(particles,i){
            if(this.y + this.newHeight > this.wh-10){
                this.ctx.beginPath()
                this.ctx.globalCompositeOperation='source-over'
                this.ctx.fillStyle = 'hsla(' + this.hue + ', ' + this.saturation + '%, ' + this.lightness + '%, .03)'
                this.ctx.arc(this.x + this.newWidth / 2, this.wh - 10 - _this.rand(0, 2), _this.rand(1, 10), 0, Math.PI * 2, false)
                this.ctx.fill()
                particles.splice(i,0)
                this.ctx.globalCompositeOperation='lighter'
            }
        },

    }
    function WaterFall(ctx,ww,wh){
        this.particleRate = 3
        this.particles=[]
        this.ctx = ctx
        this.ww = ww
        this.wh = wh
        this.that = this
    }
    WaterFall.prototype={
        createParticles:function () {
            var i = this.particleRate;
            while (i--) {
                this.particles.push(new _this.Particle(this.ctx,this.ww,this.wh));
            }
        },
        updateParticles:function () {
            var i = this.particles.length;
            while (i--) {
                var p = this.particles[i];
                p.update(i);
            }
        },

        renderParticles:function () {
            var i = this.particles.length;
            while (i--) {
                var p = this.particles[i];
                p.render();
            }
        },
        removeParticles:function(){
            var i = this.particles.length;
            while (i--) {
                var p = this.particles[i];
                p.remove(this.particles,i);
            }
        },
        clearCanvas:function () {
            this.ctx.globalCompositeOperation = 'destination-in';
            this.ctx.fillStyle = 'rgba(250,250,250,.03)';
            this.ctx.fillRect(0, 0, 200, this.wh);
            this.ctx.globalCompositeOperation = 'lighter';
        },

        loop:function () {
            this.clearCanvas()
            this.createParticles()
            this.updateParticles()
            this.renderParticles() 
            this.removeParticles()
            window.requestAnimaFrame(()=>{this.that.loop()})    
        }
    }
    
    function setupRAF () {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
            window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
        };

        if (!window.requestAnimationFrame) {
            window.requestAnimationFrame = function(callback, element) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() {
                    callback(currTime + timeToCall);
                }, timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };
        };

        if (!window.cancelAnimationFrame) {
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
        };
    };
    var requestAnimaFrame = function (callback) {
        return window.requestAnimationFrame(callback) ||
        window.webkitRequestAnimationFrame(callback) ||
        window.mozRequestAnimationFrame(callback) ||
        window.oRequestAnimationFrame(callback) ||
        window.msRequestAnimationFrame(callback) ||
        function (callback) {
            setInterval(callback, 1000 / 60);
        }
    };

    $(window).ready(()=>{
        let ww = $(window).width()
        ww = 200
        let wh = $(window).height()
        let ctx = initCanvas()
        setupRAF()
        let waterfall = new WaterFall(ctx,ww,wh)       
        window.requestAnimationFrame(()=>{waterfall.loop()})
    })
</script>
发布了25 篇原创文章 · 获赞 1 · 访问量 1639

猜你喜欢

转载自blog.csdn.net/weixin_43977647/article/details/104398698