在画布文本和 html 元素之间共享基线
Posted
技术标签:
【中文标题】在画布文本和 html 元素之间共享基线【英文标题】:Sharing baseline between canvas text and html element 【发布时间】:2016-10-12 00:14:54 【问题描述】:我正在向 html 画布元素绘制一些文本,在该文本旁边我有一个纯 CSS 样式且绝对定位的 HTML 按钮,其位置由 javascript 计算。我想为两者使用相同的字体,这看起来很容易,而且我也希望两者共享相同的基线。据我所知,目前确实很难,至少考虑到我面临的一系列限制。
开箱即用,画布绘制的文本使用textBaseline = 'alphabetic'
,我无法更改它(出于兼容性原因以及在画布中使用不同字体获取基线对齐文本)。另一方面,HTML 元素的绝对定位使用上边距或下边距,而不是基线。
TextMetrics
documentation on MDN 描述了各种垂直测量值,但指出它们仅在 Chrome 上可用,甚至在设置了一些标志后才可用。我需要在当前浏览器的相当大比例(比如 >80%)上运行的东西。
我不知道将使用哪种字体,因此无法对字体的某些度量信息进行硬编码。
那么我有什么选择呢?
我是否必须将字体渲染到第二个屏幕外画布和examine its pixel data 才能找到实际尺寸?我什至可以相信这些实际尺寸(根据画布上的标记像素计算得出)与用于计算按钮大小的尺寸相匹配,即使对于某些更具艺术性的字体也是如此?
我需要resort to a spacer image吗?顺便说一句,我的问题和that one 的主要区别可能是我在 JavaScript 中进行定位,所以我不需要纯 CSS 解决方案,我可以使用画布可以给我的帮助.
是否有其他方式(即没有间隔图像)来利用vertical-align
CSS 属性,方法是创建一些具有明确定义的基线的外框,然后将按钮嵌套在与同一基线对齐的内部?
我还能采取什么其他方向吗?
【问题讨论】:
显然这是旧的,但如果您可以将textBaseline
属性更改为“中间”,您可以很好地对齐它们。您只需要获取按钮的 line-height css 属性。然后,您可以计算 y
来确定在画布上绘制文本的位置,如下所示:var y = cssLineHeight / 2
。我在制作这个库时发现了这一点:inorganik.github.io/strokeText.js
【参考方案1】:
可能有一种方法可以使用画布功能在画布上绘制 svg,以及更发达的 <text>
和其他来自 SVG 的文本相关元素。
但是,这个解决方案远非完美并且有很多注意事项,但我们会在这个小例子之后来看看它:
function basedLineText(canvasStr, ctx, HTMLStr, HTMLContainer, baseline, x, y, font)
// create our svg elements
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
var txt = document.createElementNS(svgNS, 'text');
var tspan = document.createElementNS(svgNS, 'tspan');
txt.setAttribute('dominant-baseline', baseline);
txt.setAttribute('x', x);
txt.setAttribute('y', y);
tspan.setAttribute('style', 'font:' + font);
txt.appendChild(tspan);
svg.appendChild(txt);
// we will first deal with the canvas part
tspan.textContent = canvasStr;
// we need to append it in the doc to get its BoundingBox
HTMLContainer.appendChild(svg);
// remove the whitespace around our text
var bB = svg.getBBox();
svg.setAttribute('viewBox', bB.x + ',' + bB.y + ',' + bB.width + ',' + bB.height);
svg.setAttribute('width', bB.width);
svg.setAttribute('height', bB.height);
// get the end x position of our text
var left = txt.getBoundingClientRect().right;
// draw it to the canvas
var svgData = new XMLSerializer().serializeToString(svg);
var img = new Image();
img.onload = function()
ctx.drawImage(this, x, y);
;
img.src = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);
// now the HTML side
tspan.textContent = HTMLStr;
// text size probably has changed
var bB = svg.getBBox();
svg.setAttribute('viewBox', bB.x + ',' + bB.y + ',' + bB.width + ',' + bB.height);
svg.setAttribute('width', bB.width);
svg.setAttribute('height', bB.height);
// move our absolutely positioned container
HTMLContainer.style.top = y + 'px';
HTMLContainer.style.left = (x + left) + 'px';
var ctx = canvas.getContext('2d');
basedLineText('hello', ctx, ' world', tst1, 'alphabetic', 20, 20, '12px "sans serif"');
basedLineText('hello', ctx, ' world', tst2, 'middle', 20, 60, '32px monospace');
basedLineText('hello', ctx, ' world', tst3, 'hanging', 20, 80, '64px cursive');
div,
canvas
position: absolute;
top: 0;
left: 0px;
/* only html content is affected */
div
fill: green;
<canvas id="canvas" ></canvas>
<div id="tst1"></div>
<div id="tst2"></div>
<div id="tst3"></div>
所以现在,注意事项:
跨浏览器状态 : 所有现代浏览器(IE>=9)都应该支持基本渲染,但是一些早期版本的 FF 无法将 SVG 绘制到画布上,但它仍然应该高于您的 80% 请求(我认为它是 80%支持画布的浏览器顺便说一句)。 但是如果你需要导出画布,或者只是访问它的 imageData,那么比率就会下降:IE
可以使用哪些字体: 这可能是最大的限制:在画布上绘制 svg 时,我们需要先将其传递给 HTMLImageElement。浏览器害怕这个元素,出于安全原因,不能从内部文档请求外部资源。对于我们的例子,这确实意味着如果你想包含一个不在用户系统字体库中的字体,你必须将它作为一个 dataURI 包含在 svg 元素中。
易于使用: 我写的函数是针对这个例子的,我认为它不适合任何其他情况,也不适合你的情况。
【讨论】:
这是一个有趣的方法。我没有考虑过 SVG 的 thegetBBox
method。我想知道我是否可以通过使用 SVG 来测量我将要绘制到画布上的文本,而不使用 draw-image-to-canvas 步骤。需要进行一些试验,但这听起来像是另一种选择。
不幸的是,我认为画布文本方法和 svg 方法在所有 UA 中都不够相似,无法避免 drawImage 部分【参考方案2】:
您可以补充说,根据不同浏览器的文本渲染引擎,指标会略有不同。
如果没有低级指标,文本很难且不容易正确。正如您所说,它们目前仅在 Chrome 中以实验性标志可用。
我还能采取其他任何方向吗?
您可以手动读取和解析字体并从中获取指标。 this (Font.js) 解决方案以及 this one (OpenType.js) 可以帮助您做到这一点。这反过来会限制您可以使用的字体类型。
另一种方法是使用第二个画布元素简单地创建按钮。您仍然可以接收点击事件等,但现在您将拥有与主画布中相同的字体条件。
【讨论】:
以上是关于在画布文本和 html 元素之间共享基线的主要内容,如果未能解决你的问题,请参考以下文章