在画布文本和 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 的 the getBBox method。我想知道我是否可以通过使用 SVG 来测量我将要绘制到画布上的文本,而不使用 draw-image-to-canvas 步骤。需要进行一些试验,但这听起来像是另一种选择。 不幸的是,我认为画布文本方法和 svg 方法在所有 UA 中都不够相似,无法避免 drawImage 部分【参考方案2】:

您可以补充说,根据不同浏览器的文本渲染引擎,指标会略有不同。

如果没有低级指标,文本很难且不容易正确。正如您所说,它们目前仅在 Chrome 中以实验性标志可用。

    我还能采取其他任何方向吗?

您可以手动读取和解析字体并从中获取指标。 this (Font.js) 解决方案以及 this one (OpenType.js) 可以帮助您做到这一点。这反过来会限制您可以使用的字体类型。

另一种方法是使用第二个画布元素简单地创建按钮。您仍然可以接收点击事件等,但现在您将拥有与主画布中相同的字体条件。

【讨论】:

以上是关于在画布文本和 html 元素之间共享基线的主要内容,如果未能解决你的问题,请参考以下文章

如何在 HTML5 画布元素上编写文本?

如何在两个动画画布元素之间进行通信?

HTML宽度属性和CSS宽度属性(画布)之间的区别? [复制]

在没有多个画布的情况下分层画布

如何在画布构造函数中将文本放在特定宽度和长度之间

通过字母在画布上绘制文本,以控制单个字母的alpha,同时与中心对齐