画布(Canvas)实现技术

重要通知

canvas技术,是基于HTML5与JavaScript综合的高级技术,通过对外提供二维图像API接口,可以用于图像绘制处理、实时视频处理和渲染。

基本概况

基础简介

  • 兼容性:不支持IE8及以下版本
  • 兼容解决方案:第一、使用explorecanvas,支持IE8及IE8以下;第二、使用Google Chrome Frame,将IE引擎替换为Google Chrome引擎;
  • 默认canvas元素大小是300*150个屏幕像素,设置canvas属性时, 不能使用"px"单位, 因为已经默认

坐标系统

  • 原点:canvas画布左上角;
  • X坐标:向右方增长;
  • Y坐标:向下方延伸。
/**
 * 将鼠标坐标转换为canvas坐标
 * @param {*} canvas:canvas对象
 * @param {*} x:document文档 X 坐标
 * @param {*} y:document文档 Y 坐标
 */
export function windowToCanvas(canvas, x, y) {
  const clientRect = canvas.getBoundingClientRect();

  return {
    x: x - clientRect.left * (canvas.width / clientRect.width),
    y: y - clientRect.top * (canvas.height / clientRect.height)
  }
}

基础示例

const canvas = document.createElement( 'canvas' );
const ctx	= canvas.getContext( '2d' );

ctx.imageSmoothingEnabled	= false;        // 设置图像平滑度的属性。  
ctx.webkitImageSmoothingEnabled	= false;
ctx.mozImageSmoothingEnabled = false;

function updateCanavsResize() {
  const width = document.body.clientWidth;
  const height = document.body.clientHeight;
  canvas.setAttribute('width', width);
  canvas.setAttribute('height', height);
}

updateCanavsResize();
document.body.append(canvas);

window.onresize = function () {
  updateCanavsResize();
}

性能优化

  • 使用空间划分算法(如四叉树)优化碰撞检测
  • 实现脏矩形渲染,避免全量重绘

canvas 2d绘图环境

canvasRenderingContext2D对象

canvas对象属性

  • 当设置canvas元素的width与height属性值时, 实际上是同时修改了canvas元素自身的大小与canvas元素绘图表面的大小
  • 通过CSS属性修改canvas元素的大小, 仅仅修改了canvas元素自身的大小, canvas绘图表面大小依然为默认的300*150个屏幕像素
-------------------------------------------------------------------------------------------------------
	属性       描述                    类型          取值范围                      默认值
-------------------------------------------------------------------------------------------------------
	width     canvas元素绘图的宽度      非负整数       在有效范围内的任意非负整数       300
	height    canvas元素绘图的高度      非负整数       在有效范围内的任意非负整数       150
-------------------------------------------------------------------------------------------------------

getContext("2d") 属性

--------------------------------------------------------------------------------------------------------------------------
	属性                            简介
--------------------------------------------------------------------------------------------------------------------------
  canvas                          获取canvas对象的宽度与高度, 例如:ctx.canvas.width与ctx.canvas.height	
  
  # 文本
  font	                          设置或返回文本内容的当前字体属性					
  textAlign	                      设置或返回文本内容的当前对齐方式				
  textBaseline	                  设置或返回在绘制文本时使用的当前文本基线

  # 颜色、样式和阴影		
  fillStyle	                      设置或返回用于填充绘画的颜色、渐变或模式					
  strokeStyle	                    设置或返回用于笔触的颜色、渐变或模式					
  shadowColor	                    设置或返回用于阴影的颜色					
  shadowBlur	                    设置或返回用于阴影的模糊级别,该值为非负、非无穷的double值,默认值为0。					
  shadowOffsetX	                  设置或返回阴影距形状的水平距离				
  shadowOffsetY	                  设置或返回阴影距形状的垂直距离

  # 线条样式
  lineCap                         设置或返回线条末端线帽的样式;取值类型为butt(平直,默认值)、round(圆形)、square(方形)。
  lineWidth                       定义线段像素宽度,该值为非负、非无穷的double值,默认值为0,Number类型。
  lineJoin                        定义两条线段相交时焦点类型, 取值为'bevel''round'(圆角)'miter'(方角,默认值),String类型。
  miterLimit                      设置或返回最大斜接长度,斜接长度指在两条线交汇处内角和外角之间的距离,当lineJoin = miter有效。					
  globalAlpha                     定义全局透明度, 取值0(完全透明)~1.0(完全不透明);浏览器会将每个像素的alpha值与该值相乘。

  # 合成
  globalCompsiteOperation         设置或返回如何将一个源(新的)图像绘制到目标(已有的)的图像上。
  imageSmoothingEnabled           图像平滑的方式;如果禁用,缩放时,图像不会被平滑处理。
--------------------------------------------------------------------------------------------------------------------------

getContext("2d") 方法

--------------------------------------------------------------------------------------------------------------------------
	方法                            简介
