# canvas使鼠标和屏幕互动

# onmousemove 事件

可以看出来,移动的轨迹是由一个一个的圆构成的,如果移动的速度过快的话,那么就可以明显看出一个一个的圆。

既然轨迹是由很多圆构成,那么就应该使用数组储存圆的信息(坐标、半径),然后在鼠标移动的时候将鼠标的位置信息储存在数组中。

所以在鼠标移动的过程先要获得鼠标的坐标,然后将鼠标的坐标以及其他信息 push 到数组中去:

    window.onmousemove = function (event) {

        mouseX = event.clientX;
        mouseY = event.clientY;

        round_arr.push({
            mouseX: mouseX,
            mouseY: mouseY,
            r: para.r,  // 设置半径每次增大的数值        
            o: 1,    //  判断圆消失的条件,数值越大,消失得越快
        })
    };

# 设置 color

将圆的相关信息储存在 round_arr 数组中,现在要在 animate() 函数中将圆显示出来。

创建圆需要的坐标信息以及半径,在鼠标移动的事件中都已经将其 push 到 round_arr 数组中了,还有一个条件是需要设置的,那就是颜色。

怎么对颜色进行处理呢?在 para 参数中,可以看出,其中有设置 color 值。如果 color 值不为 false,那么设置的圆的颜色就是设置的 color 值;如果设置的 color 值为 false,那么圆的颜色就是随机的。


if (para.color) {
    color2 = para.color;
} else {
    color = Math.random() * 360;
}

那么怎么设置颜色的渐变呢?将 color 的颜色值依次增加一个增量。


if (!para.color) {
    color += .1;
    color2 = 'hsl(' + color + ',100%,80%)';
}


要让颜色一直改变,要将上面颜色改变的代码放在一个一直执行的函数。将上面改变颜色的代码放在 animate() 函数中。

# animate() 函数

需要一个一直在执行的函数,这个函数主要负责动画的 animate() 函数。从函数名就可以看出这个函数的作用,需要在该函数中写动画。

先来清除屏幕。

接着使用 round_arr 数组中的数据将一个一个的圆绘制出来。

for (var i = 0; i < round_arr.length; i++) {

    ctx.fillStyle = color2;
    ctx.beginPath();
    ctx.arc( round_arr[i].mouseX ,round_arr[i].mouseY,round_arr[i].r,0, Math.PI * 2);
    ctx.closePath();
    ctx.fill();
    round_arr[i].r += para.r;
    round_arr[i].o -= para.o;

    if( round_arr[i].o <= 0){
        round_arr.splice(i,1);
        i--;
    }
    
}

还需要一直执行这个函数:

window.requestAnimationFrame(animate);


function animate() {

    if (!para.color) {
        color += .1;
        color2 = 'hsl(' + color + ',100%,80%)';
    }

    ctx.clearRect(0, 0, WIDTH, HEIGHT);

    for (var i = 0; i < round_arr.length; i++) {

        ctx.fillStyle = color2;
        ctx.beginPath();
         ctx.arc( round_arr[i].mouseX ,round_arr[i].mouseY,round_arr[i].r,0, Math.PI * 2);
        ctx.closePath();
        ctx.fill();
        round_arr[i].r += para.r;
        round_arr[i].o -= para.o;

        if( round_arr[i].o <= 0){
            round_arr.splice(i,1);
            i--;
        }
    }

    window.requestAnimationFrame(animate);
};

