# canvas

<canvas> 可以用于 绘制图形、照片、动画,甚至进行实时视频处理或渲染。

# 在网页上画一个圆

  • 图片

  • border + border-radius

  • svg

  • Canvas + JS动态画圆

  • clip-path 分析一下以上几种方式的优劣性:

  • 使用 div + CSS3 适用于单个的圆,实现简单,代价小,但增加没有意义DOM 节点,不符语义化。

#k{
	width:500px;
	height:500px;
	background: blue;
	clip-path: circle(50%);
}

# 什么是 svg

svg(Scalable Vector Graphics,可缩放矢量图形)是基于 XML(可扩展标记语言,标准通用标记语言的子集),用于描述二维矢量图形的一种图形格式。

简单的说就是,svg 可以用来定义 XML 格式的矢量图形。因为其本质是 XML 文件,所以 svg 是使用 XML 文档描述来绘图的。

# Canvas 和 svg 的区别

svg 本质上是一种使用 XML 描述 2D 图形的语言

svg 创建的每一个元素都是一个独立的 DOM 元素,既然是独立的 DOM 元素,那么可以通过 css 和 JS来操控 dom。可以对每一个 DOM 元素进行监听。

并且因为每一个元素都是一个 DOM 元素,所以修改 svg 中的 DOM 元素,系统会自动进行 DOM 重绘。

Canvas 通过 JavaScript 来绘制 2D 图形,Canvas 只是一个 HTML 元素,其中的图形不会单独创建 DOM 元素。因此不能通过 JS 操控 Canvas 内单独的图形,不能对其中的具体图形进行监控。

在 Canvas 中,一旦图形被绘制完成,就不会继续得到浏览器关注。如果位置发生变化,那么整个场景也需要重绘,包括任何或许已被图形覆盖的对象。

# 创建 Canvas 画布

如果没有设置宽高,那么会自动创建一个 300 * 150 的画布。

改变画布的大小呢,有三种方式

  • HTML 设置 widthheight
  • CSS 设置 widthheight
  • JS 动态设置 widthheight

# HTML 属性设置 width、height

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #canvas {
            background: rgb(220,50,100) 
        }
    </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
</canvas>
<script>
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    context.beginPath();
    context.arc(150, 150, 50, 0, Math.PI * 2, true);
    context.closePath();
    context.fillStyle = 'rgb(255,255,255)';
    context.fill();
</script>
</body>
</html>

Canvas 画布的宽度为 400,高度为 400,背景颜色为红色。在 Canvas 上画了一个圆心坐标为 150px、150px,半径为 50px 的白色的圆。

# CSS 属性设置 width、height

#canvas {
	background: rgb(220,50,100) ;
	width: 400px;
	height: 400px;
}

如果使用 CSS 来设置宽高,画布就会按照 300 * 150 的比例进行缩放,也就是将 300 * 150 的页面显示在 400 * 400 的容器中。

# JS 属性设置 width、height

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
.......

图片正常canvas.width canvas.height不需要style.width

所以尽量使用 HTML 的widthheight 属性或者直接使用 JS 动态来设置宽高,不要使用 CSS 设置。

# 获取 Canvas 对象

创建好 Canvas 画布,第二步要做的就是获取到 Canvas 的上下文环境:

 canvas.getContext(contextType, contextAttributes);
  • 上下文类型(contextType):
    • 2d:代表一个二维渲染上下文
    • webgl(或"experimental-webgl"):代表一个三维渲染上下文
    • webgl2(或"experimental-webgl2"):代表一个三维渲染上下文;这种情况只能在浏览器中实现 WebGL 版本2。

第二个参数并不是经常用到~

    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");

# 绘制路径

方法 描述
fill() 填充路径
stroke() 描边
arc() 创建圆弧
rect() 创建矩形
fillRect() 绘制矩形路径区域
strokeRect() 绘制矩形路径描边
clearRect() 在给定的矩形内清除指定的像素
arcTo() 创建两切线之间的弧/曲线
beginPath() 起始一条路径,或重置当前路径( 清除之前路径,防止改变之前绘图出来的内容被干扰 )
moveTo() 把路径移动到画布中的指定点,不创建线条
lineTo() 添加一个新点,然后在画布中创建从该点到最后指定点的线条
closePath() 创建从当前点回到起始点的路径, 闭合当前的路径
clip() 从原始画布剪切任意形状和尺寸的区域
quadraticCurveTo() 创建二次方贝塞尔曲线
bezierCurveTo() 创建三次方贝塞尔曲线
isPointInPath() 如果指定的点位于当前路径中,则返回 true,否则false