--------------------------------------------------------------------------------------------------------------------------
  # 文本					
  fillText()	                    在画布上绘制“被填充的”文本				
  strokeText()	                  在画布上绘制文本(无填充)					
  measureText()	                  返回包含指定文本宽度的对象, 可以精准计算出文本的宽度
  
  # 路径					
  fill()	                        填充当前绘图(路径)					
  stroke()	                      绘制已定义的路径				
  beginPath()	                    起始一条路径,或重置当前路径					
  moveTo()	                      把路径移动到画布中的指定点,不创建线条				
  closePath()	                    创建从当前点回到起始点的路径				
  lineTo()	                      添加一个新点,然后在画布中创建从该点到最后指定点的线条					
  clip()	                        从原始画布剪切任意形状和尺寸的区域					
  quadraticCurveTo()	            创建二次贝塞尔曲线					
  bezierCurveTo()	                创建三次方贝塞尔曲线				
  arc()	                          创建弧/曲线(用于创建圆形或部分圆)					
  arcTo()	                        创建两切线之间的弧/曲线					
  isPointInPath()	                如果指定的点位于当前路径中,则返回 true,否则返回 false

  # 颜色、样式和阴影
  createLinearGradient()	        创建线性渐变(用在画布内容上)					
  createPattern()	                在指定的方向上重复指定的元素					
  createRadialGradient()	        创建放射状/环形的渐变(用在画布内容上)					
  addColorStop()	                规定渐变对象中的颜色和停止位置
  
  # 矩形					
  rect()	                        创建矩形					
  fillRect()	                    绘制“被填充”的矩形					
  strokeRect()	                  绘制矩形(无填充)					
  clearRect()	                    在给定的矩形内清除指定的像素
  
  # 转换
  scale()	                        缩放当前绘图至更大或更小,例如:ctx.scale(2,2);					
  rotate()	                      旋转当前绘图,例如:ctx.rotate(1*Math.PI);					
  translate()	                    重新映射画布上的 (0,0) 位置				
  transform()	                    替换绘图的当前转换矩阵,rotate()、scale()、translate() 或 transform() 完成的其他变换					
  setTransform()	                将当前转换重置为单位矩阵。然后运行 transform()

  # 状态的保存和恢复
  save()	                        保存当前环境的状态。
  restore()	                      返回之前保存过的路径状态和属性。

  # 其他
  createEvent()	 
  getContext()	 
  toDataURL()
--------------------------------------------------------------------------------------------------------------------------

toBlob(callback, type, args)

创建一个用于表示此canvas元素图像文件的Blob

canvas.toBlob(
  callback,  // 回调函数,浏览器会以一个指向blob的引用为参数,去调用该回调函数
  type,      // 图像类型,例如:'image/jpeg' | 'image/png'
  args       // 图像质量,例如:0 ~ 1
);

globalCompositeOperation 属性

合成,设置或返回如何将一个源(新的)图像绘制到目标(已有的)的图像上。 源图像:您打算放置到画布上的绘图。 目标图像:您已经放置在画布上的绘图。

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

状态的保存和恢复

ctx.fillRect(0, 0, 150, 150);
ctx.save();

ctx.fillStyle = 'red';
ctx.fillRect(15, 15, 120, 120);

ctx.save();
ctx.fillStyle = '#FFF';
ctx.fillRect(30, 30, 90, 90);

ctx.restore();
ctx.fillRect(45, 45, 60, 60);

ctx.restore();
ctx.fillRect(60, 60, 30, 30);

颜色、样式和阴影

属性列表

应用于线条、文本、图形、图像等对象。

------------------------------------------------------------------------------------------------------------
  属性	              描述
------------------------------------------------------------------------------------------------------------
  fillStyle	         设置或返回用于填充绘画的颜色、渐变或模式。
  strokeStyle	       设置或返回用于笔触的颜色、渐变或模式。
  shadowColor	       设置或返回用于阴影的颜色。
  shadowBlur	       设置或返回用于阴影的模糊级别,数值越大,模糊扩展范围越大(该值与像素无关)。
  shadowOffsetX	     设置或返回阴影与形状的水平距离。
  shadowOffsetY	     设置或返回阴影与形状的垂直距离。
------------------------------------------------------------------------------------------------------------

方法列表

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

渐变

addColorStop() 方法

规定渐变对象中的颜色和位置,与 createLinearGradient() 或 createRadialGradient() 一起使用。

线性渐变

渐变方向:左~右,上~下 填充范围:使用最后一种颜色来填充渐变线以外的区域

  var radient = ctx.createLinearGradient(
    startX,  // 线性渐变起始位置X坐标
    startY,  // 线性渐变起始位置y坐标
    endX,    // 线性渐变终止位置x坐标
    endY     // 线性渐变终止位置y坐标
  );  

放射渐变

(该方法掌握难度太大,请静下心来仔细研究总结) 放射渐变有两种情况:第一种为起始圆与终止圆的圆心坐标一致,此时渐变规律比较观察;第二种为起始圆与终止圆圆心不同,此时太复杂。 渐变方向:当startRadius < endRadius时,由内向外;当startRadius > endRadius时,由外向内 填充范围:仅局限于由传递给createRadialGradient()方法的两个圆参数定义的区域之内

var grd = ctx.createRadialGradient(
  startX,        // 内圆圆心坐标X
  startY,        // 内圆圆心坐标Y
  startRadius,   // 内圆半径(该半径内颜色不渐变,半径外部开始渐变)
  endX,          // 外圆圆心坐标X
  endY,          // 外圆圆心坐标Y
  endRadius      // 外圆半径(放射区域:endRadius - startRadius)
);

实例

(startRadius < endRadius时,颜色渐变由内向外)

