想象一下,用几行代码就能创造出如此逼真的图像和动画,仿佛将艺术与科技完美融合,前端开发的Canvas技术正是这个数字化时代中最具魔力的一环,它不仅仅是网页的一部分,更是一个无限创意的画布,一个让你的想象力自由驰骋的平台。
目录
图像变换设置
图像合成设置
裁剪路径设置
状态保存与恢复
像素操作设置
图像变换设置
接下来我们开始讲解如何对canvas绘制的图像进行一些简单的操作,这里借助代码进行一个简单的概述及演示:
位移操作:这里借助translate函数传递两个参数代表水平和竖直移动的距离:
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
// 位移,translate,位移坐标系,水平位移和竖直位移
ctx.translate(100, 100)
// 绘制矩形
ctx.fillRect(0, 0, 50, 50)
</script>
</body>
缩放操作:这里借助scale函数传递两个参数代表水平和竖直缩放的倍速:
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
// 缩放,scale,水平缩放5倍,垂直缩放2倍
ctx.scale(5, 2)
// 绘制矩形
ctx.fillRect(0, 0, 50, 50)
</script>
</body>
旋转操作:这里借助rotate函数传递一个参数代表旋转的角度:
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
// 旋转,rotate,旋转的角度
ctx.rotate(Math.PI / 4);
ctx.translate(300, 0);
// 绘制矩形
ctx.fillRect(-250, -25, 500, 50)
</script>
</body>
综合操作:这里借助transform函数传递六个参数代表不同的作用:
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
// transform,参数分别代表:
/*
1. 水平缩放
2. 水平倾斜
3. 垂直倾斜
4. 垂直缩放
5. 水平位移
6. 垂直位移
*/
ctx.transform(2, 1, -1, 2, 50, 0);
// 绘制矩形
ctx.fillRect(0, 0, 50, 50)
</script>
</body>
图像合成设置
在canvas中不仅可以在已有图形后面再画新图形,还可以用来遮盖指定区域,清除画布中的某些部分(清除区域不仅限于矩形,像clearRect()方法做的那样)以及更多其他操作,这里就其做一个简单的概述,globalCompositeOperation = type 这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识 12 种遮盖方式的字符串。这里给出其简易的示例代码:
source-over:这是默认设置,并在现有画布上下文之上绘制新图形。
source-in:新图形只在新图形和目标画布重叠的地方绘制。其他的都是透明的。
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
// 绘制矩形
ctx.fillStyle = "rgba(0, 0, 255, 1)"; // 蓝色
ctx.fillRect(300, 200, 100, 100)
ctx.globalCompositeOperation='source-in'; // 蓝色和红色重叠部分的红色区域
ctx.fillStyle = "rgba(255, 0, 0, 1)"; // 红色
ctx.fillRect(250, 150, 100, 100)
</script>
</body>
source-out:在不与现有画布内容重叠的地方绘制新图形。
source-atop:新图形只在与现有画布内容重叠的地方绘制。
destination-over:在现有的画布内容后面绘制新的图形。
destination-in:现有的画布内容保持在新图形和现有画布内容重叠的位置。其他的都是透明的。
destination-out:现有内容保持在新图形不重叠的地方。
destination-atop:现有的画布只保留与新图形重叠的部分,新的图形是在画布内容后面绘制的。
lighter:两个重叠图形的颜色是通过颜色值相加来确定的。
copy:只显示新图形。
当然还有一些其他的属性,如下供大家参考:
xor:图像中,那些重叠和正常绘制之外的其他地方是透明的。
multiply:将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片。
screen:像素被倒转,相乘,再倒转,结果是一幅更明亮的图片。
overlay:multiply和screen的结合,原本暗的地方更暗,原本亮的地方更亮。
darken:保留两个图层中最暗的像素。
lighten:保留两个图层中最亮的像素。
color-dodge:将底层除以顶层的反置。
color-burn:将反置的底层除以顶层,然后将结果反过来。
hard-light:屏幕相乘(Acombinationofmultiplyandscreen)类似于叠加,但上下图层互换了。
soft-light:用顶层减去底层或者相反来得到一个正值。
difference:一个柔和版本的强光(hard-light)。纯黑或纯白不会导致纯黑或纯白。
exclusion:和difference相似,但对比度较低。
hue:保留了底层的亮度(luma)和色度(chroma),同时采用了顶层的色调(hue)。
saturation:保留底层的亮度(luma)和色调(hue),同时采用顶层的色度(chroma)。
color:保留了底层的亮度(luma),同时采用了顶层的色调(hue)和色度(chroma)。
luminosity:保持底层的色调(hue)和色度(chroma),同时采用顶层的亮度(luma)。
接下来我们可以借助globalCompositeOperation属性中的destination-out属性值实现一个类似刮刮卡的效果出来,具体的代码如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#ggk {
width: 600px;
height: 400px;
font-size: 30px;
font-weight: 900;
text-align: center;
line-height: 400px;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
</style>
</head>
<body>
<div id="ggk">谢谢回顾</div>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
// 是否可以挂卡
let isDraw = false
// 设置canvas背景
let img = new Image();
img.src = "./1.jpg"
img.onload = function () {
ctx.drawImage(img, 0, 0, 600, 400)
}
// 鼠标按下去可以挂卡
canvas.onmousedown = function () {
isDraw = true
}
// 鼠标抬起可以停止挂卡
canvas.onmouseup = function () {
isDraw = false
}
// 鼠标移动可以挂卡
canvas.onmousemove = function (e) {
if (isDraw) {
let x = e.pageX
let y = e.pageY
ctx.globalCompositeOperation = "destination-out";
ctx.arc(x, y, 20, 0, 2 * Math.PI)
ctx.fill()
}
}
// 随机中奖事件
let random = Math.random()
if (random < 0.1) {
let ggkDiv = document.getElementById("ggk");
ggkDiv.innerHTML = "恭喜你中奖了"
}
</script>
</body>
</html>
最终呈现的效果如下所示:
裁剪路径设置
裁切路径和普通的canvas图形差不多,不同的是它的作用是遮罩,用来隐藏不需要的部分,如果和上面介绍的globalCompositeOperation属性作一比较,它可以实现与source-in和source-atop差不多的效果。最重要的区别是裁切路径不会在canvas上绘制东西,而且它永远不受新图形的影响。这些特性使得它在特定区域里绘制图形时相当好用。给出如下代码示例:
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
let chatPath = new Path2D();
chatPath.moveTo(200, 300)
chatPath.quadraticCurveTo(150, 300, 150, 200);
chatPath.quadraticCurveTo(150, 100, 300, 100);
chatPath.quadraticCurveTo(450, 100, 450, 200);
chatPath.quadraticCurveTo(450, 300, 250, 300);
chatPath.quadraticCurveTo(250, 350, 150, 350);
chatPath.quadraticCurveTo(200, 350, 200, 300);
ctx.clip(chatPath) // 裁剪路径
// 获取图片
let img = new Image();
img.src = "./1.jpg";
img.onload = function () {
ctx.drawImage(img, 0, 0, 600, 400)
ctx.lineWidth = 5 // 路径的宽度
ctx.stroke(chatPath) // 显示路径,可有可无
}
</script>
</body>
最终呈现的效果如下所示:
状态保存与恢复
canvas的状态就是当前画面应用的所有样式和变形的一个快照,save和restore方法是用来保存和恢复canvas状态的且都没有参数,canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。示例代码如下所示:
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
// 绘制矩形
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);
ctx.save() // 保存当前状态
ctx.fillStyle = "green";
ctx.fillRect(100, 100, 100, 100);
ctx.save() // 保存当前状态
ctx.fillStyle = "blue";
ctx.fillRect(200, 200, 100, 100);
ctx.save() // 保存当前状态
ctx.fillStyle = "yellow";
ctx.fillRect(300, 300, 100, 100);
ctx.restore() // 恢复到上一次保存的状态
ctx.fillRect(400, 400, 100, 100) // 蓝色
ctx.restore() // 恢复到上一次保存的状态
ctx.fillRect(500, 500, 100, 100) // 绿色
</script>
</body>
最终呈现的效果如下所示:
像素操作设置
在canvas中我们可以直接通过ImageData对象操纵像素数据,直接读取或将数据数组写入该对象中,接下来我们开始讲解如何控制图像使其平滑(反锯齿)以及如何从canvas画布中保存对象。在让一些代码中我们通过getImageData获取图片的像素数据,并对其进行像素数据的修改,如下:
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById("canvas");
// 获取2维画笔,上下文对象
let ctx = canvas.getContext("2d");
// 获取图片
let img = new Image();
img.src = "./1.jpg"
img.onload = function () {
ctx.drawImage(img, 0, 0, 600, 400);
let imageData = ctx.getImageData(0, 0, 600, 400) // 获取图片信息数组
for (let i = 0; i < imageData.data.length; i+=4) {
// 计算当前像素的平均值
let avg = (imageData.data[i] + imageData.data[i+1] + imageData.data[i+2]) / 3;
imageData.data[i] = avg
imageData.data[i + 1] = avg
imageData.data[i + 2] = avg
imageData.data[i + 3] = 255
}
// 将修改后的数据呈现渲染到画布上
ctx.putImageData(imageData, 0, 0)
}
</script>
</body>
最终呈现的效果如下所示,可以看到我们对原本的图片实现了一个复古效果的展示:
如果想实现对图片像素的反向操作,可以设置如下的代码进行实现:
呈现的效果如下所示:
如果想绘制从某一坐标到另一坐标的的区域位置的话,可以通过如下代码操作:
ctx.putImageData(imageData, 0, 0, 300, 200, 600, 400)