记录canvas使用及常见问题

前言

在具体项目开发时,咱们常常会使用canvas绘制图表、丰富的图形、动画交互等应用场景。
本篇文章总结概括一下本身在canvas开发的认知和可能遇到的常见问题。
至于基础知识和常见API使用,你们自行学习,这里不予复述。javascript

重要概念

HTML5 <canvas> 元素用于图形的绘制,生成图形容器,必须经过JS脚原本完成。
canvas绘图时相对于一根画笔,每一步骤的绘制是基于当前状态的(位置、颜色等)
canvas api文档
canvas.width/height :用来控制Canvas画布绘制区域的宽高。需根据实际屏幕环境设置,不然绘制的图形会失真。
canvas.getContext():返回canvas的绘制上下文, 实际绘制依赖于该对象。
canvas.toBlob():能够对Canvas图像生成对应的Blob对象。
canvas.toDataURL():返回base64 data图片数据。css

<canvas> 标签

HTML5 中的<canvas> 标签只会生成图形容器,图形绘制必须经过JS脚原本完成。html

Canvas宽高与CSS宽高

<canvas width="600" height="300" style="width: 300px; height: 150px"></canvas>
复制代码
  • style中的width/height表明canvas元素在界面上所占据的宽/高, 即样式上的CSS宽高。
  • 属性中的width/height **则表明canvas实际像素的宽高。**用来控制Canvas画布绘制区域的宽高。不设置宽高时,会有默认的宽高(300*150),通常建议设置好图形宽高限度绘制区域。


当使用Canvas API绘制图形时使用的坐标、尺寸大小是基于Canvas宽高属性的,而与CSS样式宽高无关。
而CSS宽高则决定canvas图形的视觉显示大小,canvas画布的宽高会等比例缩放成CSS宽高显示。java

实际使用时,尽可能避免这种因尺寸不一致比例缩放渲染,致使的图形模糊、锯齿化等问题。canvas

绘制上下文

canvas.getContext():返回canvas的绘制上下文, 实际绘制依赖于该对象。
本文主要讨论二维平面绘制:const context =``canvas.getContext('2d');api

实际画布绘图时每一路径的绘制过程是基于绘制上下文的当前状态的。
绘制过程至关于只使用一根画笔做画,每一次路径的绘制点、颜色等样式是基于绘制上下文的当前状态(位置、颜色、粗细等)。
对于非连续的路径绘制,绘制过程要用context.beginPath()声明,这样可使绘制样式不会被覆盖影响。跨域

好比:bash

context.strokeStyle = 'blue';
context.moveTo(10, 10);
context.lineTo(100, 10);
context.stroke();

context.strokeStyle = 'red';
context.moveTo(100, 10);
context.lineTo(10, 50);
context.stroke();
复制代码

样式被覆盖,绘制效果以下:
iphone

image.png

而用 context.beginPath()声明路径,代码:

context.beginPath();
context.strokeStyle = 'blue';
context.moveTo(10, 10);
context.lineTo(100, 10);
context.stroke();

context.beginPath();
context.strokeStyle = 'red';
context.moveTo(100, 10);
context.lineTo(10, 50);
context.stroke();
复制代码

则两条线的样式不受影响,绘制效果以下:
函数

image.png

context.canvas

这个在不少时候须要在绘制时获取当前画布信息时很是有用。CanvasRenderingContext2D.canvas是一个只读属性,能够返回当前上下文源自哪一个<canvas>元素,并获取画布的width/height等属性信息。

context.clearRect()

在实际开发时常常会使用到的api,清除制定的绘制区域,不然在同一区域重复绘制会发生图形叠加。

Canvas与SVG的区别

Canvas和SVG是当前HTML5中主要使用的图形绘制技术,前者提供画布标签和绘制API,后者是一整套独立的矢量图形语言,使用 XML 格式定义图像。

  1. Canvas经过JS绘制图形,只有当个HTML元素;而SVG使用 XML 格式定义图形,生成的图形包含多种图形元素(Path、Line、Rect)。
  2. Canvas绘制基于像素级控制;SVG则基于内部图形元素操做控制;
  3. Canvas是像素级渲染,依赖分辨率;SVG则是矢量图形,缩放时图形质量不会失真;
  4. 事件交互:Canvas中,事件只能注册到<canvas>标签上,但经过事件委托,能够细化到像素点(x,y)的交互;SVG则能够为某个元素附加 单独的JavaScript 事件处理器,但也只能控制细化在图形元素上。
  5. Canvas适合小面积、大数据应用场景;SVG适合大面积、小数量应用场景(图像元素少)。

Canvas适用场景:适合像素处理,动态渲染和大数据量绘制; 适合图像密集型的游戏;
SVG适用场景:适合静态图片展现,高保真文档查看和打印的应用场景。

常见问题

移动端绘制canvas模糊问题

现象

以下图:

image.png