# 使用 Canvas 画一个点

context.beginPath();       // 起始一条路径,或重置当前路径
context.arc(100, 100, 1, 0, Math.PI * 2, true);  // 创建弧/曲线
context.closePath();       // 创建从当前点回到起始点的路径
context.fillStyle = 'rgb(255,255,255)'; // 设置或返回用于填充绘画的颜色、渐变或模式
context.fill();            // 填充当前绘图(路径)

使用 Canvas 绘制图像的步骤:

# 绘制弧/曲线 arc

arc() 方法创建弧/曲线(用于创建圆或部分圆)。

context.arc(x,y,r,sAngle,eAngle,counterclockwise);
  • x:圆心 x 坐标
  • y:圆心 y 坐标
  • r:圆半径
  • sAngle:起始角,以弧度计( 弧的圆形的三点钟位置是 0 度
  • eAngle:结束角,以弧度计
  • counterclockwise:可选。规定应该逆时针还是顺时针绘图。false 为顺时针,true 为逆时针

比如想画一个顺时针的四分之一圆

  context.arc(100, 100, 50, 0, Math.PI * 0.5, false);

# arcTo

在画布上创建介于两个切线之间的弧

arcTo(x1,y1,x2,y2,radius)

# bezierCurveTo 三次贝塞尔曲线

bezierCurveTo(c1x,c1y,c2x,c2y,x,y)
  • 开始点:moveTo(20,20)
  • 控制点 1:bezierCurveTo(20,100,200,100,200,20)
  • 控制点 2:bezierCurveTo(20,100,200,100,200,20)
  • 结束点:bezierCurveTo(20,100,200,100,200,20)

# quadraticCurveTo 二次贝塞尔曲线

context.quadraticCurveTo(cpx,cpy,x,y);

通过使用表示二次贝塞尔曲线的指定控制点,向当前路径添加一个点

提示:二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来定义开始点。

  • 开始点:moveTo(20,20)
  • 控制点:quadraticCurveTo(20,100,200,20)
  • 结束点:quadraticCurveTo(20,100,200,20)
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding:0
			}
			#k{
				background: skyblue;
			}
		</style>
	</head>
	<body>
		<canvas id="k" width="400" height="400"></canvas>
	</body>
	<script type="text/javascript">
		var c=document.getElementById("k");
		var ctx=c.getContext("2d");
		ctx.beginPath();
		ctx.moveTo(20,20);           // 创建开始点
		ctx.lineTo(100,20);          // 创建水平线
		ctx.arcTo(150,20,150,70,20); // 创建弧
		ctx.strokeStyle="blue"
		ctx.lineTo(150,120);         // 创建垂直线
		ctx.stroke();                // 进行绘制
		
		
		ctx.beginPath();
		ctx.moveTo(20,20);
		ctx.bezierCurveTo(20,100,200,200,200,20);
		ctx.strokeStyle="green"
		ctx.stroke();
		
		var ctx=c.getContext("2d");
		ctx.beginPath();
		ctx.moveTo(20,20);
		ctx.quadraticCurveTo(20,100,200,20);
		ctx.strokeStyle="orange"
		ctx.stroke();
	</script>
</html>

# stroke() fill()

stroke()fill() 的区别,根据上面的两个例子,我们很容易看出这两个函数的区别:一个是描边,一个是填充。

  • stroke() :描边
  • fill() :填充

我们可以通过 strokeStyle属性 和 fillStyle属性来设置描边和填充的颜色。这里不仅可以设置单一的颜色,还可以设置渐变。

# 绘制直线moveTo lineTo

    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var cx = canvas.width = 400;
    var cy = canvas.height = 400;

    context.beginPath();
    context.moveTo(50,50);
    context.lineTo(100,100);
    context.strokeStyle = '#fff';
    context.stroke();