# 小结

  1. 创建 Canvas 元素,设置参数
  2. 鼠标移动事件,将坐标信息 push 到数组
  3. 设置颜色
  4. 设置动画 animate() 函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        #canvas {
            background: #000;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>


        var canvas = document.getElementById('canvas'),
            ctx = canvas.getContext('2d'),
            WIDTH = canvas.width = document.documentElement.clientWidth,
            HEIGHT = canvas.height = document.documentElement.clientHeight,
            para = {
                num: 100,
                color: false,    //  颜色  如果是false 则是随机渐变颜色
                r: 0.9,
                o: 0.09,         //  判断圆消失的条件,数值越大,消失的越快
                a: 1,

            },
            color,
            color2,
            round_arr = [];

        window.onmousemove = function (event) {

            mouseX = event.clientX;
            mouseY = event.clientY;

            round_arr.push({
                mouseX: mouseX,
                mouseY: mouseY,
                r: para.r,
                o: 1
            })
        };

        // 判断参数中是否设置了 color,如果设置了 color,就使用该值、
        // 如果参数中的 color 为 false,那么就使用随机的颜色
        if (para.color) {
            color2 = para.color;
        } else {
            color = Math.random() * 360;
        }

        function animate() {

            if (!para.color) {
                color += .1;
                color2 = 'hsl(' + color + ',100%,80%)';
            }

            ctx.clearRect(0, 0, WIDTH, HEIGHT);

            for (var i = 0; i < round_arr.length; i++) {

                ctx.fillStyle = color2;
                ctx.beginPath();
                ctx.arc( round_arr[i].mouseX ,round_arr[i].mouseY,round_arr[i].r,0, Math.PI * 2);
                ctx.closePath();
                ctx.fill();
                round_arr[i].r += para.r;
                round_arr[i].o -= para.o;

                if( round_arr[i].o <= 0){
                    round_arr.splice(i,1);
                    i--;
                }
            }

            window.requestAnimationFrame(animate);
        };

        animate();
</script>
</body>
</html>

# 常见的 Canvas 优化方法

# 避免浮点数的坐标点

绘制图形时,长度与坐标应选取整数而不是浮点数,原因在于 Canvas 支持半个像素绘制。

会根据小数位实现插值算法实现绘制图像的反锯齿效果,如果没有必要请不要选择浮点数值。

# 使用多层画布去画一个复杂的场景

一般在游戏中这个优化方式会经常使用,但是在我们的背景特效中不经常使用,这个优化方式是将经常移动的元素和不经常移动的元素分层,避免不必要的重绘。

比如在游戏中,背景不经常变换和人物这些经常变换的元素分成不同的层,这样需要重绘的资源就会少很多。

# 用 CSS transform 特性缩放画布

如果你使用 lefttop 这些 CSS 属性来写动画的话,那么会触发整个像素渲染流程 —— paintlayoutcomposition

但是使用 transform 中的 translateX/Y 来切换动画,你将会发现,这并不会触发 paintlayout,仅仅会触发 composition 的阶段。

这是因为 transform 调用的是 GPU 而不是 CPU。

# 离屏渲染

名字听起来很复杂,什么离屏渲染,其实就是设置缓存,绘制图像的时候在屏幕之外的地方绘制好,然后再直接拿过来用,这不就是缓存的概念吗?!︿( ̄︶ ̄)︿.

建立两个 Canvas 标签,大小一致,一个正常显示,一个隐藏(缓存用的,不插入 DOM 中)。先将结果 draw 到缓存用的 canvas 上下文中,因为游离 Canvas 不会造成 UI 的渲染,所以它不会展现出来;再把缓存的内容整个裁剪再 draw 到正常显示用的 Canvas 上,这样能优化不少。

我们首先要在全局设置一个变量 useCache 来存放我们是否使用离屏渲染这种优化方式。

var useCache = true;

# Round_item 方法

然后我们在 Round_item 原型的 draw() 方法中创建每一个离屏的小的 canvas

    function Round_item(index, x, y) {
        this.index = index;
        this.x = x;
        this.y = y;
        this.useCache = useChache;
        
        this.cacheCanvas = document.createElement("canvas");
        this.cacheCtx = this.cacheCanvas.getContext("2d");

        this.r = Math.random() * 2 + 1;
        
        this.cacheCtx.width = 6 * this.r;
        this.cacheCtx.height = 6 * this.r;
        
        var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
        this.color = "rgba(255,255,255," + alpha + ")";

        if(useChache){
            this.cache();
        }
    }

有人会产生疑问,为什么这里的 cacheCanvas 画布的宽度要设置为 6 倍的半径?那是因为,我们创建的 cacheCanvas 不仅仅是有圆,还包括圆的阴影,所以我们要将 cacheCanvas 的面积设置得稍微大一些,这样才能将圆带阴影一起剪切到我们的主 Canvas 中。

draw() 方法中,我们新创建了 cacheCanvas,并获取到了 cacheCanvas 的上下文环境,然后设置其宽高。

