Canvas的高级绘制技巧和事件系统

图片填充

在第一章中,我们知道Canvas可以使用颜色和渐变填充形状。同样你也可以自定义一个图案,并给形状填充图案。就像在CSS中操作背景图片一样,在Canvas的图案中,你也可以控制图案的重复模式。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>pattern</title>
</head>
<body>
    <canvas id='canvas' width="500" height="500"></canvas>
</body>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

// img 可以通过以下方式引入
var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/222/Canvas_createpattern.png';
img.onload = function() {
    var pattern = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = pattern;
    ctx.fillRect(0, 0, 400, 400);
};
</script>
</html>

值得注意的是,使用一张图片作为纹理的来源时,只有该图片加载完才有效,所以我们需要把绘制函数放在图片的onLoad事件的回调函数中。

透明度

Canvas API 提供的任何绘制函数都允许你通过globalAlpha属性控制所绘制图形的透明度。下面这个例子将绘制两个重叠的正方形,这两个正方形将展现不同透明度的效果。 重叠的不同透明度的矩形

<body>
    <canvas id="canvas" width="500" height="500"></canvas>
</body>
<script>
    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d');
    ctx.fillStyle='red';
    ctx.globalAlpha = 50/100;
    ctx.fillRect(0,0,100,100);
    ctx.globalAlpha = 30/100;
    ctx.fillRect(50,50,100,100);
    ctx.globalAlpha=1;
</script>

透明度的设置对所有的绘制操作都有效,你可以随意尝试,但是要记住在最后要把透明度改回1,以免影响后续的绘制。globalAlpha的属性值必须是在0和1之间否则会被忽略。

变化transforms

在绘制柱状图那一节,我们设置不同x,y坐标的重复的绘制了一遍又一遍的矩形。除了这种方法外,我们还可以使用translate函数实现,如下,每次循环我们把下一个矩形向右移动100px.

<body>
    <canvas id="canvas" width="500" height="500"></canvas>
</body>
<script>
    let data=[ 100, 68, 20, 30, 100 ];
    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d');
    ctx.fillStyle = 'red';
    for(let i =0;i<data.length;i++){
        let dp = data[i];
        // 每次往右移动100px
        ctx.translate(100,0);
        ctx.fillRect(0,0,50,dp);
    }
</script>

想很多2D API一样,Canvas也支持多种变化模式,位移,旋转,压缩,伸展等等。这使得你不必每次都手动计算新的位置。你同样可以按顺序调用各种变形,比如说,你可以先绘制一个正方形,然后移动到中点,再旋转30度。

    ctx.translate(50,50);
    let rads = 30*Math.PI*2/360;
    ctx.rotate(rads);
    ctx.fillRect(0,0,100,100);

你的每一次变换都会叠加在前一次的基础上,当然多次变化以后可能会让你感到迷惑。你可以通过代码回到某个状态,但是这是恼人的,为你你可以会付出花好几小时找一个小bug的代价。不过幸好,Canvas提供一个API用以存储状态。

状态存储

context2D对象代表着当前的绘制状态。在本文中,我习惯用变量xtc指代这个变量。这个状态包括当前的变换tranform,填充色fill,描边色stroke,当前字体font和一些其它的值。通过使用save()函数,你可以把这个变量保存在一个栈中。此后,你可以随意做变换,当你需要恢复到当前的状态时,只需要调用restore()函数。具体使用可以参看这个例子。

for(let i=0,i<data.length;i++){
    c.save();
    c.translate(40+i*100,460-dp*4);
    let dp = data[i];
    c.fillRect(0,0,50,dp*4);
    c.restore();
}

剪切(Clipping

通过clip函数,你可以只绘制一个形状的一部分,他把当前的形状当做之后绘制的蒙版层。之后所有的绘制都将发生在这个蒙版层之中。在蒙版层之外的所有绘制都不会显示在屏幕上,这在你想通过拼合创建一个复杂的形状,或者考虑对性能的原因只想重绘一部分区域时十分有效。具体用法参照以下代码 Clipping

<body>
    <canvas id="canvas" width="500" height="500">
    </canvas>
    <script>
    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d');
    // 绘制长方形
    ctx.fillStyle = 'red';
    ctx.fillRect(0, 0, 400, 100);

    // 绘制三角形
    ctx.beginPath();
    ctx.moveTo(200, 50);
    ctx.lineTo(250, 150);
    ctx.lineTo(150, 150);
    ctx.closePath();

    // 给三角形填充颜色
    ctx.lineWidth = 10;
    ctx.stroke();

    // 剪切三角形区域
    ctx.clip();

    // 再次填充颜色
    ctx.fillStyle = 'yellow';
    ctx.fillRect(0, 0, 400, 400);
    </script>
</body>

事件

Canvas本身并不提供任何事件,不过你依旧可以把Canvas当做一个普通的DOM元素,来监听鼠标和触摸事件。不是很好,但是至少我们有可用的事件了。

对于浏览器来说,Canvas元素本身就是一个大矩形。浏览器本身不能识别任何你绘制的形状,所以在Canvas里某个形状上触发的事件,浏览器会把整个Canvas当做整体。这意味着,如果你想把事件具体到某个地方(比如说一个按钮),你需要自己计算并给浏览器具体的数据。

计算那个形状在鼠标下是一个非常困难的事情。还好,于此Canvas为我们提供了一个还有用的APIisPointInPath。这个函数会告诉我们一个给定的坐标点是否在当前的Path中。示例如下:

    ctx.beginPath();
    ctx.moveTo(200, 50);
    ctx.lineTo(250, 150);
    ctx.lineTo(150, 150);
    ctx.closePath();

    let a = ctx.isPointInPath(225,100);
    let b = ctx.isPointInPath(300,300);
    console.log(a); // true
    console.log(b); // false

results matching ""

    No results matching ""