在绘制直线的例子中,我们使用了

  • moveTo(x,y):把路径移动到画布中的指定点,不创建线条
  • lineTo(x,y):添加一个新点,然后在画布中创建从该点到最后指定点的线条

这里需要注意以下几点:

  • 如果没有 moveTo,那么第一次 lineTo 的就视为 moveTo
  • 每次 lineTo 后如果没有 moveTo,那么下次 lineTo 的开始点为前一次 lineTo 的结束点。
   <!DOCTYPE html>
   <html lang="en">
   <head>
       <meta charset="UTF-8">
       <title>Title</title>
       <style>
           #canvas {
               background: rgb(220,50,100) ;
           }
       </style>
   </head>
   <body>
   <canvas id="canvas">
   
   </canvas>
   <script>
       var canvas = document.getElementById("canvas");
       var context = canvas.getContext("2d");
       var cx = canvas.width = 400;
       var cy = canvas.height = 400;
   
       context.beginPath();
       context.lineTo(200, 200);
       context.lineTo(200, 100);
   		context.moveTo(300, 100);
       context.lineTo(100,50);
       context.strokeStyle = '#fff';
       context.stroke();
   
   </script>
   </body>
   </html>
样式 描述
lineCap 设置或返回线条的结束端点样式
lineJoin 设置或返回两条线相交时,所创建的拐角类型
lineWidth 设置或返回当前的线条宽度
miterLimit 设置或返回最大斜接长度
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var cx = canvas.width = 400;
    var cy = canvas.height = 400;

    context.beginPath();

    context.moveTo(10,10);//起点
    context.lineTo(100,100);//重点
    context.lineWidth = 10;//线条宽度
    context.lineCap = 'round';//顶端弧形
    context.strokeStyle = '#fff';//描边白色
    context.stroke()

总结

  1. closePath 闭合路径,如果不调用,路径就不会起点到终点间加上连接线(如果在style之后调用,也不会闭合路径了)
  2. 同一个canvas上样式要单独设,否则样式合并,可能会后者覆盖前者
  3. lineWidth=10不需要px
  4. closePath最好放在style样式之前,否则闭环不生效。

# 绘制矩形 rect fillRect strokeRect clearRect

    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var cx = canvas.width = 400;
    var cy = canvas.height = 400;

    context.beginPath();
    context.fillStyle = '#fff';
    context.fillRect(10, 10, 100, 100);
    context.strokeStyle = '#fff';
    context.strokeRect(130, 10, 100, 100);

  • fillRect(x,y,width,height):绘制一个实心矩形
  • strokeRect(x,y,width,height):绘制一个空心矩形
  • rect:绘制矩形
  • clearRect(x,y,width,height):擦除一个区域

通过 fillStylestrokeStyle 来设置填充的颜色和描边的颜色。

# 绘制文本fillText() strokeText()

二者都需要的传参

(要传输的字符串,x坐标,y坐标[,最大像素宽度])
context.font="blod 14px" //设置css样式
context.textAlign="center" //文本对齐方式:start end center [left, right](优先使用start/end)
context.textBaseline="middle"//文本基线:top hanging middle alphaabetic bottom...
context.strokeStyle="cadetblue"
context.strokeText("12",100,20)
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding:0
			}
			#k{
				background: skyblue;
			}
		</style>
	</head>
	<body>
		<canvas id="k" width="400" height="400"></canvas>
	</body>
	<script type="text/javascript">
		let drawing = document.getElementById("k");
		         
		if (drawing.getContext) {
		  let context = drawing.getContext("2d");
		  
		  context.beginPath();
		         
		  context.strokeStyle="green"
		  context.arc(100, 100, 99, 0, 2 * Math.PI, false);
		  
		  context.stroke();
		 
		  
		  context.beginPath()
		  context.arc(100, 100, 94, 0, 2 * Math.PI, false);
		   context.strokeStyle="orange"
		
		  context.moveTo(100, 100);
		  context.lineTo(100, 15);
		  
		 
		  context.moveTo(100, 100);
		  context.lineTo(35, 100);
		  
		  
		  context.stroke();
		  
		  context.beginPath()
		  context.font="blod 14px"
		  context.textAlign="center"
		  context.textBaseline="middle"
		  context.strokeStyle="cadetblue"
		  context.strokeText("12",100,20)
		 
		  
		}
		
	</script>
