如何修复 HTML5 画布中的模糊文本?

IT技术 javascript html html5-canvas
2021-01-29 08:25:03

我是一个总的n00bHTML5和我与工作canvas呈现的形状,颜色和文字。在我的应用程序中,我有一个视图适配器,它动态地创建一个画布,并用内容填充它。这真的很好用,除了我的文本变得非常模糊/模糊/拉伸。我已经看到了很多,为什么定义其他职位的宽度高度CSS会引起这个问题,但我确定这一切javascript

相关代码(查看Fiddle):

var width  = 500;//FIXME:size.w;
var height = 500;//FIXME:size.h;
    
var canvas = document.createElement("canvas");
//canvas.className="singleUserCanvas";
canvas.width=width;
canvas.height=height;
canvas.border = "3px solid #999999";
canvas.bgcolor = "#999999";
canvas.margin = "(0, 2%, 0, 2%)";
    
var context = canvas.getContext("2d");

//////////////////
////  SHAPES  ////
//////////////////
    
var left = 0;

//draw zone 1 rect
context.fillStyle = "#8bacbe";
context.fillRect(0, (canvas.height*5/6)+1, canvas.width*1.5/8.5, canvas.height*1/6);

left = left + canvas.width*1.5/8.5;

//draw zone 2 rect
context.fillStyle = "#ffe381";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*2.75/8.5, canvas.height*1/6);

left = left + canvas.width*2.75/8.5 + 1;

//draw zone 3 rect
context.fillStyle = "#fbbd36";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*1.25/8.5, canvas.height*1/6);

left = left + canvas.width*1.25/8.5;

//draw target zone rect
context.fillStyle = "#004880";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*0.25/8.5, canvas.height*1/6);

left = left + canvas.width*0.25/8.5;
    
//draw zone 4 rect
context.fillStyle = "#f8961d";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*1.25/8.5, canvas.height*1/6);

left = left + canvas.width*1.25/8.5 + 1;

//draw zone 5 rect
context.fillStyle = "#8a1002";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width-left, canvas.height*1/6);

////////////////
////  TEXT  ////
////////////////

//user name
context.fillStyle = "black";
context.font = "bold 18px sans-serif";
context.textAlign = 'right';
context.fillText("User Name", canvas.width, canvas.height*.05);

//AT:
context.font = "bold 12px sans-serif";
context.fillText("AT: 140", canvas.width, canvas.height*.1);

//AB:
context.fillText("AB: 94", canvas.width, canvas.height*.15);
       
//this part is done after the callback from the view adapter, but is relevant here to add the view back into the layout.
var parent = document.getElementById("layout-content");
parent.appendChild(canvas);
<div id="layout-content"></div>

我看到的结果(在Safari 中)比 Fiddle 中显示的要倾斜得多:

Safari 中的渲染输出

小提琴

在 JSFiddle 上渲染输出

我做错了什么?每个文本元素都需要一个单独的画布吗?是字体吗?我是否需要先在 HTML5 布局中定义画布?有错别字吗?我搞不清楚了。

6个回答

画布元素独立于设备或显示器的像素比率运行。

在 iPad 3+ 上,这个比率是 2。这实质上意味着你的 1000 像素宽度的画布现在需要填充 2000 像素以匹配它在 iPad 显示器上规定的宽度。对我们来说幸运的是,这是由浏览器自动完成的。另一方面,这也是为什么您在直接适合其可见区域的图像和画布元素上看到较少定义的原因。因为您的画布只知道如何填充 1000 像素,但被要求绘制到 2000 像素,所以浏览器现在必须智能地填充像素之间的空白,以便以适当的大小显示元素。

我强烈建议您阅读HTML5Rocks上的这篇文章它更详细地解释了如何创建高清元素。

tl;博士?这是我在自己的项目中使用的一个示例(基于上述 tut)以正确的分辨率吐出画布:

var PIXEL_RATIO = (function () {
    var ctx = document.createElement("canvas").getContext("2d"),
        dpr = window.devicePixelRatio || 1,
        bsr = ctx.webkitBackingStorePixelRatio ||
              ctx.mozBackingStorePixelRatio ||
              ctx.msBackingStorePixelRatio ||
              ctx.oBackingStorePixelRatio ||
              ctx.backingStorePixelRatio || 1;

    return dpr / bsr;
})();


createHiDPICanvas = function(w, h, ratio) {
    if (!ratio) { ratio = PIXEL_RATIO; }
    var can = document.createElement("canvas");
    can.width = w * ratio;
    can.height = h * ratio;
    can.style.width = w + "px";
    can.style.height = h + "px";
    can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
    return can;
}

//Create canvas with the device resolution.
var myCanvas = createHiDPICanvas(500, 250);

//Create canvas with a custom resolution.
var myCustomCanvas = createHiDPICanvas(500, 200, 4);

希望这可以帮助!

