- 1
- var c = document.getElementById("myCanvas");
- 2
- var ctx = c.getContext("2d");
- 3 ctx.fillStyle = "red";
- 4 ctx.fillRect(0, 0, 300, 150);
- 5 ctx.clearRect(20, 20, 100, 50);
引用 globalCompositeOperation()函数,这个函数是用来在画布上组合颜色,我们可以利用这个原理,叠加 (数学上的" 或 "原理) 来制作橡皮。
首先看看 globalCompositeOperation 属性可以设置的值有哪些,分别是什么效果:
值 | 描述 |
---|---|
source-over | 默认。在目标图像上显示源图像。 |
source-atop | 在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。 |
source-in | 在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。 |
source-out | 在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。 |
destination-over | 在源图像上方显示目标图像。 |
destination-atop | 在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。 |
destination-in | 在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。 |
destination-out | 在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。 |
lighter | 显示源图像 + 目标图像。 |
copy | 显示源图像。忽略目标图像。 |
xor | 使用异或操作对源图像与目标图像进行组合。 |
- 1 2 3 4 5 canvas 6 {
- 7 border: 1px solid#d3d3d3;
- 8 margin - right: 10px;
- 9 margin - bottom: 20px;
- 10
- }
- 11 12 13 14 15 16 17
- var gco = new Array();
- 18 gco.push("source-atop");
- 19 gco.push("source-in");
- 20 gco.push("source-out");
- 21 gco.push("source-over");
- 22 gco.push("destination-atop");
- 23 gco.push("destination-in");
- 24 gco.push("destination-out");
- 25 gco.push("destination-over");
- 26 gco.push("lighter");
- 27 gco.push("copy");
- 28 gco.push("xor");
- 29
- for (n = 0; n) 30 {
- 31 document.write("
- " + gco[n] + ":
- ");
- 32
- var c = document.createElement("canvas");
- 33 c.width = 120;
- 34 c.height = 100;
- 35 document.getElementById("p_" + n).appendChild(c);
- 36
- var ctx = c.getContext("2d");
- 37 ctx.fillStyle = "blue";
- 38 ctx.fillRect(10, 10, 50, 50);
- 39 ctx.globalCompositeOperation = gco[n];
- 40 ctx.beginPath();
- 41 ctx.fillStyle = "red";
- 42 ctx.arc(50, 50, 30, 0, 2 * Math.PI);
- 43 ctx.fill();
- 44 document.write("");
- 45
- }
- 46 47 48 49 50
可以看出如果设置成 destination-out,就可以清除 canvas 现有的像素点的图像。
在我最近实现的项目中有画笔功能, 同时画笔画出的线条可以被橡皮擦擦除,有点擦除和线擦除两种方式。
使用以上两种方法也可以,但是如果这些线条不止绘制一次的话呢,中间有其他操作(例如绘制的内容变换一次后)那上面的方法就不容易做到了,因为要反复绘制存储每次擦除后的数据,简单的为了能达到该目的,可以将整个 canvas 画布转化成 base64 编码的 image,后面再次绘制的时候把这个 image 数据再绘制到 canvas 上,可以继续在这个 canvas 上进行绘制和擦除内容。但是怎么样也不好做到线擦除的功能了!
下面介绍另外一种存储绘制路径点坐标的方法去实现绘制线条后的点擦除和线擦除的功能。
首先介绍下存储线条的数据结构,之前写的一篇《js 实现存储对象的数据结构 hashTable 和 list》大家可以先大致看看 hash 结构的实现,但是 key 和 value 快速查找的优势需要清楚。另外在 canvas 画的各种形状和线条,我们是如何知道点击到哪个元素哪条线?《软件项目技术点 (4)——实现点击画布上元素》这篇博客里有说明实现原理。
项目中我存储的线条 hash 结构的对象如下:
展开第一个线条 key 值为 "#8c471a" 的具体信息如下,value 值其中有 colorKey,lineColor,lineWidth,以及最重要的 List 结构的 points 对象,是一个存储了该线条所有点坐标集合的 List 对象。
下面的一段代码,实现了绘制该线条到画布。使用二次贝塞尔函数使得绘制出来的线条流畅平滑没有折痕,当只有一个点时可绘制出一个圆点。
- 1
- var count = this.points.length();
- 2
- var p: Core.Point = this.points.get(0);
- 3
- if (isDrawHit) {
- 4 ctx.strokeStyle = this.colorKey;
- 5
- }
- 6
- else {
- 7 ctx.strokeStyle = this.lineColor;
- 8
- }
- 9 ctx.lineCap = "round";
- 10 ctx.lineJoin = 'round'; //转折的时候不出现尖角
- 11
- if (ctx.canvas.id == "hitCanvas") 12 ctx.lineWidth = this.lineWidth + eraserRadius; //扩大hit上线条的范围,橡皮半径
- 13
- else 14 ctx.lineWidth = this.lineWidth;
- 15 ctx.beginPath();
- 16
- if (count >= 2) {
- 17 ctx.moveTo(p.x, p.y);
- 18
- for (var i = 1; i < count - 2; i++) {
- 19 // p = this.points.get(i);
- 20 // ctx.lineTo(p.x, p.y);
- 21
- if (this.points.get(i).x == this.points.get(i + 1).x && this.points.get(i).y == this.points.get(i + 1).y) 22
- continue;
- 23
- var c = (this.points.get(i).x + this.points.get(i + 1).x) / 2;
- 24
- var d = (this.points.get(i).y + this.points.get(i + 1).y) / 2;
- 25 ctx.quadraticCurveTo(this.points.get(i).x, this.points.get(i).y, c, d); //二次贝塞曲线函数
- 26
- }
- 27 // For the last 2 points
- 28
- if (count >= 3) {
- 29 ctx.quadraticCurveTo(30 this.points.get(i).x, 31 this.points.get(i).y, 32 this.points.get(i + 1).x, 33 this.points.get(i + 1).y 34);
- 35
- } else if (count >= 2) {
- 36 ctx.lineTo(this.points.get(1).x, this.points.get(1).y);
- 37
- }
- 38 ctx.stroke();
- 39
- } else {
- 40
- if (isDrawHit) {
- 41 ctx.fillStyle = this.colorKey;
- 42
- }
- 43
- else {
- 44 ctx.fillStyle = this.lineColor;
- 45
- }
- 46
- if (ctx.canvas.id == "hitCanvas") 47
- var radius = this.lineWidth + eraserRadius; //扩大hit上线条的范围,橡皮半径
- 48
- else 49
- var radius = this.lineWidth;
- 50 ctx.arc(this.points.get(0).x, this.points.get(0).y, radius, 0, 2 * Math.PI);
- 51 ctx.fill();
- 52
- }
- 53
其中绘制到 hitCanvas 上的时候将 lineWidth 扩大加上了 eraserRadius(圆形橡皮擦半径),下图即为绘制到 hitCanvas 上的 colorKey 颜色线条,每个线条颜色值是上图中的 key 值 colorKey。另外线条粗细明显比上面的白色线条要粗很多,因为橡皮擦是个 cur 鼠标样式它的半径很大,但获取的鼠标点击位置还只是一个像素点坐标,所以为了扩大鼠标点到线条上的范围将其变粗。
这样线擦除就很容易实现,只需要找到橡皮擦点到画布上的坐标点的色值,就其从 hash 集合中根据 colorKey 删除掉该项,即实现了删除整条线。
点擦除就需要考虑到从两端擦除或者从中间擦除的情况:
- 1
- if (that.isErasePoint) {
- 2 line.points.foreach(function(i, p) {
- 3 //橡皮擦距离该线条上点的距离是否在橡皮擦半径范围内
- 4
- if (Math.pow(p.x - point.x, 2) + Math.pow(p.y - point.y, 2) <= Math.pow(eraserRadius, 2)) {
- 5 isSeparate = true; //已经找到橡皮擦半径范围内的点,该点不存入两个集合中的任何一个
- 6
- } else {
- 7
- if (isSeparate) //找到后将之后的点存入另一个点集合points2中
- 8 points2.add(p);
- 9
- else //找到之前将点存入点集合points1中
- 10 points1.add(p);
- 11
- }
- 12
- }) 13 //遍历完线条points上的所有点后。根据points1和points2是否为空处理点擦除后的线条
- 14
- if (points1.length() >= 1 && points2.length() >= 1) { //points1和points2都不为空,说明从中间擦除变为两条线
- 15
- var preLine = editor.commonEditLogic.clonePenLine(line);
- 16 line.points = points1;
- 17
- var linePen = editor.bdCanvas.elementFactory.createPenLine(point, line.lineWidth, line.lineColor);
- 18 linePen.points = points2;
- editor.bdCanvas.activeElement.setPenLine(linePen.colorKey, linePen);
- 19
- } else if (points1.length() == 0 && points2.length() >= 1) { //从一端擦除
- 20 line.points = points2;
- 21
- } else if (points1.length() >= 1 && points2.length() == 0) { //从一端擦除
- 22 line.points = points1;
- 23
- } else if (points1.length() == 0 && points2.length() == 0) { //线条上的点全部被擦除,删除该线条
- 24 editor.bdCanvas.activeElement.delPenLine(line.colorKey);
- 26
- }
- 27 editor.courseware.currentBlackboard.draw(false, true);
- 30
- }
来源: http://www.cnblogs.com/fangsmile/p/7171789.html