</html>

# measureText

measureText() 方法返回包含一个对象,该对象包含以像素计的指定字体宽度。

context.measureText(text).width;
let drawing = document.getElementById("k");
			 
	if (drawing.getContext) {
	  let context = drawing.getContext("2d");
	  
	  context.beginPath();
			 
	  let fontSize = 100;
	  context.font = fontSize + "px Arial";
	  while(context.measureText("Hello world!").width > 200) {
		fontSize--;
		console.log(fontSize)
		context.font = fontSize + "px Arial";
	  }
	  context.textBaseline="top"
	  context.fillText("Hello world!", 10, 10);
	  context.fillText("Font size is " + fontSize + "px", 10, 50); 
	  
	 
	  
	}

# 颜色、样式和阴影

上面几个函数教大家怎么绘制点、线、以及圆形和矩形,都是通过先创建路径,然后再使用 fill()stroke() 进行填充或者描边。

属性 描述
fillStyle 设置或返回用于填充绘画的颜色、渐变或模式
strokeStyle 设置或返回用于笔触的颜色、渐变或模式
shadowColor 设置或返回用于阴影的颜色
shadowBlur 设置或返回用于阴影的模糊级别
shadowOffsetX 设置或返回阴影距形状的水平距离
shadowOffsetY 设置或返回阴影距形状的垂直距离

# 设置阴影

设置阴影我们用到的是 shadowBlur 这个属性。


    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var cx = canvas.width = 400;
    var cy = canvas.height = 400;

    context.beginPath();
    context.arc(100,100,50,0,2*Math.PI,false);
    context.fillStyle = '#fff';
    context.shadowBlur = 20;
    context.shadowColor = '#fff';
    context.fill()

同样的方法,只要在 fill() 方法之前设置模糊指数(shadowBlur)和颜色(shadowColor)就可以了。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding:0
			}
			#k{
				background: skyblue;
			}
		</style>
	</head>
	<body>
		<canvas id="k" width="400" height="400"></canvas>
	</body>
	<script type="text/javascript">
		let drawing = document.getElementById("k");
		         
		// make sure <canvas> is completely supported
		if (drawing.getContext) {
		  let context = drawing.getContext("2d");
		  
		 // start the path
		  context.beginPath();
		         
		  // draw outer circle
		 
		 
		  context.arc(100, 100, 99, 0, 2 * Math.PI, false);
		  context.closePath()
		  context.strokeStyle="green"
		  context.stroke();
		  // draw inner circle
		  // context.moveTo(194, 100);
		 
		    
		  context.beginPath()
		  context.arc(100, 100, 94, 0, 2 * Math.PI, false);
		  context.strokeStyle="plum"
		   // stroke the path
		  context.stroke();
		  context.beginPath()
		  // draw minute hand
		  context.moveTo(100, 100);
		  context.lineTo(100, 15);
		  
		  // draw hour hand
		  context.moveTo(100, 100);
		  context.lineTo(35, 100);
		  context.strokeStyle="red"
		  // stroke the path
		  context.stroke();
		
		}
		
	</script>
</html>

# 设置渐变

方法 描述
createLinearGradient() 创建线性渐变(用在画布内容上)
createPattern() 在指定的方向上重复指定的元素
createRadialGradient() 创建放射状/环形的渐变(用在画布内容上)
addColorStop() 规定渐变对象中的颜色和停止位置

其中绘制渐变主要用到了 createLinearGradient() 方法, context.createLinearGradient(x0,y0,x1,y1);

  • x0:开始渐变的 x 坐标
  • y0:开始渐变的 y 坐标
  • x1:结束渐变的 x 坐标
  • y1:结束渐变的 y 坐标

这是设置比如说我们下一个粉色到白色的由上向下的渐变:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            #canvas {
                background: rgb(220,50,100) ;
            }
        </style>
    </head>
    <body>
    <canvas id="canvas">
    
    </canvas>
    <script>
         var canvas = document.getElementById("canvas");
        var context = canvas.getContext("2d");
        var cx = canvas.width = 400;
        var cy = canvas.height = 400;
        
        var grd = context.createLinearGradient(100,100,100,200);
        grd.addColorStop(0,'pink');
        grd.addColorStop(1,'orange');
        
        context.fillStyle = grd;
        context.fillRect(100,100,200,200);
    </script>
    </body>
    </html>

可以看出,createLinearGradient() 的参数是两个点的坐标,这两个点的连线实际上就是渐变的方向。我们可以使用 addColorStop() 方法来设置渐变的颜色。

gradient.addColorStop(stop,color);:

  • stop:介于 0.0 与 1.0 之间的值,表示渐变中开始与结束之间的位置
  • color:在结束位置显示的 CSS 颜色值

我们可以设置多个颜色断点,比如,要实现一个彩虹的效果,只需要多增加几个颜色断点就可以了~

    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var cx = canvas.width = 400;
    var cy = canvas.height = 400;

    var grd = context.createLinearGradient(0,0,0,400);
    grd.addColorStop(0,'rgb(255, 0, 0)');
    grd.addColorStop(0.2,'rgb(255, 165, 0)');
    grd.addColorStop(0.3,'rgb(255, 255, 0)');
    grd.addColorStop(0.5,'rgb(0, 255, 0)');
    grd.addColorStop(0.7,'rgb(0, 127, 255)');
    grd.addColorStop(0.9,'rgb(0, 0, 255)');
    grd.addColorStop(1,'rgb(139, 0, 255)');

    context.fillStyle = grd;
    context.fillRect(0,0,400,400);

效果如下:

context.createRadialGradient(x0,y0,r0,x1,y1,r1);

var grd=ctx.createRadialGradient(75,50,5,90,60,100);
grd.addColorStop(0,"red");
grd.addColorStop(1,"white");
draw('repeat')//repeat-x repeat-y no-repeat
function draw(direction)
{
	var c=document.getElementById("myCanvas");
	var ctx=c.getContext("2d");
	ctx.clearRect(0,0,c.width,c.height); 
	var img=document.getElementById("lamp")
	var pat=ctx.createPattern(img,direction);
	ctx.rect(0,0,220,128);
	ctx.fillStyle=pat;
	ctx.fill();
}

# 合成globalAlpha

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding:0
			}
			#k{
				background: skyblue;
			}
		</style>
	</head>
	<body>
		<canvas id="k" width="100" height="100"></canvas>
	</body>
	<script type="text/javascript">

let drawing = document.getElementById("k");
// make sure <canvas> is completely supported
if (drawing.getContext) {
  let context = drawing.getContext("2d");
   context.fillStyle = "#ff0000";
   context.fillRect(10, 10, 50, 50);
   // change the global alpha
   context.globalAlpha = 0.9;
   // draw a blue rectangle
   context.fillStyle = "rgba(0,0,255,1)";
   context.fillRect(30, 30, 50, 50);
   // reset
   context.globalAlpha = 1;
   
   context.fillStyle = "orange";
   context.fillRect(0, 0, 50, 50);
}

	
		
	</script>
</html>

# globalCompositeOperation

globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。

源图像 = 您打算放置到画布上的绘图。

目标图像 = 您已经放置在画布上的绘图。

描述
source-over 默认。在目标图像上显示源图像。
source-atop 在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。
source-in 在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。
source-out 在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。
destination-over 在源图像上方显示目标图像。
destination-atop 在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。
destination-in 在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。
destination-out 在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
lighter 显示源图像 + 目标图像。
copy 显示源图像。忽略目标图像。
xor 使用异或操作对源图像与目标图像进行组合。
let context = drawing.getContext("2d");

context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);

context.globalCompositeOperation = "source-in";

context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);

# 图形转换

方法 描述
scale() 缩放当前绘图至更大或更小 context.scale(scalewidth,scaleheight);
rotate() 旋转当前绘图
translate() 重新映射画布上的 (0,0) 位置,translate(x,y):把原点移动到(x,y)。执行这个操作后,坐标(0,0)就会变成(x,y)
transform() 替换绘图的当前转换矩阵
setTransform() 将当前转换重置为单位矩阵,然后运行 transform()

# 缩放

