Canvas学习笔记,记录使用过程中遇到的一些问题

2022-11-07

1.反向绘制

fill(),方法用于填充已有的闭合路径,假设有一个如下图的路径,默认情况下圆和方形都会被填充,最后的效果就是一个黑色的方形;

Canvas学习笔记,记录使用过程中遇到的一些问题
demo

通过fillRule参数,可以指定填充的算法,决定点是在路径内还是在路径外。 允许的值:

  • "nonzero": 非零环绕规则,默认的规则。
  • "evenodd": 奇偶环绕规则。

将填充规则设置为evenodd,绘制的结果会变为下面这样,通过这种方式可以实现反向裁剪。

Canvas学习笔记,记录使用过程中遇到的一些问题
图片说明文字

通过destination-out的擦除效果,同意可以实现上面的效果

/* 保存状态 */
context.save();
context.fillStyle = "rgba(246,246,246,0.6)"; //背景色
context.fillRect(0, 0, canvas_w, canvas_h); //画圆
context.globalCompositeOperation = "destination-out"

context.fillStyle = "rgba(255,255,255,1)"; //背景色
context.fillRect(x, y, w, h); //画圆
context.restore(); //恢复状态

2.原地旋转

默认的旋转是通过改变坐标系的角度实现的(矩阵),所以旋转后中心点不会在原来的地方;

/* 角度换算弧度 */
let rotateRadius=45*Math.PI/180

/*计算方形的中心点 */
let rectCenterPoint = {
    x: x + w / 2,
    y: y + h / 2
};

/* 旋转的同时,让中心点回到原来的位置 */
context.translate(rectCenterPoint.x, rectCenterPoint.y);
context.rotate(rotateRadius); //旋转
context.translate(-rectCenterPoint.x, -rectCenterPoint.y);

3.原地缩放

默认的旋转是通过改变坐标系的刻度实现的(矩阵),所以缩放后中心点不会在原来的地方;

/* 倍数 */
let scaleRadio=1.5

/*计算方形的中心点 */
let rectCenterPoint = {
    x: x + w / 2,
    y: y + h / 2
};

/* 中心点复位 */
context.translate(
    rectCenterPoint.x * (1 - scaleRadio),
    rectCenterPoint.y * (1 - this.scaleRadio));
context.scale(scaleRadio, scaleRadio);

4.clearRect

如果没有依照 绘制路径 的步骤(begin、close),使用 clearRect() 会导致意想之外的结果(线条乱窜),在调用 clearRect()之后绘制新内容前调用beginPath() 。

5.drawImage模糊

在 iPhone3G 时代,屏幕宽度是 320px,其宽度上的物理像素也是 320px;而到了 4s 时代,屏幕宽度依然是 320px,但是宽度上的物理像素却变成了 640px,是宽度的两倍

屏幕宽度没变,物理像素却增加了,所以为了屏幕显示的内容不改变,原先需要一个像素绘制的点,现在会用两个像素来绘制,为了表示这种屏幕的特性,浏览器全局对象下就有了这样一个属性——devicePixelRatio设备像素比,它的计算方式是 物理像素 / 屏幕宽度的像素;

  • 首先设置canvas的宽度和高度是原来的2倍
  • 使用ctx.scale(2,2)设置绘制的东西也放大2倍
  • 在canvas的父元素上使用缩放,使用css3的 transform:scale(0.5,0.5)即可,意思为缩放到原来的2倍大小,和canvas放大两倍刚好抵消掉。
提示
canvas 绘图时,会从两个物理像素的中间位置开始绘制并向两边扩散 0.5 个物理像素。当设备像素比为 1 时,一个 1px 的线条实际上占据了两个物理像素(每个像素实际上只占一半),由于不存在 0.5 个像素,所以这两个像素本来不应该被绘制的部分也被绘制了,于是 1 物理像素的线条变成了 2 物理像素,视觉上就造成了模糊