var grd = ctx.createRadialGradient(50, 50, 0, 50, 50, 100);
grd.addColorStop(0, '#f00');  
grd.addColorStop(1, "#00f"); 
ctx.fillStyle = grd;
ctx.fillRect(50, 50, 100, 100);	

实例

(startRadius endRadius时,颜色渐变由外向内)

var grd = ctx.createRadialGradient(50, 50, 100, 50, 50, 0);
grd.addColorStop(0, '#f00');  
grd.addColorStop(1, "#00f"); 
ctx.fillStyle = grd;
ctx.fillRect(50, 50, 100, 100);  

实例

(起始圆、终止圆与正方形中心相同时,图形很有规律)

var grd = ctx.createRadialGradient(150, 150, 0, 150, 150, 100);
grd.addColorStop(0, '#f00');  
grd.addColorStop(1, "#00f"); 
ctx.fillStyle = grd;
ctx.fillRect(50, 50, 200, 200);  

几何图形

线条(Line)

  • 属性列表
------------------------------------------------------------------------------------------------------------
  属性	              描述
------------------------------------------------------------------------------------------------------------
  lineCap	设置或返回线条的结束端点样式。
lineJoin	设置或返回两条线相交时,所创建的拐角类型。
lineWidth	设置或返回当前的线条宽度。
miterLimit	设置或返回最大斜接长度。
------------------------------------------------------------------------------------------------------------
	// 线段 • 折线:起点 | 终点
	ctx.moveTo(20, 50); 
	ctx.lineTo(500, 110);  
	ctx.closePath();
	ctx.stroke();  	

  ctx.lineWidth = 20;       // 设置或返回当前的线条宽度。默认值是 1.0,并且这个属性必须大于 0.0。
	ctx.lineJoin = "round";   // 设置或返回两条线相交时,所创建的拐角类型。合法的值是 "round"、"bevel" 和 "miter",默认值是 "miter"。
	ctx.lineCap = 'butt';     // 设置或返回线条的结束端点样式,其值分别为:"butt"、"round" 和 "square",默认值是 "butt"。
	ctx.miterLimit = 10;      // 设置或返回最大斜接长度,表示斜面长度和线条长度的比值。当ctx.lineJoin = "miter"时,通过该属性调整斜面

虚线

	function dashedLineTo(ctx, x1, y1, x2, y2, dashLength) {
		dashLength = dashLength === undefined ? 5 : dashLength;
		var deltaX = x2 - x1;
		var deltaY = y2 - y1;
		var nameDashes = Math.floor(Math.sqrt(deltaX * deltaX + deltaY * deltaY) / dashLength);
		for (var i = 0; i < nameDashes; i++)
		{
			ctx[i % 2 === 0 ? 'moveTo' : 'lineTo'](x1 + (deltaX / nameDashes) * i, y1 + (deltaY / nameDashes) * i);
		}
		ctx.stroke();
	}
	ctx.lineWidth = 3;
	ctx.strokeStyle = 'blue';
	dashedLineTo(ctx, 20, 20, ctx.canvas.width - 20, 20, 5);
	dashedLineTo(ctx, ctx.canvas.width - 20, 20, ctx.canvas.width - 20, ctx.canvas.height - 20, 10);
	dashedLineTo(ctx, ctx.canvas.width - 20, ctx.canvas.height - 20, 20, ctx.canvas.height - 20, 15);
	dashedLineTo(ctx, 20, ctx.canvas.height - 20, 20, 20, 2);	

矩形(Rect)

  • 方法列表
------------------------------------------------------------------------------------------------------------
  方法	              描述
------------------------------------------------------------------------------------------------------------
  rect()	创建矩形。
fillRect()	绘制"被填充"的矩形。
strokeRect()	绘制矩形(无填充)。
clearRect()	在给定的矩形内清除指定的像素。
------------------------------------------------------------------------------------------------------------
	ctx.strokeRect(  // 描边
		double x, 
		double y, 
		double width, 
		double height
	);

	ctx.fillRect(  // 填充
		double x, 
		double y, 
		double width, 
		double height
	);

	clearRect(    // 清除
		double x, 
		double y, 
		double width, 
		double height
	);
  // 笔触的默认颜色是黑色
  ctx.fillStyle = "#f00";      // 背景色
	ctx.rect(10, 10, 150, 100);  // 长宽
	ctx.fill();                  // 填充
	ctx.stroke();                // 描边-边框

	ctx.fillStyle = "#0f0";      // 可以设置透明度:'rgba(0, 0, 0, 0.5)'
	ctx.fillRect(200, 10, 150, 100);

	ctx.strokeStyle = "#000";
  ctx.strokeRect(400, 50, 150, 100);



	// 实例(清除)
	ctx.canvas.onclick = function() {
		ctx.clearRect(10, 10, ctx.canvas.width, ctx.canvas.height);
	}

圆形-圆弧

圆形(圆形处于半封闭状态时,便可以成为圆弧,这里比较特殊,也很复杂,请仔细研究)

特别注意,特别声明,绘制圆形的填充颜色区域或描边颜色区域是在圆环上的起始位置、在圆环上的终止位置与该两点之间的圆环封闭区域

	ctx.arc(
		Number x,           // 圆心X坐标
		Number y,           // 圆心y坐标
		Number radius,      // 圆半径
		Number startRdian,  // 圆在圆环上的起始位置,以弧度为单位,例如:0
		Number stopRdian,   // 圆在圆环上的终止位置,以弧度为单位,例如:2 * Math.PI
		Boolean direction   // 取值为true(逆时针方向)、false(默认值,顺时针方向)
	);