绘制一个矩形;放大到 200%,再次绘制矩形;放大到 200%,然后再次绘制矩形;放大到 200%,再次绘制矩形:

   var canvas = document.getElementById("canvas");
     var context = canvas.getContext("2d");
     var cx = canvas.width = 400;
     var cy = canvas.height = 400;
          
     context.strokeStyle = 'white';
     context.strokeRect(40,40,50,25);
     context.scale(2,2);
     context.strokeRect(1,1,50,25);
     context.scale(2,2);
     context.strokeStyle="orange";
     context.strokeRect(5,5,50,25);

只是使用 scale() 方法就可以实现缩放的效果,我们再来看一下浏览器中的显示情况:

可以看到,在设置 scale() 方法之后再设置的矩形,无论是线条的宽度还是坐标的位置,都被放大了。并且 scale() 的效果是可以叠加的,也就是说,我们在上面的例子中使用了两次 scale(2,2) 那么,最后一个矩形相对于第一个矩形长和宽,以及坐标的位置就放大了 4 倍。

# 旋转

    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var cx = canvas.width = 400;
    var cy = canvas.height = 400;

    context.fillStyle = 'white';
    context.rotate(20*Math.PI/180);
    context.fillRect(70,30,200,100);

我们使用的是 rotate() 方法 context.rotate(angle);

  • angle : 旋转角度,以弧度计。 如需将角度转换为弧度,请使用 degrees*Math.PI/180 公式进行计算。 举例:如需旋转 5 度,可规定下面的公式:5*Math.PI/180

在刚刚的例子中,我们将画布旋转了 20°,然后再画了一个矩形。

通过上述两个例子,我们会发现一个特点,在进行图形变换的时候,我们需要画布旋转,然后再绘制图形。

这样的结果是,我们使用的图形变换的方法都是作用在画布上的,既然对画布进行了变换,那么在接下来绘制的图形都会变换。这点是需要注意的。

比如我对画布使用了 rotate(20*Math.PI/180) 方法,就是将画布旋转了 20°,然后之后绘制的图形都会旋转 20°。

ctx.translate(70,70);//写在绘制之前
ctx.fillRect(10,10,100,50);

# restore() save()

  • save方法:保存,所有这一时刻的设置会被放到一个暂存栈中
  • restore方法:恢复之前的上下文,从暂存栈中取出并恢复之前保存的设置
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding:0
			}
			#k{
				background: skyblue;
			}
		</style>
	</head>
	<body>
		<canvas id="k" width="400" height="400"></canvas>
	</body>
	<script type="text/javascript">
	let drawing = document.getElementById("k");
	         
	// make sure <canvas> is completely supported
	if (drawing.getContext) {
	  let context = drawing.getContext("2d");
	  	  
	  context.fillStyle = "#ff0000";
	  context.save();
	           
	  context.fillStyle = "#00ff00";
	  context.translate(100, 100);
	  context.save();
	           
	  context.fillStyle = "#0000ff";
	  context.fillRect(0, 0, 100, 200);   // draws blue rectangle at (100, 100)
	           
	  context.restore();
	  context.fillRect(10, 10, 100, 200);   // draws green rectangle at (110, 110)恢复成绿色,坐标原点(100,100)
	  context.restore();
	  context.fillRect(0, 0, 100, 200);  // draws red rectangle at (0,0),恢复成红色,原点(0,,0)
	  
	  
	  
	}
		
	</script>
</html>

# 图像绘制drawImage

Canvas 还有一个经常用的方法是drawImage()。向画布上绘制图像、画布或视频