6.分层渲染

将多个canvas叠在一起,通过设置每个canvas的 z-index 达到多个画布还是在同一层的错觉;

7.局部渲染

静态画面,本身就可以清空指定区域,然后指定那个区域重新绘制新图行。

8.离屏渲染

OffscreenCanvas提供了一个可以脱离屏幕渲染的canvas对象。它在窗口环境和web worker环境均有效。(一个不在屏幕上实际显示的画布)

 // 离屏canvas 
 const offscreen = new OffscreenCanvas(200, 200);

通过transferToImageBitmap函数可以从OffscreenCanvas对象的绘制内容创建一个ImageBitmap对象。该对象可以用于到其他canvas的绘制。

9.矩阵变换

向量是有长度及方向的量,一般由多个标量(scalar,即单纯的数字)组合而成。比如由两个标量组合而成的二维向量,可以表示二维空间(平面)中有长度及方向的量。由三个标量组成的三维向量,可以表示三维空间中具有长度及方向的量;

 

Canvas学习笔记,记录使用过程中遇到的一些问题
矩阵
Canvas学习笔记,记录使用过程中遇到的一些问题
平移
Canvas学习笔记,记录使用过程中遇到的一些问题
旋转
Canvas学习笔记,记录使用过程中遇到的一些问题
缩放

10.touchmove

触发touchstart事件之后,假如move的距离特别小,就不会触发toucemove事件。

所以图形拖动的时候,以touchstart事件的坐标作为拖动的参照点时会产生偏差,结果就是拖动开始的时候,图形会瞬移一段距离。

可以将参照点的坐标调整为第一次touchmove事件触发时的坐标。

11.move和requestAnimationFrame

在大多数情况下,mousemove、touchmove事件的触发频率会比 window.requestAnimationFrame 高得多。

所以在绘制手写线条时,需要监听mousemove事件,使用requestAnimationFrame 会出现线条断层的情况。

2022-11-09

1. canvas导出模糊

移动端受限于屏幕,实际能看到的画布只有手机的大小,按照手机的分辨率导出canvas作为图片,分辨率确实太低了。

可以在导出之前将canvas的长宽放大指定倍数,同时将图形放大,之后再通过toDataURL导出,分辨率相对来说就高了。

小贴士
离屏canvas没有toDataURL方法

2022-11-15

1.放大canvas

canvas的分辨率是实际分辨率的两倍,此时保持图形正常绘制,原点,大小都要跟着放大。

屏幕坐标换算到画布上需要乘以放大的倍数。

2.图形选中

2.1 范围判断

以正方形为例,正常情况下可通过如下算法去判断图形是否被点击(点击point,图形rect)。

if (
    point.x > rect.x  &&
    point.x < rect.x + rect.w &&
    point.y > rect.y  &&
    point.y < rect.y + rect.h
) {
    return true;
} else {
    return false;
}

方大之后这个判断方法仍然有效,但是旋转之后,图形有一部分就点不到了。

2.2 isPointInPath

不管是方形、图片、还是其他的,描绘一个相同的路径(位移、旋转、缩放),都可以进行如下判断

context.beginPath();
context.rect(x, y, w, h);
context.closePath();
this.clicked = context.isPointInPath(point.x, point.y);
点击判断
绘制图形时,可以同步绘制一个形状一致的路径,然后通过isPointInPath判断是否被点击

2.3 离屏绘制

每个图形在离屏画布上绘制一个大小状态一样的图片,并使用唯一的颜色值填充,这个颜色值就代表这个图形的索引。

问题记录

1.学习总结

  • canvas初始化之后,就一直在运行window.requestAnimationFrame,之后只有更改图形的属性,就会自动刷新。
  • 如果在浏览器中创建多个Image对象并加载同一张图片,浏览器不会重复加载该图片。浏览器会将该图片缓存到内存中,并在需要时从缓存中获取它。