代码示例

	// 实例(描边,当第五个参数不等于2 * Math.PI或不等于2 * Math.PI的整倍数时,绘制为圆弧,且弧线不关闭,起始点与终止点没有实线连接)
	ctx.strokeStyle = '#f00';
	ctx.beginPath();
	ctx.arc(150, 150, 100, 0, 1 * Math.PI, true);
	ctx.stroke();

	// 实例(填充圆)
	ctx.fillStyle = '#f00';
	ctx.beginPath();
	ctx.arc(150, 150, 100, 0, 0.1 * Math.PI, true);
	ctx.closePath();  //声明关闭路径
	ctx.fill();	

圆弧:创建介于两个切线(先设置一个点,由前四个参数构成两个点,三个点两个切线)之间的弧/曲线

通常情况下,由该方法ctx.moveTo(20, 20)定义第一条切点的起始点坐标 startX与stopX最好设置为相同y坐标,如此效果比较明显,容易总结规律

	ctx.arcTo(
		Number startX,  // 两切线交点的横坐标
		Number startY,  // 两切线交点的纵坐标
		Number stopX,   // 第二条切线上一点的x坐标
		Number stopY,   // 第二条切线上一点的y坐标
		Number radius   // 圆弧半径
	);

代码示例

	// 实例(描边,使用ctx.closePath()时,起始点与终止点之间存在实线连接,即处于封闭状态
	ctx.beginPath();
	ctx.arc(150, 150, 100, 0, 1 * Math.PI, true);
	ctx.strokeStyle = '#f00';
	ctx.closePath();
	ctx.stroke();
				
	// 实例(arcTo()方法,该方法掌握难度太大,请静下心来仔细研究总结)	
	ctx.beginPath();
	ctx.moveTo(20, 20);  //圆弧的第一条切线起始点坐标
	ctx.arcTo(150, 20, 150, 200, 100); // 创建圆弧
	ctx.stroke(); 

路径(Path)

方法列表

------------------------------------------------------------------------------------------------------------
  方法	              描述
------------------------------------------------------------------------------------------------------------
  fill()	填充当前绘图(路径)。
  stroke()	绘制已定义的路径。
  beginPath()	起始一条路径,或重置当前路径。
  moveTo()	把路径移动到画布中的指定点,不创建线条。
  closePath()	创建从当前点回到起始点的路径。
  lineTo()	添加一个新点,然后在画布中创建从该点到最后指定点的线条。
  clip()	从原始画布剪切任意形状和尺寸的区域,裁剪路径的作用是遮罩。只显示裁剪路径内的区域,裁剪路径外的区域会被隐藏。
  quadraticCurveTo()	创建二次贝塞尔曲线。
  bezierCurveTo()	创建三次贝塞尔曲线。
  arc()	创建弧/曲线(用于创建圆形或部分圆)。
  arcTo()	创建两切线之间的弧/曲线。
  isPointInPath()	如果指定的点位于当前路径中,则返回 true,否则返回 false。 
------------------------------------------------------------------------------------------------------------

平方贝塞尔曲线

二次曲线,由三个点定义,两个描点,一个控制点。

	quadraticCurveTo(
		double controlX,  // 控制点x坐标
		double controlY,  // 控制点y坐标
		double stopX,     // 第二个描点的x坐标
		double stopY      // 第二个描点的y坐标
	)
  • 代码示例
	ctx.strokeStyle = "#f00";
	ctx.beginPath();
	ctx.moveTo(20, 20);
	ctx.quadraticCurveTo(50, 30, 70, 100);
	ctx.stroke(); 

立方贝塞尔曲线

三次曲线,由四个点定义,两个描点,两个控制点。

	ctx.bezierCurveTo(
		double control1X,  // 第一个控制点x坐标
		double control1Y,  // 第一个控制点y坐标
		double control2X,  // 第二个控制点x坐标
		double control2Y,  // 第二个控制点y坐标
		double stopX,      // 第二个描点的x坐标
		double stopY       // 第二个描点的y坐标
	);
  • 代码示例
	ctx.strokeStyle = "#f00";
	ctx.beginPath();
	ctx.moveTo(20, 20);
	ctx.bezierCurveTo(200, 80, 20, 150, 200, 200);
	ctx.stroke(); 

基本操作

  • 方法列表
------------------------------------------------------------------------------------------------------------
  方法	              描述
------------------------------------------------------------------------------------------------------------
  scale()	缩放当前绘图至更大或更小。
  rotate()	旋转当前绘图。
  translate()	重新映射画布上的 (0,0) 位置。
  transform()	替换绘图的当前转换矩阵。
  setTransform()	将当前转换重置为单位矩阵。然后运行 transform()。
------------------------------------------------------------------------------------------------------------

平移 translate

translate(x, y): 重新映射画布上的 (0,0) 位置 x:添加到水平坐标(x)上的值。 y:添加到垂直坐标(y)上的值。

旋转 rotate

rotate(angle):旋转当前绘图 angle:旋转角度,以弧度计。

缩放 scale

scale(scalewidth, scaleheight):缩放当前绘图至更大或更小 scalewidth:缩放当前绘图的宽度(1=100%,0.5=50%,2=200%,依次类推)。 scaleheight:缩放当前绘图的高度(1=100%,0.5=50%,2=200%,依次类推)

