<canvas>元素本身并不可见,它只是创建了一个绘图表面并向客户端js暴露了强大的绘图API。
1 <canvas> 与图形
为优化图片质量,不要在HTML中使用width和height属性设置画布的屏幕大小。而要使用CSS的样式属性width和height来设置画布在屏幕上的预期大小。然后在JS开始绘制前,再将画布对象的width和heigh属性设置为CSS像素乘以window.devicePixelRatio。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<canvas width="100" height="100" id="canvas1"></canvas>
<canvas id="canvas2" style="width: 100px; height: 100px"></canvas>
</body>
</html>
<script>
let canvas2 = document.querySelector("#canvas2");
canvas2.width = 100 * window.devicePixelRatio;
canvas2.height = 100 * window.devicePixelRatio;
</script>
图 代码实现效果
1.1 save()、restore()与beginPath()
beginPath() 开始一条路径或重置当前路径,表示重新开始一个新的路径内容。
<script>
let context = document.querySelector("#canvasId").getContext("2d");
context.rect(0,0,30,30);
context.fillStyle = "green";
context.fill();
// 缺失 content.beginPath() 那么上面的矩形fillStyle颜色
// 始终是在beginPath出现之前设置的fillStyle,即"red";
context.rect(40,40,30,30);
context.fillStyle = "red";
context.fill();
context.beginPath(); // 重新开始路径内容
context.rect(80,80,30,30);
context.fillStyle = "blue";
context.fill();
</script>
图 beginPath演示效果图
save()方法把当前的图形状态(不包括当前定义的路径和当前的点)推到一个保存到状态栈中,而restore()方法则从该栈中弹出状态。
<script>
let context = document.querySelector("#canvasId").getContext("2d");
context.fillStyle = "green";
context.fillRect(0,0,50,50);
context.save(); //保存fillStyle等状态信息
context.fillStyle = "red";
context.fillRect(60,0,50,50);
context.save();
context.restore(); // red
context.fillRect(120,0,50,50);
context.restore(); // green
context.fillRect(180,0,50,50);
</script>
图 store() 演示效果图
1.2 渐变色
需要将fillStyle(或strokeStyle)设置为CanvasGradient对象。上下文有两个方法用于创建这个对象:
1)createLinearGradient(),参数是定义一条直线的两个点的坐标,颜色将在这条直线的方向上渐变。
2)createRadialGradient(),需要指定两个圆心和半径(着两个圆不一定是同心圆),小圆内部区域或大圆外部区域将被实色填充,这两个区域之间的部分则会以渐变色填充。
在创建这个对象后,必须调用该对象的addColorStop()方法定义渐变色。第一个参数是一个介于0.0和1.0之间的数值,第二个参数是一个CSS颜色说明。至少必须调用这个方法两次。
<script>
let canvas = document.querySelector("#canvasId");
canvas.width = 300 * window.devicePixelRatio;
canvas.height = 200 * window.devicePixelRatio;
let context = canvas.getContext("2d");
context.rect(0,0,130,150);
let canvasGradient = context.createLinearGradient(30,20, 130,150);
context.fillStyle = canvasGradient;
canvasGradient.addColorStop(0,"red");
canvasGradient.addColorStop(1,"blue");
context.fill();
context.beginPath(); // 开始新路径
context.rect(140,0,200,200);
let radial = context.createRadialGradient(240,100,50,240,100,100);
context.fillStyle = radial;
radial.addColorStop(0, "yellow");
radial.addColorStop(1,"green");
context.fill();
</script>
图 渐变色实现效果
1.3 坐标系转换
translate()方法简单地向左、右、上、下移动坐标系原点。rotate()方法按照指定角度旋转坐标轴。scale()方法沿x轴或y轴拉伸或压缩距离(给scale()方法传入一个负缩放因子会围绕原点反转坐标轴,就好像镜子里的倒影一样)。
<script>
let context = document.querySelector("#canvasId").getContext("2d");
(function scaleTest() {
context.translate(100,0);
drawTrapezoid("green")
context.scale(-1,1);
drawTrapezoid("red")
}());
function drawTrapezoid(color) {
context.beginPath();
context.moveTo(0,20);
context.lineTo(0,170);
context.lineTo(-70,150);
context.lineTo(-70,70);
context.closePath();
context.fillStyle = color;
context.fill();
context.save();
}
</script>
图 scale 实现效果图
《JavaScript权威指南》中的科赫雪花代码:
<script>
let canvas = document.querySelector("#canvasId");
canvas.width = 500 * window.devicePixelRatio;
canvas.height = 500 * window.devicePixelRatio;
let c = canvas.getContext("2d");
let deg = Math.PI / 180;
function snowflake(n,x,y,len) {
c.save();
c.translate(x,y);
c.moveTo(0,0);
leg(n);
c.rotate(-120 * deg);
leg(n);
c.rotate(-120 * deg);
leg(n);
c.closePath();
c.restore();
function leg(n) {
c.save();
if (n === 0) {
c.lineTo(len,0);
} else {
c.scale(1/3,1/3);
leg(n-1);
c.rotate(60 * deg);
leg(n-1);
c.rotate(-120 * deg);
leg(n-1);
c.rotate(60 * deg);
leg(n-1);
}
c.restore();
c.translate(len,0);
}
}
snowflake(0,25,125,125);
snowflake(1,175,125,125);
snowflake(2,325,125,125);
snowflake(3,475,125,125);
snowflake(4,625,125,125);
c.stroke();
</script>
图 科赫雪花实现效果
1.4 剪切
clip()方法定义一个剪切区域,定义后,这个区域外部将不会被绘制。它的作用是遮罩,用来隐藏没有遮罩的部分。
<script>
const context = document.querySelector("#canvasId").getContext("2d");
context.fillStyle = "red";
context.arc(100,100,100,0,Math.PI * 2,true);
context.fill();
context.clip();
context.beginPath();
context.fillStyle = "blue";
context.arc(200,100,100,0,Math.PI * 2, true);
context.fill();
context.beginPath();
context.fillStyle = "green";
context.arc(100,200,100,0,Math.PI * 2, true);
context.fill();;
</script>
图 clip()演示效果图
1.5 像素操作
context的getImageDate()方法返回一个ImageData对象,表示画布中某矩形区域中包含的原始像素(包括R、G、B和A组件)。createImageData()创建空的ImageData对象,ImageData对象中的像素是可写的。putImageData()方法则是把像素复制到画布中。
<script>
let canvas = document.querySelector("#canvasId");
canvas.width = 300 * window.devicePixelRatio;
canvas.height = 300 * window.devicePixelRatio;
let context = canvas.getContext("2d");
context.fillStyle = "#008000";
context.arc(100,100,100,0,Math.PI,true);
context.fill();
let imageData = context.getImageData(0,0,200,200);
let width = imageData.width,height = imageData.height;
let data = imageData.data; // 每个像素占用4个连续字节,分别是R、G、B和A
for (let pos = 0; pos < data.length; pos+=4) {
if (data[pos + 1] === 128) {
data[pos] = 255;
data[pos + 1] = 0; //G
data[pos + 2] = 0; // B
data[pos + 3] = 255; // A
}
}
context.putImageData(imageData,210,0);
</script>
图 像素操作演示效果图