请注意,该比率在页面的生命周期内实际上可能会发生变化。例如,如果我将 Chrome 窗口从旧的“标准”res 外部显示器拖到 macbook 的内置视网膜屏幕,代码将计算不同的比率。如果您打算缓存此值,仅供参考。(如果你好奇的话,外部是比率 1,视网膜屏幕 2)
2021-03-15 08:25:03
更多琐事:Windows Phone 8 的 IE 总是为 window.devicePixelRatio 报告 1(并且支持像素调用不起作用)。1 看起来很糟糕,但 2 的比率看起来不错。现在,我的比率计算无论如何至少返回 2(糟糕的解决方法,但我的目标平台是现代手机,几乎所有手机似乎都具有高 DPI 屏幕)。在 HTC 8X 和 Lumia 1020 上测试。
2021-03-22 08:25:03
backingStorePixelRatio 在 MacBook 上的 Firefox、Chrome 和 Safari 中已折旧且未定义。只有 document.createElement("canvas").getContext("2d").webkitBackingStorePixelRatio 在 Safari 中返回一个数字,该数字为 1。因此似乎将 PIXEL_RATIO 定义为等于window.devicePixelRatio || 1将给出相同的结果。
2021-03-28 08:25:03
认为值得一提的是,我的 createHiDPI() 方法的名称有些糟糕。DPI 是仅用于打印的术语,而 PPI 是更恰当的首字母缩写词,因为显示器使用像素而不是点来显示图像。
2021-03-30 08:25:03
谢谢你的解释。但是图像资产呢?我们是否需要以双倍分辨率提供每个画布图像并手动缩小它?
2021-04-06 08:25:03

解决了!

我决定看看我设置宽度高度属性发生了什么变化javascript,看看这如何影响画布大小——但它没有。它改变了分辨率。

为了获得我想要的结果,我还必须设置canvas.style.width属性,这会更改 的物理大小canvas

canvas.width=1000;//horizontal resolution (?) - increase for better looking text
canvas.height=500;//vertical resolution (?) - increase for better looking text
canvas.style.width=width;//actual width of canvas
canvas.style.height=height;//actual height of canvas
window.devicePixelRatio,并且在大多数现代浏览器中都得到了很好的实现。
2021-03-18 08:25:03
在您的回答中,您将 canvas.width 设置为 1000,将 canvas.style.width 设置为 500 的一半。这有效,但仅适用于像素比为 2 的设备。对于低于此值的任何设备,例如您的桌面显示器,画布现在是绘制到不必要的像素。对于更高的比率,您现在又回到了从模糊、低分辨率的资产/元素开始的地方。Philipp 似乎暗示的另一个问题是,您绘制到上下文中的所有内容现在都必须绘制到两倍的宽度/高度,即使它以该值的一半显示。对此的解决方法是将画布的上下文设置为两倍。
2021-03-29 08:25:03
我不同意。更改 style.width/height 属性正是您创建 HiDPI 画布的方式。
2021-04-11 08:25:03

虽然@MyNameIsKo 的答案仍然有效,但它在 2020 年现在有点过时了,可以改进:

function createHiPPICanvas(w, h) {
    let ratio = window.devicePixelRatio;
    let cv = document.createElement("canvas");
    cv.width = w * ratio;
    cv.height = h * ratio;
    cv.style.width = w + "px";
    cv.style.height = h + "px";
    cv.getContext("2d").scale(ratio, ratio);
    return cv;
}

总的来说,我们做了以下改进:

  • 我们删除了backingStorePixelRatio这些引用,因为它们并没有在任何浏览器中以任何重要的方式真正实现(实际上,只有 Safari 返回除 之外的其他内容undefined,并且该版本在 Safari 中仍然可以完美运行);
  • 我们将所有比率代码替换为window.devicePixelRatio它具有出色的支持
  • 这也意味着我们少声明了一个全局属性——万岁!!
  • 我们还可以删除 上的|| 1回退window.devicePixelRatio,因为它毫无意义:所有不支持此属性的浏览器都不支持.setTransform.scale两者之一,因此该功能对它们不起作用,回退与否;
  • 我们可以替换.setTransform.scale,因为传递宽度和高度比传递变换矩阵更直观。
  • 该函数已从 重命名createHiDPICanvascreateHiPPICanvas正如@MyNameIsKo 在他们的回答评论中提到的那样,DPI(每英寸点数)是打印术语(因为打印机是用彩色墨水的小点组成图像)。虽然类似,监视器使用像素显示图像,因此 PPI(每英寸像素数)是我们用例的更好缩写。

这些简化的一大好处是,现在可以在 TypeScript 中使用此答案// @ts-ignore(因为 TS 没有 类型backingStorePixelRatio)。

感谢您的更新。然而,这是否解决了@spenceryue 在此处提出的问题:stackoverflow.com/a/54027313/144088
2021-03-23 08:25:03

这 100% 为我解决了它:

var canvas = document.getElementById('canvas');
canvas.width = canvas.getBoundingClientRect().width;
canvas.height = canvas.getBoundingClientRect().height;

(它接近 Adam Mańkowski 的解决方案)。

我通过 css 调整画布元素的大小以获取父元素的整个宽度。我注意到我的元素的宽度和高度没有缩放。我一直在寻找设置大小的最佳方法。

canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

无论您使用什么屏幕,这种简单的方式都可以完美地设置您的画布。