变换 transform

transform(a, b, c, d, e, f):替换绘图的当前转换矩阵 该变换只会影响 transform() 方法调用之后的绘图。 transform() 方法的行为相对于由 rotate()、scale()、translate() 或 transform() 完成的其他变换。

	ctx.transform(
		a,  // 水平缩放绘图。
		b,  // 水平倾斜绘图。
		c,  // 垂直倾斜绘图。
		d,  // 垂直缩放绘图。
		e,  // 水平移动绘图。
		f   // 垂直移动绘图。
	);

setTransform()

画布上的每个对象都拥有一个当前的变换矩阵。 把当前的变换矩阵重置为单位矩阵,然后以相同的参数运行 transform()。 setTransform() 允许您缩放、旋转、移动并倾斜当前的环境。

	ctx.setTransform(
		a,  // 水平缩放绘图。
		b,  // 水平倾斜绘图。
		c,  // 垂直倾斜绘图。
		d,  // 垂直缩放绘图。
		e,  // 水平移动绘图。
		f   // 垂直移动绘图。
	);	

裁剪(clip)

ctx.clip();

从原始画布中剪切任意形状和尺寸。 一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。 您也可以在使用 clip() 方法前通过使用 save() 方法对当前画布区域进行保存,并在以后的任意时间对其进行恢复(通过 restore() 方法)。 所以可以通过清除制定范围来实现擦除功能。

实现功能 | 橡皮檫 | 放大镜 | 查看器

橡皮檫

import { windowToCanvas } from '/public/library/canvas.js';


ctx.fillStyle = "#f00";      // 背景色
ctx.rect(0, 0, canvas.width, canvas.height);  // 长宽
ctx.fill();                  // 填充

let action = { start: false, startX: null, startY: null, };
const rubberSize = 25;

document.querySelector('canvas').addEventListener('mousedown', function() {
  action.start = true;
}, false);

document.querySelector('canvas').addEventListener('mousemove', function(e) {
  if (!action.start) return;

  const coordinate = windowToCanvas(canvas, e.clientX, e.clientY);

  ctx.save();
  ctx.beginPath();
  // 绘制圆形
  ctx.arc(coordinate.x, coordinate.y, rubberSize/2, 0, Math.PI * 2);
  // 声明裁剪区域为圆形
  ctx.clip();
  // 清除所有区域,但是由于声明了裁剪机制,则仅仅清除了与圆形相交的公共区域。
  ctx.clearRect(coordinate.x - rubberSize/2, coordinate.y - rubberSize/2, rubberSize, rubberSize);
  ctx.restore();
}, false);

document.querySelector('canvas').addEventListener('mouseup', function() {
  action.start = false;
}, false);

Text文本

属性列表

------------------------------------------------------------------------------------------------------------
  属性	              描述
------------------------------------------------------------------------------------------------------------
  font	              设置或返回文本内容的当前字体属性。
  textAlign	          设置或返回文本内容的当前对齐方式。
  textBaseline	      设置或返回在绘制文本时使用的当前文本基线。
------------------------------------------------------------------------------------------------------------

方法列表

------------------------------------------------------------------------------------------------------------
  方法	              描述
------------------------------------------------------------------------------------------------------------
  fillText()	        在画布上绘制"被填充的"文本。
  strokeText()	      在画布上绘制文本(无填充)。
  measureText()	      返回包含指定文本宽度的对象。
------------------------------------------------------------------------------------------------------------

strokeText

文本(描边) | 文本轮廓

ctx.strokeText(
  String content,  // 需要填充的文本
  Number x,        // 该文本位于canvas元素中的x坐标
  Number y         // 该文本位于canvas元素中的y坐标
);


ctx.font = "48px Arial";
ctx.strokeStyle = "#f00";
ctx.strokeText("你好, 欢迎使用canvas", 50, 50);

fillText

文本(填充) | 填充文本

ctx.fillText(
  String content,  // 需要填充的文本
  Number x,        // 该文本位于canvas元素中的x坐标
  Number y         // 该文本位于canvas元素中的y坐标
);


ctx.font = "48px Arial";
ctx.fillStyle = "#f00";
ctx.fillText("文本", 190, 100);

measureText

计算字符串宽度

function calculateTextSize(text = '文本', fontSize = 16, fontFamily = '宋体') {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext("2d");   

  ctx.font = `${fontSize} ${fontFamily}`;
  ctx.fillStyle = '#000000';
  const { width } = ctx.measureText(text);  // 文案尺寸总长度

  return width;
}

function getStyle(nodeName, keyName) {
  // nodeName:"#id"、".class"、"div"、document.querySelector(target)
  if (!nodeName || !((nodeName).nodeType === 1 || typeof nodeName === 'string')) throw new Error('缺失节点对象');
  if (typeof nodeName === 'string' && !document.querySelector(nodeName)) throw new Error('缺失节点对象');

  var nodeTarget = (nodeName).nodeType === 1 ? nodeName : document.querySelector(nodeName);

  // 判断浏览器是否为IE-IE8及IE8以下:!!window.ActiveXObject || "ActiveXObject" in window
  return (!!window.ActiveXObject || "ActiveXObject" in window) ? nodeName.currentStyle[keyName] : document.defaultView.getComputedStyle(nodeName, null)[keyName];
}