然后我们判断了 useChache 变量的值,也就是说,如果我们将 useChache 设置为 true,也就是使用缓存,我们就调用 this.cache() 方法。接下来,我们来看一下 this.cache() 方法。

# this.cache() 方法

同样的,我们也是在 Round_item 的原型中设置 this.cache() 方法。

this.cache() 方法中,我们的主要任务是在每一个 cacheCanvas 中都绘制一个圆。

    Round_item.prototype.cache = function () {
        this.cacheCtx.save();
        this.cacheCtx.fillStyle = this.color;
        this.cacheCtx.shadowColor = "white";
        this.cacheCtx.shadowBlur = this.r * 2;
        this.cacheCtx.beginPath();
        this.cacheCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI);
        this.cacheCtx.closePath();
        this.cacheCtx.fill();
        this.cacheCtx.restore();
    };

这里需要注意的是,和在 draw() 方法中画的圆不同之处是,要注意这里设置的圆心坐标,是 this.r * 3,因为我们创建的 cacheCanvas 的宽度和高度都是 6 * this.r,我们的圆是要显示在 cacheCanvas 的正中心,所以设置圆心的坐标应该是 this.r * 3,this.r * 3

# draw() 方法

既然设置了 cacheCanvas,那么我们在 draw() 中,就需要使用 Canvas 的 drawImage 方法将 cacheCanvas 中的内容显示在屏幕上。

    Round_item.prototype.draw = function () {

        if( !useChache){
            content.fillStyle = this.color;
            content.shadowBlur = this.r * 2;
            content.beginPath();
            content.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
            content.closePath();
            content.fill();
        }else{
            content.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r);
        }

    };

这里也是要判断下,如果没有使用缓存的话,还是使用最原始的创建圆的方式。

这样,我们就完成了离屏渲染的优化,我们来一起看一下完整的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        html, body {
            margin: 0;
            overflow: hidden;
            width: 100%;
            height: 100%;
            cursor: none;
            background: black;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>

<script>
    var ctx = document.getElementById('canvas'),
        content = ctx.getContext('2d'),
        round = [],
        WIDTH,
        HEIGHT,
        initRoundPopulation = 80,
        useChache = true;



    WIDTH = document.documentElement.clientWidth;
    HEIGHT = document.documentElement.clientHeight;

    ctx.width = WIDTH;
    ctx.height = HEIGHT;

    function Round_item(index, x, y) {
        this.index = index;
        this.x = x;
        this.y = y;
        this.useCache = useChache;
        this.cacheCanvas = document.createElement("canvas");
        this.cacheCtx = this.cacheCanvas.getContext("2d");

        this.cacheCtx.width = 6 * this.r;
        this.cacheCtx.height = 6 * this.r;
        this.r = Math.random() * 2 + 1;
        var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
        this.color = "rgba(255,255,255," + alpha + ")";

        if(useChache){
            this.cache();
        }
    }

    Round_item.prototype.draw = function () {

        if( !useChache){
            content.fillStyle = this.color;
            content.shadowBlur = this.r * 2;
            content.beginPath();
            content.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
            content.closePath();
            content.fill();
        }else{
            content.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r);
        }

    };

    Round_item.prototype.cache = function () {
        this.cacheCtx.save();
        this.cacheCtx.fillStyle = this.color;
        this.cacheCtx.shadowColor = "white";
        this.cacheCtx.shadowBlur = this.r * 2;
        this.cacheCtx.beginPath();
        this.cacheCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI);
        this.cacheCtx.closePath();
        this.cacheCtx.fill();
        this.cacheCtx.restore();
    };
    function animate() {
        content.clearRect(0, 0, WIDTH, HEIGHT);

        for (var i in round) {
            round[i].move();
        }
        requestAnimationFrame(animate)
    }

    Round_item.prototype.move = function () {
        this.y -= 0.15;
        if (this.y <= -10) {
            this.y = HEIGHT + 10;
        }
        this.draw();
    };


    function init() {
        for (var i = 0; i < initRoundPopulation; i++) {
            round[i] = new Round_item(i, Math.random() * WIDTH, Math.random() * HEIGHT);
            round[i].draw();
        }
        animate();

    }

    init();
</script>
</body>
</html>

最后更新: 1/28/2021, 4:11:50 PM