首页» 前端 » HTML5 » canvas中常见问题的解决方法及分析,踩坑填坑经历

canvas中常见问题的解决方法及分析,踩坑填坑经历

日期:2019年12月24日 阅读次数:8242 分类:HTML5前端

一、canvas中画线条,线条效果比预期宽1像素且模糊问题分析及解决。

【出现条件】:这种情况一般是垂直或者水平的线,且坐标为整数,宽度不是偶数。
【解决方法】:坐标偏移0.5像素。

1.1、canvas画线的原理:以指定坐标为中心向两侧画线(两侧各画宽的一半)。

下面我们看个例子

var dom = document.querySelector("#canvas1");
var ctx = dom.getContext('2d');

ctx.strokeStyle = '#000';

// 正常画线(坐标为整数,线宽为1px),1像素画出的效果像2像素。
ctx.lineWidth = 1;
ctx.moveTo(30, 50);
ctx.lineTo(30, 200);
ctx.stroke();

// 处理之后(坐标偏移0.5像素),线条宽度正常。
ctx.lineWidth = 1;
ctx.moveTo(50.5, 50);
ctx.lineTo(50.5, 200);
ctx.stroke();

效果如下图(在PS中放大后效果)
canvas画线的原理

【实例解析】
1. 指定坐标为30px时,实际是以30px为中心向两边各画一半(0.5px),会画在30px前后的两个像素格子中。又因为像素是最小单位,所以30px前后的两个像素都被画了1px的线,但是颜色要比实际的谈一些。

  1. 而指定坐标为50.5px时,线是以50.5为中心向两边各画一半(0.5px),这样子刚好只占用了一个像素的宽,就实现了1px的宽了。

1.2、当线的宽度为非整数时,同样会出现“宽度大1px”的情况

canvas画非整数宽的线

如上图所示,从左到右宽分别是1.3px、0.8px、0.5px、0.1px。上面4条以整数为坐标的线宽度其实是2px,下面4条X坐标都偏移了0.5px。效果更接近预期的宽度。

canvas画线问题总结

以上所说的偏移0.5px,其实并不准确。因为上面例子中,坐标都是整数。
更准确的说法应该是: 当线宽为偶数时,坐标应指定为整数。否则坐标应指定为整数+0.5px。

1.3、下面奉上我总结的最终解决方案

这里以竖线为例,横线同理


// 封装一个画线的方法 function drawLine (ctx, x, y1, y2, width) { // 当线宽为偶数时,坐标应指定为整数。否则坐标应指定为整数+0.5px。 let newx = width % 2 === 0 ? Math.floor(x) : Math.floor(x) + 0.5; ctx.lineWidth = width; ctx.moveTo(newx, y1); ctx.lineTo(newx, y2); } ctx.beginPath(); ctx.strokeStyle = '#000'; drawLine (ctx, 350, 250, 380, 1); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = '#000'; drawLine (ctx, 360, 250, 380, 2); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = '#000'; drawLine (ctx, 370.4, 250, 380, 1.3); ctx.stroke();

具体效果请看canvas画线条源码中,右下角的三根线。

想了解更多canvas画线问题的解析,请稳步到 canvas中画线条,线条效果比预期宽1像素且模糊问题分析及解决方案

二、canvas中没有画圆角矩形的API,我们自己写一个方法实现圆角矩形

2.1、canvas已有的api,并且我们可能会用到用来画圆角矩形的有如下几个:

2.1.1、创建矩形:

语法:context.rect(x,y,width,height)

参数 描述
x 矩形左上角的 x 坐标。
y 矩形左上角的 y 坐标。
width 矩形的宽度,以像素计。
height 矩形的高度,以像素计。

2.1.2、创建弧/曲线/圆:

语法:context.arc(x,y,r,sAngle,eAngle,counterclockwise)

参数 描述
x 圆的中心的 x 坐标。
y 圆的中心的 y 坐标。
r 圆的半径。
sAngle 起始角,以弧度计(弧的圆形的三点钟位置是 0 度)。
eAngle 结束角,以弧度计。
counterclockwise 可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针。

2.1.3、把路径移动到画布中的指定点,不创建线条:

语法:context.moveTo(x,y)

参数 描述
x 路径的目标位置的 x 坐标。
y 路径的目标位置的 y 坐标。

2.1.4、添加一个新点,然后在画布中创建从该点到最后指定点的线条

语法:context.lineTo(x,y)

参数 描述
x 路径的目标位置的 x 坐标。
y 路径的目标位置的 y 坐标。

2.1.5、创建介于两个切线之间的弧/曲线:

arcTo() 方法在画布上创建介于两个切线之间的弧/曲线。

语法:context.arcTo(x1,y1,x2,y2,r)