let target = document.querySelector('#text');
let content = document.all ? target.innerText : target.textContent;

var fontFamily = getStyle(target, 'fontFamily');
var fontSize = getStyle(target, 'fontSize');

// console.log(calculateTextSize(content, fontSize, fontFamily));

事件系统 Event

createEvent()

isPointInPath(x, y)

判断当前焦点是否为指定区域,如果指定的点位于当前路径中,返回 true,否则返回 false。

语法结构

isPointInPath(
  x, // 检测点的 X 坐标 
  y // 检测点的 Y 坐标
)

isPointInPath(
  x, // 检测点的 X 坐标 
  y, // 检测点的 Y 坐标
  fillRule // 用来决定点在路径内还是在路径外的算法,其值为nonzero(非零环绕规则,默认的规则) | evenodd(奇偶环绕原则)。
)

isPointInPath(
  path, // Path2D应用的路径。
  x, // 检测点的 X 坐标 
  y // 检测点的 Y 坐标
)

isPointInPath(
  path, // Path2D应用的路径。
  x, // 检测点的 X 坐标 
  y, // 检测点的 Y 坐标
  fillRule // 用来决定点在路径内还是在路径外的算法,其值为nonzero(非零环绕规则,默认的规则) | evenodd(奇偶环绕原则)。
)

代码示例

  canvas.addEventListener("click", (e)=> {
    console.log(ctx.isPointInPath(e.clientX, e.clientY));
  }, false);

  ctx.isPointInPath(x, y);
  ctx.isPointInPath(x, y, fillRule);

具体思路与方法

像素检测法 角度法 射线法

代码示例

<script type='text/javascript'>
  var canvas = document.createElement('canvas');
  var ctx = canvas.getContext("2d");
  let WIDTH = document.body.clientWidth;
  let HEIGHT = document.body.clientHeight;
  canvas.setAttribute('width', WIDTH);
  canvas.setAttribute('height', HEIGHT);
  document.body.append(canvas);
</script>

<script type='module'>
  import { randomHexColor } from '/public/module/color.js';
  import { SetObjectCoordinate } from '/Visual/Canvas/common/index.js';


  const GRAPHIC_POINTS = {};


  const caseRect = { l: 10, t: 10, w: 150, h: 100, };
  ctx.fillStyle = "#f00"; 
  ctx.rect(caseRect.l, caseRect.t, caseRect.w, caseRect.h);
  ctx.fill();
  GRAPHIC_POINTS['caseRect'] = { ...caseRect, ...SetObjectCoordinate(ctx) };


  const caseCircle = { l: 85, t: 200, radius: 75 };
  ctx.beginPath();
  ctx.arc(caseCircle.l, caseCircle.t, caseCircle.radius, 0, 2 * Math.PI);
  ctx.fillStyle = '#0f0';
  ctx.fill();
  ctx.closePath();
  GRAPHIC_POINTS['caseCircle'] = { ...caseCircle, ...SetObjectCoordinate(ctx) };


  console.log(GRAPHIC_POINTS, 'GRAPHIC_POINTS');


  // 单击
  canvas.onclick = (e) => { 
    Object.keys(GRAPHIC_POINTS).forEach((key) => {
      if (GRAPHIC_POINTS[key].x[e.clientX] && GRAPHIC_POINTS[key].y[e.clientY]) {
        canvasModel(key);
      }
    });
  };

  // 移动
  canvas.onmousemove = (e) => { 
    Object.keys(GRAPHIC_POINTS).forEach((key) => {
      if (GRAPHIC_POINTS[key].x[e.clientX] && GRAPHIC_POINTS[key].y[e.clientY]) {
        canvasModel(key);
      }
    });
  };

  function canvasModel(type) {
    switch(type) {
      case 'caseRect':
        {
          const { l, t, w, h } = GRAPHIC_POINTS[type];
          ctx.clearRect(l, t, w, h);

          ctx.fillStyle = randomHexColor(); 
          ctx.fillRect(caseRect.l, caseRect.t, caseRect.w, caseRect.h);
        }
        break;
      case 'caseCircle':
        {
          const { l, t, radius } = GRAPHIC_POINTS[type];
          ctx.clearRect(l - radius, t - radius, radius * 2, radius * 2);

          ctx.beginPath();
          ctx.arc(l, t, radius, 0, 2 * Math.PI);
          ctx.fillStyle = randomHexColor();
          ctx.fill();
          ctx.closePath();
        }
        break;
      default:
        //
    }
  }
</script>

动画(Animate)

动画循环:持续的更新与重绘

window.setInterval():Window系统下表现为15毫秒;Forefix浏览器表现为10毫秒;Chrome浏览器表现为1毫秒

window.setTimeout():Window系统下表现为15毫秒;Forefix浏览器表现为10毫秒;Chrome浏览器表现为1毫秒

window.requestAnimationFrame(callback)

	window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

	var val = 0;
	function test() {
		val += 1;
		console.log(val);
		window.requestAnimationFrame(test);
	}
	window.requestAnimationFrame(test);

双缓冲技术

作用:防止闪烁

图像操作技术

  • 属性列表
------------------------------------------------------------------------------------------------------------
  属性	              描述
------------------------------------------------------------------------------------------------------------
width	返回 ImageData 对象的宽度。
height	返回 ImageData 对象的高度。
data	返回一个对象,其包含指定的 ImageData 对象的图像数据。
------------------------------------------------------------------------------------------------------------
  • 代码示例
