我是 HTML5
的一员 n00b,我正在使用 canvas
来渲染形状、颜色和文本。在我的应用程序中,我有一个 view adapter 动态创建画布,并用内容填充它。这非常有效,除了我的文本被渲染得非常模糊/模糊/拉伸。我看过很多其他帖子,说明为什么在 CSS
中定义 width 和 height 会导致这个问题,但我在 javascript
中定义了这一切。
相关代码(查看Fiddle):
var width = 500;//FIXME:size.w; var height = 500;//FIXME:size.h; var canvas = document.createElement("canvas"); //canvas.className="singleUserCanvas";画布.宽度=宽度;画布.高度=高度; canvas.border = "3px 实心#999999"; canvas.bgcolor = "#999999"; canvas.margin = "(0, 2%, 0, 2%)"; var context = canvas.getContext("2d"); /////////////////// 形状 //// //////////////// var left = 0; //绘制区域1 rect context.fillStyle = "#8bacbe"; context.fillRect(0, (canvas.height*5/6)+1, canvas.width*1.5/8.5, canvas.height*1/6);左=左+canvas.width*1.5/8.5; //绘制区域 2 矩形 context.fillStyle = "#ffe381"; context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*2.75/8.5, canvas.height*1/6);左 = 左 + canvas.width*2.75/8.5 + 1; //绘制区域 3 rect context.fillStyle = "#fbbd36"; context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*1.25/8.5, canvas.height*1/6);左=左+canvas.width*1.25/8.5; //绘制目标区域 rect context.fillStyle = "#004880"; context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*0.25/8.5, canvas.height*1/6);左=左+canvas.width*0.25/8.5; //绘制区域 4 rect context.fillStyle = "#f8961d"; context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*1.25/8.5, canvas.height*1/6);左 = 左 + canvas.width*1.25/8.5 + 1; //绘制区域 5 rect context.fillStyle = "#8a1002"; context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width-left, canvas.height*1/6); ///////////////// TEXT //// ////////////////用户名 context.fillStyle = "黑色的”; context.font = "粗体 18px 无衬线"; context.textAlign = '对'; context.fillText("用户名", 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); //这部分是在视图适配器回调之后完成的,但在这里与将视图重新添加到布局中相关。 var parent = document.getElementById("layout-content"); parent.appendChild(画布);
我看到的结果(在 Safari 中)比 Fiddle 中显示的更偏斜:
矿
https://i.stack.imgur.com/Dh51D.png
小提琴
https://i.stack.imgur.com/HuRUB.png
我做错了什么?每个文本元素是否需要单独的画布?是字体吗?我需要先在 HTML5 布局中定义画布吗?有错别字吗?我搞不清楚了。
clearRect
。
画布元素独立于设备或显示器的像素比率运行。
在 iPad 3+ 上,这个比率是 2。这基本上意味着你的 1000 像素宽度的画布现在需要填充 2000 像素以匹配它在 iPad 显示屏上的规定宽度。对我们来说幸运的是,这是由浏览器自动完成的。另一方面,这也是您在直接适合其可见区域的图像和画布元素上看到较少定义的原因。因为你的画布只知道如何填充 1000 像素,但被要求绘制到 2000 像素,所以浏览器现在必须智能地填充像素之间的空白,以便以适当的大小显示元素。
我强烈建议您阅读 HTML5Rocks 中的 this article,其中更详细地解释了如何创建高清元素。
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);
希望这可以帮助!
解决了!
我决定看看我在 javascript
中设置的 width 和 height 属性发生了什么变化,看看它是如何影响画布大小的——但它没有。它改变了分辨率。
为了得到我想要的结果,我还必须设置 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
,它在大多数现代浏览器中都得到了很好的实现。
虽然@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,因为传入宽度和高度比传入变换矩阵更直观一些。
该函数已从 createHiDPICanvas 重命名为 createHiPPICanvas。正如@MyNameIsKo 自己在他们的回答评论中提到的那样,DPI(每英寸点数)是打印术语(因为打印机用彩色墨水的微小点组成图像)。虽然类似,但显示器使用像素显示图像,因此 PPI(每英寸像素数)是我们用例的更好缩写。
这些简化的一大好处是这个答案现在可以在没有 // @ts-ignore
的 TypeScript 中使用(因为 TS 没有 backingStorePixelRatio
的类型)。
我通过 css 调整画布元素的大小以获取父元素的整个宽度。我注意到我的元素的宽度和高度没有缩放。我正在寻找设置大小的最佳方法。
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
无论您将使用什么屏幕,这种简单的方式都将完美地设置您的画布。
这 100% 为我解决了这个问题:
var canvas = document.getElementById('canvas');
canvas.width = canvas.getBoundingClientRect().width;
canvas.height = canvas.getBoundingClientRect().height;
(它接近 Adam Mańkowski 的解决方案)。
我注意到其他答案中没有提到的一个细节。画布分辨率截断为整数值。
默认画布分辨率尺寸为 canvas.width: 300
和 canvas.height: 150
。
在我的屏幕上,window.devicePixelRatio: 1.75
。
因此,当我设置 canvas.height = 1.75 * 150
时,值会从所需的 262.5
截断为 262
。
一种解决方案是为给定的 window.devicePixelRatio
选择 CSS 布局尺寸,以便在缩放分辨率时不会发生截断。
例如,我可以使用 width: 300px
和 height: 152px
,它们乘以 1.75
会产生整数。
编辑:另一个解决方案是利用 CSS 像素可以是分数的事实来抵消缩放画布像素的截断。
下面是使用此策略的演示。
编辑:这是 OP 的小提琴更新为使用此策略:http://jsfiddle.net/65maD/83/。
主要的(); // 在窗口调整大小时重新运行。 window.addEventListener('resize', main); function main() { // 准备具有适当缩放尺寸的画布。缩放画布(); // 通过渲染一些文本来测试缩放计算。测试渲染(); } 函数 scaleCanvas() { 常量容器 = document.querySelector('#container'); const canvas = document.querySelector('#canvas'); // 从容器中获取所需的画布尺寸。让 {width, height} = container.getBoundingClientRect(); // 获取像素比例。常量 dpr = window.devicePixelRatio; // (可选)报告 dpr。 document.querySelector('#dpr').innerHTML = dpr.toFixed(4); // 画布的大小比期望的大一点。 // 在实际代码中使用 exaggeration = 0。常量夸大 = 20;宽度 = Math.ceil (宽度 * dpr + 夸张);高度 = Math.ceil(高度 * dpr + 夸张); // 设置画布分辨率尺寸(整数值)。画布.宽度 = 宽度; canvas.height = 高度; /*------------------------------------------------ ----------- - 关键步骤 - 相对于画布分辨率尺寸设置画布布局尺寸。 (不一定是整数值!)-------------------------------------------- ----------------*/ canvas.style.width = `${width / dpr}px`; canvas.style.height = `${height / dpr}px`; // 调整画布坐标以使用 CSS 像素坐标。 const ctx = canvas.getContext('2d'); ctx.scale(dpr,dpr); } function testRender() { const canvas = document.querySelector('#canvas'); const ctx = canvas.getContext('2d'); // fontBaseline 是衬线字体的基线位置 // 写为行高的一部分,并从行的顶部 // 向下计算。 (通过反复试验来衡量。) const fontBaseline = 0.83; // 从盒子的顶部开始。让基线 = 0; // 50px 字体文本 ctx.font = `50px serif`; ctx.fillText("Hello World", 0, 基线 + fontBaseline * 50);基线 += 50; // 25px 字体文本 ctx.font = `25px serif`; ctx.fillText("Hello World", 0, 基线 + fontBaseline * 25);基线 += 25; // 12.5px 字体文本 ctx.font = `12.5px serif`; ctx.fillText("Hello World", 0, 基线 + fontBaseline * 12.5); } /* HTML 是红色的 */ #container { background-color: red;位置:相对; /* 设置边框会打乱缩放计算。 */ /* 在真实代码中隐藏画布溢出(如果有)。 */ /* 溢出:隐藏; */ } /* 画布是绿色 */ #canvas { background-color: rgba(0,255,0,.8);动画:2s缓入出无限交替两者比较; } /* 动画比较 HTML 和 Canvas 渲染 */ @keyframes 比较 { 33% {opacity:1;变换:翻译(0,0);} 100% {不透明度:.7; transform: translate(7.5%,15%);} } /* 悬停暂停 */ #canvas:hover, #container:hover > #canvas { animation-play-state: paused; } /* 点击将 Canvas 平移 (1px, 1px) */ #canvas:active { transform: translate(1px,1px) !important;动画:无; } /* HTML 文本 */ .text { 位置:绝对;白颜色; } .text:nth-child(1) { 顶部:0px;字体大小:50px;行高:50px; } .text:nth-child(2) { 顶部:50px;字体大小:25px;行高:25px; } .text:nth-child(3) { 顶部:75px;字体大小:12.5px;行高:12.5px; }
悬停可暂停动画。
点击可将绿色框平移 (1px, 1px)。
red = HTML 渲染
green = Canvas 渲染
设备像素比: (每个 CSS 像素的物理像素)
缩放浏览器以重新运行缩放计算。 (Ctrl+
或 Ctrl-
)
window.devicePixelRatio
用于此处的每个答案。它依赖于客户端,即使对于单个客户端,它也依赖于 zoom(可以随时更改)。客户端需要将此信息提供给您正在使用的任何画布(无论是在网络工作者还是服务器中)。
container.style.width
) 为 501px
而 devicePixelRatio
为 1.25
,则逻辑画布宽度 (canvas.width
) 应为 Math.ceil(501 * 1.25) = 627
,可见画布宽度 (canvas.style.width
) 应为 { 8} (px
)。
我稍微修改了 canvg 下的 MyNameIsKo 代码(SVG 到 Canvas js 库)。我困惑了一段时间,为此花了一些时间。希望这对某人有所帮助。
HTML
<div id="chart"><canvas></canvas><svg>Your SVG here</svg></div>
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);
}
对于那些在 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}/>
)
}
}
在此示例中,我将画布的宽度和高度作为道具传递。
对我来说,只有不同的“像素完美”技术的组合有助于存档结果:
按照@MyNameIsKo 的建议获取和缩放像素比率。 pixelRatio = window.devicePixelRatio/ctx.backingStorePixelRatio 在调整大小时缩放画布(避免画布默认拉伸缩放)。将 lineWidth 与 pixelRatio 相乘以找到合适的“真实”像素线粗细: context.lineWidth = thickness * pixelRatio;检查线的粗细是奇数还是偶数。将一半的 pixelRatio 添加到奇数粗细值的行位置。 x = x + 像素比/2;
奇数线将放置在像素的中间。上面的行用于移动它一点点。
函数 getPixelRatio(context) { dpr = window.devicePixelRatio || 1、bsr = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1个;返回 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);函数 rescale() { var width = initialWidth * pixelRatio;变量高度 = 初始高度 * 像素比; if (width != context.canvas.width) context.canvas.width = 宽度; if (height != context.canvas.height) context.canvas.height = height; context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); } 函数 pixelPerfectLine(x) { context.save(); context.beginPath();厚度 = 1; // 将笔画粗细乘以像素比! context.lineWidth = 厚度 * pixelRatio; context.strokeStyle = "黑色"; context.moveTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 0)); context.lineTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 200)); context.stroke(); context.restore(); } 函数 pixelPerfectRectangle(x, y, w, h, 厚度, useDash) { context.save(); // 像素完美矩形:context.beginPath(); // 将笔画粗细乘以像素比! context.lineWidth = 厚度 * pixelRatio; context.strokeStyle = "红色"; if (useDash) { context.setLineDash([4]); } // 使用尖锐的 x,y 和整数 w,h! context.strokeRect(getSharpPixel(厚度,x),getSharpPixel(厚度,y),Math.floor(w),Math.floor(h)); context.restore(); } function redraw() { context.clearRect(0, 0, canvas.width, canvas.height);像素完美线(50);像素完美线(120);像素完美线(122);像素完美线(130);像素完美线(132);像素完美矩形(); pixelPerfectRectangle(10, 11, 200.3, 443.2, 1, false); pixelPerfectRectangle(41, 42, 150.3, 443.2, 1, true);像素完美矩形(102、100、150.3、243.2、2、真); } function getSharpPixel(thickness, pos) { if (thickness % 2 == 0) { return pos; } 返回 pos + pixelRatio / 2; } 重新缩放();重绘();画布 { 图像渲染:-moz-crisp-edges;图像渲染:-webkit-crisp-edges;图像渲染:像素化;图像渲染:边缘清晰;宽度:100vh;高度:100vh; }
Resize 事件不会在 snipped 中触发,因此您可以尝试 github 上的文件
对我来说,不仅是图像,而且文字质量也很差。用于视网膜/非视网膜显示器的最简单的跨浏览器工作解决方案是渲染两倍于预期大小的图像并像这个人建议的那样缩放画布上下文:https://stackoverflow.com/a/53921030/4837965
以下代码直接为我工作(而其他代码则没有):
const myCanvas = document.getElementById("myCanvas");
const originalHeight = myCanvas.height;
const originalWidth = myCanvas.width;
render();
function render() {
let dimensions = getObjectFitSize(
true,
myCanvas.clientWidth,
myCanvas.clientHeight,
myCanvas.width,
myCanvas.height
);
const dpr = window.devicePixelRatio || 1;
myCanvas.width = dimensions.width * dpr;
myCanvas.height = dimensions.height * dpr;
let ctx = myCanvas.getContext("2d");
let ratio = Math.min(
myCanvas.clientWidth / originalWidth,
myCanvas.clientHeight / originalHeight
);
ctx.scale(ratio * dpr, ratio * dpr); //adjust this!
}
// adapted from: https://www.npmjs.com/package/intrinsic-scale
function getObjectFitSize(
contains /* true = contain, false = cover */,
containerWidth,
containerHeight,
width,
height
) {
var doRatio = width / height;
var cRatio = containerWidth / containerHeight;
var targetWidth = 0;
var targetHeight = 0;
var test = contains ? doRatio > cRatio : doRatio < cRatio;
if (test) {
targetWidth = containerWidth;
targetHeight = targetWidth / doRatio;
} else {
targetHeight = containerHeight;
targetWidth = targetHeight * doRatio;
}
return {
width: targetWidth,
height: targetHeight,
x: (containerWidth - targetWidth) / 10,
y: (containerHeight - targetHeight) / 10
};
}
该实施改编自 this Medium 帖子。
不定期副业成功案例分享
window.devicePixelRatio || 1
似乎会给出相同的结果。