参数 描述
x1 两切线交点的横坐标。
y1 两切线交点的纵坐标。
x2 第二条切线上一点的横坐标。
y2 第二条切线上一点的纵坐标。
r 弧的半径。

2.2、这些API要么是画矩形,要么是画弧,但是没有真正画圆角矩形的。但是我们可以结合它们画出圆角矩形。

【分析方案】

方案1:圆弧(arc)+ 线(moveTo+lineTo)画矩形。
分析:可以实现画圆角矩形,不过需要反复多次调用以上API(要画8条线),性能略差。

方案2:使用两个切线之间的弧(arcTo)结合moveTo画矩形。
分析:可以实现画圆角矩形,并且调用API较少(只画4条线)。【推荐】

更详细的分析说明请移步到 在Canvas中绘制圆角矩形及逐步分析过程

2.3、最终实现画圆角矩形代码如下:

// 入参说明:上下文、左上角X坐标,左上角Y坐标,宽,高,圆角的半径
function arcRect (ctx, x, y, w, h, r) { 
  // 右上角弧线
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + r, r);

  // 右下角弧线
  ctx.moveTo(x + w, y + r);
  ctx.arcTo(x + w, y + h, x + w - r, y + h, r);

  // 左下角弧线
  ctx.moveTo(x + w - r, y + h);
  ctx.arcTo(x, y + h, x, y + h - r, r);

  // 左上角弧线
  ctx.moveTo(x, y + h - r);
  ctx.arcTo(x, y, x + r, y, r);
}

该代码实现的效果如下图

矩形

圆角矩形画好后,具体是要路径(外框),还是要填充。只需要分别使用stroke()fill()方法实现即可。

三、canvas中有阴影API,但是没有内阴影API。如果在Canvas中实现内阴影效果呢?

3.1、首先我们先看一下canvas中和阴影有关的API。

1.1、阴影相关属性如下:

属性 描述
shadowColor 设置或返回用于阴影的颜色。
shadowBlur 设置或返回用于阴影的模糊级别。
shadowOffsetX 设置或返回阴影与形状的水平距离。
shadowOffsetY 设置或返回阴影与形状的垂直距离

1.2、clip()方法

clip()方法从原始画布中剪切任意形状和尺寸。

提示:一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。您也可以在使用 clip() 方法前通过使用 save() 方法对当前画布区域进行保存,并在以后的任意时间对其进行恢复(通过 restore() 方法)。

3.2、实现内阴影的原理:

给闭合线(如:矩形、圆等)设置阴影,然后把线以及线外部的阴影裁切掉,只留线内部的阴影。从而达到内阴影效果。

3.3、影响内阴影效果的因素:

1、shadowBlur值越大,范围变大,但阴影也更模糊。
2、线越宽,阴影越清晰。
3、所有这两个属性相结合一起控制【内阴影】的大小。

用线框内部的阴影来实现内阴影

3.4、矩形内阴影效果的实现:

ctx.strokeStyle = '#f00';
ctx.lineWidth = 7;

// 单独内阴影
rectInnerShadow(ctx, 80, 40, 200, 120);

// 矩形框和内阴影一起时,要先画内阴影
rectInnerShadow(ctx, 20, 230, 150, 120);
ctx.strokeRect(20, 230, 150, 120);

// 否则会有重叠(因为线是向两侧画的)
ctx.strokeRect(220, 230, 150, 120);
rectInnerShadow(ctx, 220, 230, 150, 120);

// 【矩形内阴影(边框+阴影,再把边框和外阴影裁剪掉)】
// 参数说明:ctx上下文内容,x,y,w,h同rect的入参,shadowColor阴影颜色,shadowBlur和lineWidth一同控制阴影大小。
function rectInnerShadow (ctx, x, y, w, h, shadowColor, shadowBlur, lineWidth) {
  var shadowColor = shadowColor || '#00f'; // 阴影颜色
  var lineWidth = lineWidth || 20; // 边框越大,阴影越清晰
  var shadowBlur = shadowBlur || 30; // 模糊级别,越大越模糊,阴影范围也越大。

  ctx.save();
  ctx.beginPath();

  // 裁剪区(只保留内部阴影部分)
  ctx.rect(x, y, w, h);
  ctx.clip();

  // 边框+阴影
  ctx.beginPath();
  ctx.lineWidth = lineWidth;
  ctx.shadowColor = shadowColor;
  ctx.shadowBlur = shadowBlur;
  // 因线是由坐标位置向两则画的,所以要移动起点坐标位置,和加大矩形。
  ctx.strokeRect(x - lineWidth/2, y - lineWidth/2 , w + lineWidth, h + lineWidth);

  // 取消阴影
  ctx.shadowBlur = 0;

  ctx.restore();
}

效果如下图(点击查看源代码

用线框内部的阴影来实现内阴影

更多内阴影相关内容,请稳步到 在Canvas中实现内阴影效果

文章标签: