Html 如何修复 HTML5 画布中的模糊文本?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15661339/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I fix blurry text in my HTML5 canvas?
提问by Phil
I am a total n00bwith HTML5
and am working with the canvas
to render shapes, colors, and text. In my app, I have a view adapterthat creates a canvas dynamically, and fills it with content. This works really nicely, except that my text is rendered very fuzzy/blurry/stretched. I have seen a lot of other posts on why defining the widthand heightin CSS
will cause this issue, but I define it all in javascript
.
我是一个总的n00b与HTML5
和我与工作canvas
呈现的形状,颜色和文字。在我的应用程序中,我有一个视图适配器,它动态地创建一个画布,并用内容填充它。这真的很好用,除了我的文本变得非常模糊/模糊/拉伸。我已经看到了很多,为什么定义其他职位的宽度和高度在CSS
会引起这个问题,但我确定这一切javascript
。
The relevant code (view Fiddle):
相关代码(查看Fiddle):
HTML
HTML
<div id="layout-content"></div>
Javascript
Javascript
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);
The results I am seeing (in Safari) are much more skewed than shown in the Fiddle:
我看到的结果(在Safari 中)比 Fiddle 中显示的要倾斜得多:
Mine
矿
Fiddle
小提琴
What am I doing incorrectly? Do I need a separate canvas for each text element? Is it the font? Am I required to first define the canvas in the HTML5 layout? Is there a typo? I am lost.
我做错了什么?每个文本元素都需要一个单独的画布吗?是字体吗?我是否需要先在 HTML5 布局中定义画布?有错别字吗?我搞不清楚了。
回答by MyNameIsKo
The canvas element runs independent from the device or monitor's pixel ratio.
画布元素独立于设备或显示器的像素比率运行。
On the iPad 3+, this ratio is 2. This essentially means that your 1000px width canvas would now need to fill 2000px to match it's stated width on the iPad display. Fortunately for us, this is done automatically by the browser. On the other hand, this is also the reason why you see less definition on images and canvas elements that were made to directly fit their visible area. Because your canvas only knows how to fill 1000px but is asked to draw to 2000px, the browser must now intelligently fill in the blanks between pixels to display the element at its proper size.
在 iPad 3+ 上,这个比率是 2。这基本上意味着你的 1000 像素宽度的画布现在需要填充 2000 像素以匹配它在 iPad 显示器上规定的宽度。对我们来说幸运的是,这是由浏览器自动完成的。另一方面,这也是为什么您在直接适合其可见区域的图像和画布元素上看到较少定义的原因。因为您的画布只知道如何填充 1000 像素,但被要求绘制到 2000 像素,所以浏览器现在必须智能地填充像素之间的空白,以便以适当的大小显示元素。
I would highly recommend you read this articlefrom HTML5Rockswhich explains in more detail how to create high definition elements.
我强烈建议您阅读HTML5Rocks上的这篇文章,它更详细地解释了如何创建高清元素。
tl;dr? Here is an example (based on the above tut) that I use in my own projects to spit out a canvas with the proper resolution:
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);
Hope this helps!
希望这可以帮助!
回答by Phil
Solved!
解决了!
I decided to see what changing the widthand heightattributes I set in javascript
to see how that affected the canvas size -- and it didn't. It changes the resolution.
我决定看看我设置的宽度和高度属性发生了什么变化javascript
,看看这如何影响画布大小——但它没有。它改变了分辨率。
To get the result I wanted, I also had to set the canvas.style.width
attribute, which changes the physical size of the canvas
:
为了获得我想要的结果,我还必须设置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
回答by Adam Mańkowski
I resize canvas element via css to take whole width of parent element. I noticed that width and height of my element is not scaled. I was looking for best way to set size which should be.
我通过 css 调整画布元素的大小以获取父元素的整个宽度。我注意到我的元素的宽度和高度没有缩放。我一直在寻找设置大小的最佳方法。
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
This simple way your canvas will be set perfectly, no matter what screen you will use.
无论您使用什么屏幕,这种简单的方式都可以完美地设置画布。
回答by Basj
This 100% solved it for me:
这 100% 为我解决了这个问题:
var canvas = document.getElementById('canvas');
canvas.width = canvas.getBoundingClientRect().width;
canvas.height = canvas.getBoundingClientRect().height;
(it is close to Adam Mańkowski's solution).
(它接近 Adam Mańkowski 的解决方案)。
回答by LessWrong
I slightly adapted the MyNameIsKocode under canvg(SVG to Canvas js library). I was confused for a while and spend some time for this. Hope this help someone.
我稍微适应了MyNameIsKo下代码canvg(SVG到画布JS库)。我困惑了一段时间,并为此花了一些时间。希望这有助于某人。
HTML
HTML
<div id="chart"><canvas></canvas><svg>Your SVG here</svg></div>
Javascript
Javascript
window.onload = function() {
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;
})();
setHiDPICanvas = function(canvas, w, h, ratio) {
if (!ratio) { ratio = PIXEL_RATIO; }
var can = 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);
}
var svg = document.querySelector('#chart svg'),
canvas = document.querySelector('#chart canvas');
var svgSize = svg.getBoundingClientRect();
var w = svgSize.width, h = svgSize.height;
setHiDPICanvas(canvas, w, h);
var svgString = (new XMLSerializer).serializeToString(svg);
var ctx = canvas.getContext('2d');
ctx.drawSvg(svgString, 0, 0, w, h);
}
回答by jrobins
For those of you working in reactjs, I adapted MyNameIsKo's answer and it works great. Here is the code.
对于那些在 reactjs 工作的人,我改编了 MyNameIsKo 的答案,效果很好。这是代码。
import React from 'react'
export default class CanvasComponent extends React.Component {
constructor(props) {
this.calcRatio = this.calcRatio.bind(this);
}
// Use componentDidMount to draw on the canvas
componentDidMount() {
this.updateChart();
}
calcRatio() {
let 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;
}
// Draw on the canvas
updateChart() {
// Adjust resolution
const ratio = this.calcRatio();
this.canvas.width = this.props.width * ratio;
this.canvas.height = this.props.height * ratio;
this.canvas.style.width = this.props.width + "px";
this.canvas.style.height = this.props.height + "px";
this.canvas.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
const ctx = this.canvas.getContext('2d');
// now use ctx to draw on the canvas
}
render() {
return (
<canvas ref={el=>this.canvas=el} width={this.props.width} height {this.props.height}/>
)
}
}
In this example, I pass in the width and height of the canvas as props.
在这个例子中,我将画布的宽度和高度作为道具传递。
回答by spenceryue
I noticed a detail not mentioned in the other answers. The canvas resolution truncates to integer values.
我注意到其他答案中没有提到的一个细节。画布分辨率截断为整数值。
The default canvas resolution dimensions are canvas.width: 300
and canvas.height: 150
.
默认画布分辨率尺寸为canvas.width: 300
和canvas.height: 150
。
On my screen, window.devicePixelRatio: 1.75
.
在我的屏幕上,window.devicePixelRatio: 1.75
。
So when I set canvas.height = 1.75 * 150
the value is truncated from the desired 262.5
down to 262
.
因此,当我设置canvas.height = 1.75 * 150
该值时,将其从所需的截断262.5
为262
.
A solution is to choose CSS layout dimensions for a given window.devicePixelRatio
such that truncation will not occur on scaling the resolution.
一种解决方案是为给定的 CSS 布局尺寸选择,window.devicePixelRatio
这样在缩放分辨率时不会发生截断。
For example, I could use width: 300px
and height: 152px
which would yield whole numbers when multiplied by 1.75
.
例如,我可以使用width: 300px
andheight: 152px
乘以 时会产生整数1.75
。
Edit: Another solution is to take advantage of the fact CSS pixels can be fractional to counteract the truncation of scaling canvas pixels.
编辑:另一个解决方案是利用 CSS 像素可以是小数这一事实来抵消缩放画布像素的截断。
Below is a demo using this strategy.
以下是使用此策略的演示。
Edit: Here is the OP's fiddle updated to use this strategy: http://jsfiddle.net/65maD/83/.
编辑:这是 OP 的小提琴更新以使用此策略:http: //jsfiddle.net/65maD/83/。
main();
// Rerun on window resize.
window.addEventListener('resize', main);
function main() {
// Prepare canvas with properly scaled dimensions.
scaleCanvas();
// Test scaling calculations by rendering some text.
testRender();
}
function scaleCanvas() {
const container = document.querySelector('#container');
const canvas = document.querySelector('#canvas');
// Get desired dimensions for canvas from container.
let {width, height} = container.getBoundingClientRect();
// Get pixel ratio.
const dpr = window.devicePixelRatio;
// (Optional) Report the dpr.
document.querySelector('#dpr').innerHTML = dpr.toFixed(4);
// Size the canvas a bit bigger than desired.
// Use exaggeration = 0 in real code.
const exaggeration = 20;
width = Math.ceil (width * dpr + exaggeration);
height = Math.ceil (height * dpr + exaggeration);
// Set the canvas resolution dimensions (integer values).
canvas.width = width;
canvas.height = height;
/*-----------------------------------------------------------
- KEY STEP -
Set the canvas layout dimensions with respect to the canvas
resolution dimensions. (Not necessarily integer values!)
-----------------------------------------------------------*/
canvas.style.width = `${width / dpr}px`;
canvas.style.height = `${height / dpr}px`;
// Adjust canvas coordinates to use CSS pixel coordinates.
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
}
function testRender() {
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
// fontBaseline is the location of the baseline of the serif font
// written as a fraction of line-height and calculated from the top
// of the line downwards. (Measured by trial and error.)
const fontBaseline = 0.83;
// Start at the top of the box.
let baseline = 0;
// 50px font text
ctx.font = `50px serif`;
ctx.fillText("Hello World", 0, baseline + fontBaseline * 50);
baseline += 50;
// 25px font text
ctx.font = `25px serif`;
ctx.fillText("Hello World", 0, baseline + fontBaseline * 25);
baseline += 25;
// 12.5px font text
ctx.font = `12.5px serif`;
ctx.fillText("Hello World", 0, baseline + fontBaseline * 12.5);
}
/* HTML is red */
#container
{
background-color: red;
position: relative;
/* Setting a border will mess up scaling calculations. */
/* Hide canvas overflow (if any) in real code. */
/* overflow: hidden; */
}
/* Canvas is green */
#canvas
{
background-color: rgba(0,255,0,.8);
animation: 2s ease-in-out infinite alternate both comparison;
}
/* animate to compare HTML and Canvas renderings */
@keyframes comparison
{
33% {opacity:1; transform: translate(0,0);}
100% {opacity:.7; transform: translate(7.5%,15%);}
}
/* hover to pause */
#canvas:hover, #container:hover > #canvas
{
animation-play-state: paused;
}
/* click to translate Canvas by (1px, 1px) */
#canvas:active
{
transform: translate(1px,1px) !important;
animation: none;
}
/* HTML text */
.text
{
position: absolute;
color: white;
}
.text:nth-child(1)
{
top: 0px;
font-size: 50px;
line-height: 50px;
}
.text:nth-child(2)
{
top: 50px;
font-size: 25px;
line-height: 25px;
}
.text:nth-child(3)
{
top: 75px;
font-size: 12.5px;
line-height: 12.5px;
}
<!-- Make the desired dimensions strange to guarantee truncation. -->
<div id="container" style="width: 313.235px; height: 157.122px">
<!-- Render text in HTML. -->
<div class="text">Hello World</div>
<div class="text">Hello World</div>
<div class="text">Hello World</div>
<!-- Render text in Canvas. -->
<canvas id="canvas"></canvas>
</div>
<!-- Interaction instructions. -->
<p>Hover to pause the animation.<br>
Click to translate the green box by (1px, 1px).</p>
<!-- Color key. -->
<p><em style="color:red">red</em> = HTML rendered<br>
<em style="color:green">green</em> = Canvas rendered</p>
<!-- Report pixel ratio. -->
<p>Device pixel ratio: <code id="dpr"></code>
<em>(physical pixels per CSS pixel)</em></p>
<!-- Info. -->
<p>Zoom your browser to re-run the scaling calculations.
(<code>Ctrl+</code> or <code>Ctrl-</code>)</p>
回答by 1valdis
Try this one line of CSS on your canvas:
image-rendering: pixelated
在画布上试试这一行 CSS:
image-rendering: pixelated
As per MDN:
根据MDN:
When scaling the image up, the nearest-neighbor algorithm must be used, so that the image appears to be composed of large pixels.
放大图像时,必须使用最近邻算法,使图像看起来由大像素组成。
Thus it prevents anti-aliasing entirely.
因此,它完全防止了抗锯齿。
回答by Ievgen Naida
For me, only a combination of different 'pixel perfect' techniques helped to archive the results:
对我来说,只有不同的“像素完美”技术的组合有助于存档结果:
Get and scale with a pixel ratio as @MyNameIsKo suggested.
pixelRatio = window.devicePixelRatio/ctx.backingStorePixelRatio
Scale the canvas on the resize (avoid canvas default stretch scaling).
multiple the lineWidth with pixelRatio to find proper 'real' pixel line thickness:
context.lineWidth = thickness * pixelRatio;
Check whether the thickness of the line is odd or even. add half of the pixelRatio to the line position for the odd thickness values.
x = x + pixelRatio/2;
以@MyNameIsKo 建议的像素比率获取和缩放。
pixelRatio = window.devicePixelRatio/ctx.backingStorePixelRatio
在调整大小时缩放画布(避免画布默认拉伸缩放)。
将 lineWidth 与 pixelRatio 相乘以找到合适的“真实”像素线粗:
context.lineWidth = 厚度 * pixelRatio;
检查线的粗细是奇数还是偶数。将像素比率的一半添加到奇数厚度值的线位置。
x = x + pixelRatio/2;
The odd line will be placed in the middle of the pixel. The line above is used to move it a little bit.
奇数线将放置在像素的中间。上面的线用于稍微移动它。
function getPixelRatio(context) {
dpr = window.devicePixelRatio || 1,
bsr = context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
return dpr / bsr;
}
var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
var pixelRatio = getPixelRatio(context);
var initialWidth = canvas.clientWidth * pixelRatio;
var initialHeight = canvas.clientHeight * pixelRatio;
window.addEventListener('resize', function(args) {
rescale();
redraw();
}, false);
function rescale() {
var width = initialWidth * pixelRatio;
var height = initialHeight * pixelRatio;
if (width != context.canvas.width)
context.canvas.width = width;
if (height != context.canvas.height)
context.canvas.height = height;
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
}
function pixelPerfectLine(x) {
context.save();
context.beginPath();
thickness = 1;
// Multiple your stroke thickness by a pixel ratio!
context.lineWidth = thickness * pixelRatio;
context.strokeStyle = "Black";
context.moveTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 0));
context.lineTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 200));
context.stroke();
context.restore();
}
function pixelPerfectRectangle(x, y, w, h, thickness, useDash) {
context.save();
// Pixel perfect rectange:
context.beginPath();
// Multiple your stroke thickness by a pixel ratio!
context.lineWidth = thickness * pixelRatio;
context.strokeStyle = "Red";
if (useDash) {
context.setLineDash([4]);
}
// use sharp x,y and integer w,h!
context.strokeRect(
getSharpPixel(thickness, x),
getSharpPixel(thickness, y),
Math.floor(w),
Math.floor(h));
context.restore();
}
function redraw() {
context.clearRect(0, 0, canvas.width, canvas.height);
pixelPerfectLine(50);
pixelPerfectLine(120);
pixelPerfectLine(122);
pixelPerfectLine(130);
pixelPerfectLine(132);
pixelPerfectRectangle();
pixelPerfectRectangle(10, 11, 200.3, 443.2, 1, false);
pixelPerfectRectangle(41, 42, 150.3, 443.2, 1, true);
pixelPerfectRectangle(102, 100, 150.3, 243.2, 2, true);
}
function getSharpPixel(thickness, pos) {
if (thickness % 2 == 0) {
return pos;
}
return pos + pixelRatio / 2;
}
rescale();
redraw();
canvas {
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: crisp-edges;
width: 100vh;
height: 100vh;
}
<canvas id="canvas"></canvas>
Resize event is not fired in the snipped so you can try the file on the github
Resize 事件不会在截图中触发,因此您可以尝试github上的文件