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

【问题】 canvas中画线条,线条效果比预期宽1像素且模糊。 【出现条件】 这种情况一般是垂直或者水平的线,且坐标为整数,宽度不是偶数。 【解决方法】 坐标偏移0.5像素。

实际情况中可能并没有这么简单,下面我们通过实例分析更多情况。

查看源码对比下面的分析会更好理解哦。 canvas画线条源码

效果如下图(在PS中放大后效果)

【事例解析】

  1. 图中上面第1、2条都是【1像素】线,但是第一条看着像2px。而第二条(X坐标偏移了0.5px)才真正实现了1像素宽的效果。

  2. 图中第3-6条上下并列的是【非整数宽】的线,从左到右宽分别是1.3px、0.8px、0.5px、0.1px。下面4条X坐标都偏移了0.5px。效果更接近预期的宽度。上面以整数为X坐标反而像是颜色淡点的2px宽的线。0.1px的更是看不到了。

  3. 从图中几根斜线发现,canvas画斜线毛边比较明显。但是越靠近45度角毛边会越少。(这个问题暂时没有找到好的解决方案,即使偏移0.5px也不行。只能尽量避免画斜线。)

  4. 图中下面第1、2条,分别是整数坐标和偏移0.5px坐标的5像素宽线。第一条实际效果看着像是6px。

  5. 图中下面第3、4条,分别是整数坐标和偏移0.5px坐标的宽为4像素的线。而这次反而是偏移0.5px的线宽度大了1px。这是为什么呢?下面"canvas画线的原理"会解释其中原因。

  6. 从图中下面的两个矩形(第一个是X,Y坐标均为整数,第二个是X,Y坐标均偏移了0.5)可以看出,垂直或者水平的线都会有这种问题。

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

下面我们看个例子

javascript 复制代码
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中放大后效果)

【实例解析】

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

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

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

javascript 复制代码
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;

// 默认从整数坐标画起时
ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 1.3;
ctx.moveTo(65, 50);
ctx.lineTo(65, 120);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.8;
ctx.moveTo(70, 50);
ctx.lineTo(70, 120);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.5;
ctx.moveTo(75, 50);
ctx.lineTo(75, 120);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.1;
ctx.moveTo(80, 50);
ctx.lineTo(80, 120);
ctx.stroke();

// 坐标偏移0.5px后
ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 1.3;
ctx.moveTo(65.5, 130);
ctx.lineTo(65.5, 200);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.8;
ctx.moveTo(70.5, 130);
ctx.lineTo(70.5, 200);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.5;
ctx.moveTo(75.5, 130);
ctx.lineTo(75.5, 200);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.1;
ctx.moveTo(80.5, 130);
ctx.lineTo(80.5, 200);
ctx.stroke();

效果如下图(在PS中放大后效果)

上图中,上面几条线是以整数为坐标的线,下面几条是坐标偏移了0.5px的线。

从该例子中看出,即使是非整数宽的线,坐标偏移0.5也能解决这种问题。当宽小于1px时,实际画的线还是1px宽,但是颜色要淡一些,视觉上就也达到了细一些的效果了(请看第一张图中的效果)。

canvas画线问题总结

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

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

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

javascript 复制代码
// 封装一个画线的方法
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画线条源码中,右下角的三根线。

相关推荐
广州华水科技4 分钟前
深度测评2026年单北斗GNSS位移监测系统推荐,与高口碑变形监测设备一同引领行业新风尚
前端
Alice-YUE1 小时前
【js高频八股】防抖与节流
开发语言·前端·javascript·笔记·学习·ecmascript
是上好佳佳佳呀2 小时前
【前端(十一)】JavaScript 语法基础笔记(多语言对比)
前端·javascript·笔记
CDN3603 小时前
排查实录:网站偶发502/504错误?360CDN回源超时配置与日志分析技巧
前端·数据库
之歆3 小时前
Day07_CSS盒子模型 · 样式继承 · 用户代理样式
前端·css
DanCheOo3 小时前
AI 应用的安全架构:Prompt 注入、数据泄露、权限边界
前端·人工智能·prompt·安全架构
We་ct4 小时前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域
weixin_427771615 小时前
前端调试隐藏元素
前端
爱上好庆祝6 小时前
学习js的第五天
前端·css·学习·html·css3·js
C澒6 小时前
IntelliPro 产研协作平台:基于 AI Agent 的低代码智能化配置方案设计与实现
前端·低代码·ai编程