const imgData = ctx.createImageData(100, 100); 

console.info(imgData.width);
console.info(imgData.height);
console.info(imgData.data.length);
  • 方法列表
------------------------------------------------------------------------------------------------------------
  方法	              描述
------------------------------------------------------------------------------------------------------------
createImageData()	创建新的、空白的 ImageData 对象。
getImageData()	返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据。
putImageData()	把图像数据(从指定的 ImageData 对象)放回画布上。
------------------------------------------------------------------------------------------------------------

drawImage()方法

向画布上绘制图像、画布或视频,使用drawImage之前必须提前加载图像,否则不成功。

imageSmoothingEnabled属性

过度缩放图像可能会导致图像模糊或像素化,通过使用绘图环境的imageSmoothingEnabled属性来控制是否在缩放图像时使用平滑算法。默认值为true,即启用平滑缩放。

  • 在画布上定位图像
ctx.drawImage(
  img, // <必选> 规定要使用的图像、画布或视频数据,且已经加载成功。
  x, // <必选> 距离文档左侧的X坐标 
  y // <必选> 距离文档左侧的Y坐标 
);
  • 在画布上定位图像,并规定图像的宽度和高度
ctx.drawImage(
  img, // <必选> 规定要使用的图像、画布或视频数据,且已经加载成功。
  x, // <必选> 距离文档左侧的X坐标
  y, // <必选> 距离文档左侧的Y坐标
  width, // [可选] 图像在画布中显示的宽度(伸展或缩小图像),默认为图像的原始宽度。
  height // [可选] 图像在画布中显示的高度(伸展或缩小图像),默认为图像的原始高度。
);
  • 剪切图像,并在画布上定位被剪切的部分,可以运用在图像缩放与移动的场景
ctx.drawImage(
  img, // <必选> 规定要使用的图像、画布或视频数据,且已经加载成功。
  sX, // <必选> 在图像中的开始裁剪 X 位置。
  sY, // <必选> 在图像中的开始裁剪 Y 位置。
  swidth, // <必选> 裁剪图像的宽度(swidth < width,则图像看起来被拉伸,反之则被压缩),注意:宽度是基于img数据源的实际宽度
  sheight, // <必选> 裁剪图像的高度(sheight < height,则图像看起来被拉伸,反之则被压缩),注意:高度是基于img数据源的实际高度
  x, // <必选> 距离文档左侧的X坐标
  y, // <必选> 距离文档左侧的Y坐标
  width, // [可选] 图像在画布中显示的宽度(伸展或缩小图像),默认为图像的原始宽度。
  height // [可选] 图像在画布中显示的高度(伸展或缩小图像),默认为图像的原始高度。
);

createPattern()方法

在指定的方向内重复指定的元素(图片、视频,或者其他 <canvas>)。

  // 填充背景
  ctx.fillStyle = ctx.createPattern(
    image,  // 规定要使用的模式的图片、画布或视频元素。
    type    // repeat	默认。该模式在水平和垂直方向重复。repeat-x	该模式只在水平方向重复。repeat-y	该模式只在垂直方向重复。no-repeat	该模式只显示一次(不重复)。
  );

createImageData()

创建新的、空白的 ImageData 对象。

  • 以指定的尺寸(以像素计)创建新的 ImageData 对象
const imgData = ctx.createImageData(
  width, // ImageData 对象的宽度,以像素计。
  height // ImageData 对象的高度,以像素计。
);
  • 创建与指定的另一个 ImageData 对象尺寸相同的新 ImageData 对象(不会复制图像数据)
const imgData = ctx.createImageData(
  imageData // 另一个 ImageData 对象。
);

getImageData()

返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据。

  ctx.getImageData(
    x,  // 开始复制的左上角位置的 x 坐标(以像素计)。
    y,  // 开始复制的左上角位置的 y 坐标(以像素计)。
    w,  // 要复制的矩形区域的宽度。
    h   // 要复制的矩形区域的高度。
  );
  • 案例
ctx.fillStyle="red";
ctx.fillRect(10, 10, 50, 50);

var imgData = ctx.getImageData(10, 10, 50, 50);
ctx.putImageData(imgData, 10, 70);

putImageData()

把图像数据(从指定的 ImageData 对象)放回画布上,重写图像数据,ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值,所以数组的大小是 ImageData 对象的四倍:widthheight4。

  ctx.putImageData(
    imgData,     // 规定要放回画布的 ImageData 对象。
    x,           // ImageData 对象左上角的 x 坐标,以像素计。ImageData 对象距离画布顶部的 X 位置。
    y,           // ImageData 对象左上角的 y 坐标,以像素计。ImageData 对象距离画布左侧的 Y 位置。
    dirtyX,      // 可选。水平值(x),以像素计,在画布上放置图像的位置(图像基于自己,隐藏图像部分尺寸)。
    dirtyY,      // 可选。垂直值(y),以像素计,在画布上放置图像的位置(图像基于自己,隐藏图像部分尺寸)。
    dirtyWidth,  // 可选。在画布上绘制图像所使用的宽度。
    dirtyHeight, // 可选。在画布上绘制图像所使用的高度。
  );
  • 代码示例
  const imgData = ctx.createImageData(100, 100);  

  // RGBA 值 imgData.data.length == 40000
  for (var i = 0; i < imgData.data.length; i += 4) {
    imgData.data[i+0] = 255;
    imgData.data[i+1] = 0;
    imgData.data[i+2] = 0;
    imgData.data[i+3] = 255;
  }

  ctx.putImageData(imgData, 0, 0);  