context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);

  • img:规定要使用的图像、画布或视频
  • sx:可选。开始剪切的 x 坐标位置
  • sy:可选。开始剪切的 y 坐标位置
  • swidth:可选。被剪切图像的宽度
  • sheight:可选。被剪切图像的高度
  • x:在画布上放置图像的 x 坐标位置
  • y:在画布上放置图像的 y 坐标位置
  • width:可选。要使用的图像的宽度(伸展或缩小图像)
  • height:可选。要使用的图像的高度(伸展或缩小图像)
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style media="screen">
    body {background:black; text-align:center;}
    #c1 {background:white;}
    </style>
    <script>
    window.onload=function (){
      let oC=document.getElementById('c1');
      let gd=oC.getContext('2d');

      let pause=false;
      let i=0;
      let x=100;
      let frameCounter=0;

      let oImg=new Image();
      oImg.src='fish1.png';
      oImg.onload=function (){
        requestAnimationFrame(next);
        function next(){
          if(!pause){
            gd.clearRect(0, 0, oC.width, oC.height);

            if(frameCounter%4==0){
              i++;
              if(i==4)i=0;
            }

            x+=1.5;

            gd.drawImage(
              oImg,
              //sx, sy, sw, sh
              0, i*37, 55, 37,
              //dx, dy, dw, dh
              x, 100, 55, 37
            );

            frameCounter++;
          }

          requestAnimationFrame(next);
        }
      };

      document.onclick=function (){
        pause=!pause;
      };
    };
    </script>
  </head>
  <body>
    <canvas id="c1" width="800" height="600"></canvas>
  </body>
</html>

点击画布看效果

# getImageData() putImageData() 修改图片颜色

  • getImageData():获取原始图像数据
  • putImageData(): 把图像数据再绘制到画布
getImageData(x,y,width,height)
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding:0
			}
			#k{
				background: skyblue;
			}
		</style>
	</head>
	<body>
		<img src="crz1.png" alt="" id="img">
		<canvas id="k" width="100" height="100"></canvas>
	</body>
	<script type="text/javascript">

let drawing = document.getElementById("k");
// make sure <canvas> is completely supported
if (drawing.getContext) {
  let context = drawing.getContext("2d"),
    image = document.images[0],
    imageData, data,
    i, len, average,
    red, green, blue, alpha;
  
  // draw regular size
  context.drawImage(image, 0, 0);  
  
  // get the image data
  imageData = context.getImageData(0, 0, image.width, image.height);
  data = imageData.data;
  for (i=0, len=data.length; i < len; i+=4) {
  
    red = data[i];
    green = data[i+1];
    blue = data[i+2];
    alpha = data[i+3];
    
    // get the average of rgb 每次循环红绿蓝求平均,实际相当于过滤掉颜色信息,只留下类似亮度的灰度信息
    average = Math.floor((red + green + blue) / 3);
    
    // set the colors, leave alpha alone
    data[i] = average;
    data[i+1] = average;
    data[i+2] = average;
    
  }
  
  // assign back to image data and display
  imageData.data = data;        
  context.putImageData(imageData, 0, 0);
}	
	</script>
</html>
<!-- 图片转为黑白色 -->
img {
    -webkit-filter: grayscale(100%); /* Chrome, Safari, Opera */
    filter: grayscale(100%);
}

# HTMLCanvasElement.toDataURL()

方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。

canvas.toDataURL(type, encoderOptions);

type 可选 图片格式,默认为 image/png encoderOptions 可选 在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

# canvas压缩图片

链接

# 源码

本小册中各种特效的源码地址:sunshine940326/canvas (opens new window)

# canvas画电子章

let mini ={
  cstartAngle:142,
  cendAngle:45,
  cratioX:120,
  cratioY:72,
  cradiusX:57,
  cradiusY:22,
  fillText1:5,
  fillText2:5
}
//11<=x<15
let small ={
  cstartAngle:168,
  cendAngle:12,
  cratioX:120,
  cratioY:72,
  cradiusX:57,
  cradiusY:24,
  fillText1:0,
  fillText2:5
}
//15<=X<19
let middle ={
  cstartAngle:180,
  cendAngle:5,
  cratioX:122,
  cratioY:78,
  cradiusX:60,
  cradiusY:28,
  fillText1:3,
  fillText2:5
}
//X>=19
let big= {
  cstartAngle:196,
  cendAngle:6,
  cratioX:124,
  cratioY:82,
  cradiusX:60,
  cradiusY:28,
  fillText1:10,
  fillText2:5
}
let params = big
function createSealEx(company,taxNumber){
     let length = company.length;
     if(length>=19){
       params = big
     }else if(15<=length&&length<19){
      params = middle
     }else if(11<=length&&length<15){
      params = small
     }else if(length<11){
      params = mini
     }
     var sealdiv = document.createElement("canvas"); 
     sealdiv.width =280
     sealdiv.height =280
    //  sealdiv.innerHTML ="<canvas id='sealCanvas' width='280' height='280'></canvas>";
     sealdiv.id='sealCanvas'
     sealdiv.setAttribute('style', 'position:fixed;top:200px;right:200px;display:none')
     document.body.appendChild(sealdiv)

    //  var w = sealdiv.width;
    //  var h = sealdiv.height;
     var ctx=sealdiv.getContext("2d");
     ctx.clearRect(0, 0, 280, 280);
     let url =createSeal2(company,taxNumber);
     return url
  }

  function createSeal2(company,taxNumber){
      var canvas = document.getElementById("sealCanvas");
      var context = canvas.getContext("2d");
      context.strokeStyle="red";//设置文本颜色
      context.textBaseline = 'middle';//设置文本的垂直对齐方式
      context.textAlign = 'center'; //设置文本的水平对对齐方式
      context.lineWidth = 3;//椭圆1宽度
      //3个参数: 左边距 上边据 宽度 椭圆扁度
      BezierEllipse4(context, 140, 135, 135, 95); //椭圆1
      context.lineWidth = 1;
    
      // 绘制印章类型
      if(taxNumber){
        context.font = '18px FangSong';
        context.lineWidth=1;
        context.fillStyle = '#f00';
        context.fillText(taxNumber,canvas.width/2-2,canvas.height/2+5);
        context.font = '22px FangSong';
        context.fillText('收款专用章',canvas.width/2-2,canvas.height/2+30); 
      }else{
        context.lineWidth=1;
        context.fillStyle = '#f00';
        context.font = '22px FangSong';
        context.fillText('收款专用章',canvas.width/2-2,canvas.height/2+20);
      }
     
      context.save(); 

      //绘制中文
      var ccircle={
          x:canvas.width/2,
          y:canvas.height/2,
          radius:70
      };
      var cstartAngle=params.cstartAngle;//控制字符起始位置度数
      var cendAngle =params.cendAngle;//首位字符相隔度数
      var cradius=ccircle.radius //圆的半径
      var cangleDecrement=(cstartAngle-cendAngle)/(company.length-1)//每个字母占的弧度
      context.font="14px FangSong"
      var cratioX = params.cratioX / ccircle.radius; //横轴缩放比率
      var cratioY = params.cratioY / ccircle.radius; //纵轴缩放比率
      //进行缩放(均匀压缩)
      context.scale(cratioX, cratioY);
      var cindex=0;
      for(var cindex=0;cindex<company.length;cindex++){
          context.save()
          context.beginPath()
          //绘制点
          context.translate(ccircle.x+Math.cos((Math.PI/180)*cstartAngle)*cradius-params.cradiusX,ccircle.y-Math.sin((Math.PI/180)*cstartAngle)*cradius-params.cradiusY)
          context.rotate((Math.PI/2)-(Math.PI/180)*cstartAngle)   //Math.PI/2为旋转90度  Math.PI/180*X为旋转多少度
          context.fillText(company.charAt(cindex),params.fillText1,params.fillText2)
          //context.strokeText(company.charAt(cindex),params.fillText1,params.fillText2)
          cstartAngle-=cangleDecrement
          context.restore()
      }
      

      let url = canvas.toDataURL('image/png');
      console.log(url)
      document.body.removeChild(canvas)
      return url
     

  }    
  function BezierEllipse4(ctx, x, y, a, b){
      var k = .5722848,
      ox = a * k, // 水平控制点偏移量
      oy = b * k; // 垂直控制点偏移量</p> <p> 
      ctx.beginPath();
      //从椭圆的左端点开始顺时针绘制四条三次贝塞尔曲线
      ctx.moveTo(x - a, y);
      ctx.bezierCurveTo(x - a, y - oy, x - ox, y - b, x, y - b);
      ctx.bezierCurveTo(x + ox, y - b, x + a, y - oy, x + a, y);
      ctx.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b);
      ctx.bezierCurveTo(x - ox, y + b, x - a, y + oy, x - a, y);
      ctx.closePath();
      ctx.stroke();
  };
  
// document.getElementById('markpic').onclick =function(){
//    var img = document.getElementById('myimg');
//    img.src = canvas.toDataURL('image/png');
//    console.log(img)
// }


export default createSealEx
最后更新: 5/10/2022, 7:43:13 AM