上图中,在未作兼容移动端处理时,绘制的canvas刻度组件在iphone 6s机型上,canvas图形和文字模糊失真。兼容处理后图形质量得以保证。
以上刻度组件参考个人另外一篇文章,地址:「 采用Canvas绘制一个可配置的刻度(尺)组件

缘由

关于移动端高清屏DPR、图片模糊、移动端适配等问题,不清楚的童鞋能够参考「关于移动端适配,你必需要知道的」这篇文章,讲的比较详细。这里再也不赘述,本文章只处理移动端Canvas模糊问题。
在移动端高清屏幕上,常常会遇到Canvas图形模糊的问题。本质上跟移动端图片模糊问题是同样的。canvas绘制成的图像跟也是位图,在dpr > 1的屏幕上,位图的一个像素可能由多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,因此在dpr > 1的屏幕上就会模糊。
在PC端绘制canvas图形,咱们都直接把1个canvas像素直接等于1px的css像素处理,这没有问题,应该目前PC端屏幕dpr都是1。而在dpr > 1的移动端屏幕上就不能直接这样处理。

解决

解决方案固然仍是从dpr入手。

  1. 经过window.devicePixelRatio获取当前设备屏幕的dpr;
  2. 首先获取或设置Canvas容器的宽高;
  3. 根据dpr,设置canvas元素的宽高属性;在dpr = 2时至关于扩大画布2倍;
  4. 经过context.scale(dpr, dpr)缩放Canvas画布的坐标系。在dpr = 2时至关于把canvas坐标系也扩大了两倍,这样绘制比例放大了2倍,以后canvas的实际绘制像素就能够按原先的像素值处理。

在渲染到屏幕时,扩大的画布图形又等比例缩放渲染到canvas容器中。从而保证canvas图形的质量。

// 获取dpr
const dpr = window.devicePixelRatio; 
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 获取Canvas容器的宽高
const { width: cssWidth, height: cssHeight } = canvas.getBoundingClientRect();
// 根据dpr,设置Canvas的宽高,使1个canvas像素和1个物理像素相等
canvas.width = dpr * cssWidth;
canvas.height = dpr * cssHeight;
// 根据dpr,设置canvas元素的宽高属性
ctx.scale(dpr,dpr);
复制代码

canvas drawImage()参数问题,移动端图片模糊问题

canvas的drawImage() 函数有个特别容易混淆搞错的地方。它的5参数和9参数用法的参数位置是不一样的。实际开发中没注意到这一点,会让本身特别困惑问题出在哪!汗!

drawImage()方法有一个很是怪异的地方,你们必定要注意,那就是5参数和9参数用法的参数位置是不同的,这个和通常的API有所不一样。通常API可选参数是放在后面。可是,这里的drawImage()使用9个参数时候,可选参数sx,sy,sWidth和sHeight是在前面的。若是不注意这一点,有些表现会让你没法理解。

且drawImage()函数插入的图形在移动端dpr >1屏幕一样会有图片模糊的问题。
在移动端经过drawImage()载入另外一个已绘制的Canvas元素时,也要注意对另外一个canvas元素作兼容处理,还须要注意二者坐标系的不一样。

// 设置canvas_bg宽高
canvas_bg.width = (config.unit * (scale_len - 1) + config.width) * dpr;
canvas_bg.height = config.height * dpr;
ctx_bg.scale(dpr, dpr);

...

// 初始化开始位置
point_x = (config.def - config.start) / config.capacity * config.unit;
//在主画布ctx上,经过drawImage()插入另外一个canvas_bg画布;
ctx.drawImage(canvas_bg, point_x * dpr, 0, config.width * dpr, config.height * dpr, 0, 0, config.width, config.height);

复制代码

上面的代码中, canvas_bg画布一样须要处理上面提到的canvas模糊问题;在主画布ctx上,经过drawImage()插入另外一个canvas_bg画布图形时,须要注意此时二者坐标系比例的不一样,此时canvas_bg的坐标系是根据dpr缩放后的。

当canvas绘制尺寸或drawImage插入图像、getImageDate获取图形资源等尺寸大于某个阈值时,可能会出现绘制空白问题。

在实际开发中遇到,canvas绘制尺寸或drawImage插入图像、getImageDate获取图形资源等尺寸大于某个阈值时,渲染出来的图片整个都是空白。这个具体的阈值不肯定,跟运行环境有关。但这应该也是drawImage绘制的一个不知什么时候爆发的隐患。
好比下图,绘制的刻度尺画布尺寸过大,截取后渲染到主画布上,整个刻度空白,但不影响交互。

image.png

canvas getImageData 跨域问题

只要可以在网页中正常显示出来的跨域图片,就可使用canvas的drawImage() API绘制出来。可是若是想经过getImageData()方法获取图片的完整的像素信息,转换成本地输出时,则多半会出现跨域问题。

解决:

1. 是页面与服务端开启容许跨域;

2. 给图片设置容许跨域,`img.setAttribute('crossOrigin', 'anonymous');`
复制代码
相关文章
相关标签/搜索