toDataURL([type, encoderOptions])

导出画布canvas对象的图像数据源链接,返回一个数据地址(data URL),可以用于设定img元素的src属性值。

  • ctx.toDataURL([type, encoderOptions]):返回一个包含图片展示的 data URI 。
  • type:[可选],图片格式,默认为 image/png。
  • encoderOptions:[可选],指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
// 创建画布对象
const canvas = document.createElement( 'canvas' );

// 导出画布数据源链接
image.src = canvas.toDataURL(
  type,           // 图像类型,[可选] 例如:'image/jpeg' | 'image/png'
  encoderOptions  // 图像质量,[可选] double,例如:0 ~ 1
);

Audio音频

Video视频

File文件

Canvas

Canvas API 提供了一个通过JavaScript 和 HTML的<canvas>元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

代码示例

var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");  // 返回canvas元素的绘图环境对象
let width = document.body.clientWidth;
let height = document.body.clientHeight;
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
document.body.append(canvas);	

状态管理

  • 保存save():保存当前canvas绘图环境的所有属性
  • 恢复restore():恢复当前canvas绘图环境的所有属性
ctx.fillStyle = '#999';
ctx.fillRect(0, 0, 50, 50);
//保存当前canvas绘图环境的属性
ctx.save();

ctx.fillStyle = '#f00';
ctx.fillRect(60, 0, 50, 50);  // 背景色为:#f00

// //恢复经ctx.save()当前canvas绘图环境的属性
ctx.restore();

ctx.fillRect( 150, 0, 50, 50);  // 背景色为:#999

离屏canvas

离屏,即不将canvas画布作为实际展示元素,而是仅仅作为处理图像的工具,然后将canvas数据导出并利用到其他元素在文档中呈现出来。

<body class='test'>
  <canvas class='canvasCtn' id='canvasCtn' width='800' height='400' style='display: none;'></canvas>
  <img src='' id='imgcanvas'/>
</body>
<script type="text/javascript">
  var ctxCtn = document.getElementById("canvasCtn");
  var imgcanvas = document.getElementById('imgcanvas');
  imgcanvas.src = ctxCtn.toDataURL();		 
</script>	

将鼠标坐标转换为canvas坐标

/**
 * 将鼠标坐标转换为canvas坐标
 * @param {*} canvas:canvas对象
 * @param {*} x:document文档 X 坐标
 * @param {*} y:document文档 Y 坐标
 */
export function windowToCanvas(canvas, x, y) {
  const clientRect = canvas.getBoundingClientRect();

  return { 
    x: x - clientRect.left * (canvas.width / clientRect.width),
    y: y - clientRect.top * (canvas.height / clientRect.height)
  }
}

实现橡皮檫功能

import { windowToCanvas } from '/Visual/Canvas/common/canvas.js';


ctx.fillStyle = "#f00";      // 背景色
ctx.rect(0, 0, canvas.width, canvas.height);  // 长宽
ctx.fill();                  // 填充

let action = { start: false, startX: null, startY: null, };
const rubberSize = 25;

document.querySelector('canvas').addEventListener('mousedown', function() {
  action.start = true;
}, false);

document.querySelector('canvas').addEventListener('mousemove', function(e) {
  if (!action.start) return;

  const coordinate = windowToCanvas(canvas, e.clientX, e.clientY);

  ctx.save();
  ctx.beginPath();
  // 绘制圆形
  ctx.arc(coordinate.x, coordinate.y, rubberSize/2, 0, Math.PI * 2);
  // 声明裁剪区域为圆形
  ctx.clip();
  // 清除所有区域,但是由于声明了裁剪机制,则仅仅清除了与圆形相交的公共区域。
  ctx.clearRect(coordinate.x - rubberSize/2, coordinate.y - rubberSize/2, rubberSize, rubberSize);
  ctx.restore();
}, false);

document.querySelector('canvas').addEventListener('mouseup', function() {
  action.start = false;
}, false);

captureStream()

该 HTMLCanvasElement.captureStream() 方法返回的 CanvasCaptureMediaStream 是一个实时视频捕获的画布。

插件体系

canvg

https://github.com/ctxg/ctxg svg转ctxa

html2canvas

官网:http://html2canvas.hertzen.com/ GitHub:https://github.com/niklasvh/html2canvas/ 文档:http://html2canvas.hertzen.com/documentation 配置:http://html2canvas.hertzen.com/configuration 它不会制作实际屏幕截图,而是根据页面上的可用信息构建屏幕截图。

  yarn add html2canvas

  html2canvas(document.querySelector("#capture")).then(canvas => {
    document.body.appendChild(canvas)
  });
  • 缺陷

不支持iframe 不支持跨域图片 不能在浏览器插件中使用 部分浏览器上不支持SVG图片 不支持Flash 不支持古代浏览器和IE

AmmoJS

box2dweb

ConnonJS

Fabric.js

JigLibJS

lufylegend

项目实战: 水印模块

项目实战:

项目实战:

项目实战:

Last Updated:
Contributors: 709992523