HTML 5 Canvas 上的垂直对齐文本

Posted

技术标签:

【中文标题】HTML 5 Canvas 上的垂直对齐文本【英文标题】:Vertical-Align Text on HTML 5 Canvas 【发布时间】:2013-03-13 01:16:27 【问题描述】:

我要做的是在画布元素的中间显示多行文本。文本是动态的,因为它是由用户使用文本框输入的,然后画布会更新为用户输入的文本(代码如下所示)。我希望文本出现在画布上的方式与使用 CSS 中的 vertical-align: middle 属性显示文本的方式类似。我的问题是解决这个问题的最简单方法是什么。

我遇到的一个大问题是用户可以更改字体。由于不同的字体具有不同的高度(即使它们是在 px 高度定义的,它们也不是始终如一的高度)。到目前为止,我最好的想法是计算画布上文本的高度。我在How can you find the height of text on an html canvas? 网站上阅读了这篇文章,请参阅 Daniel 的第二个回答。这应该计算文本的实际高度,然后可以使用该高度来计算文本的正确起始位置,使其在画布上居中。根据我下面的代码,我相信我基本上必须运行类似的代码来预先确定字体的正确起始位置。

这是我在画布上正确包装和显示文本的方法:

    function wrapText(context, text, x, y, maxWidth, lineHeight) 
    //manage carriage return
    text = text.replace(/(\r\n|\n\r|\r|\n)/g, "\n");
    //manage tabulation
    text = text.replace(/(\t)/g, "    "); // I use 4 spaces for tabulation, but you can use anything you want
    //array of lines
    var sections = text.split("\n"); 

     for (s = 0, len = sections.length; s < len; s++) 
          var words = sections[s].split(' ');
          var line = '';

          for (var n = 0; n < words.length; n++) 
              var testLine = line + words[n] + ' ';
              var metrics = context.measureText(testLine);
              var testWidth = metrics.width;
              if (testWidth > maxWidth) 
                  context.fillText(line, x, y);
                  line = words[n] + ' ';
                  y += lineHeight;
               else 
                  line = testLine;
              
          
          context.fillText(line, x, y);

         //new line for new section of the text
         y += lineHeight;
      


      var canvas = document.getElementById('myCanvas');
      var context = canvas.getContext('2d');
      var maxWidth = 350;
      var lineHeight = 25;
      var x = (canvas.width - maxWidth) / 2;
      var y = 60;
      var text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industry's standard dummy text ever since the 1500s.";

      context.font = '14pt Verdana';
      context.fillStyle = '#000';

      wrapText(context, text, x, y, maxWidth, lineHeight); 

我想知道是否有另一种我没有考虑过的方法可以简化问题,或者这种方法是否是最好的方法?是在画布元素上更简单的方法来垂直对齐文本,类似于 CSS?

【问题讨论】:

【参考方案1】:

酷!

我看了你的参考资料:How can you find the height of text on an HTML canvas?

我为 Prestaul 的 答案设置了一个时间测试,该答案实际上使用画布来像素检查文本的下限和上限。在我的测试中,我使用了 all 大写和小写字母(a-z 和 A-Z),而不是他的小/大“gM”。我还使用了一个在 JS 中创建的画布,但我没有添加到 DOM 中。

结果:我能够每秒重复运行测试 900 次 +/- 次。

我没有运行 Daniel 的测试来操作 DOM 元素进行测量,但我假设这会比 Prestaul 的 1+ 毫秒 (!) 结果慢。

我的结论是,每当用户更改字体时,我都会使用 Prestaul 的方法来测试最大高度。

就个人而言,入侵 DOM 可能会奏效,但对我来说,这感觉就像使用一个黑匣子,在浏览器更新后某天可能会包含炸弹。

感谢您激发我的好奇心——我很享受!

[已编辑以包含压力测试代码]

这是压力测试的代码和小提琴:http://jsfiddle.net/m1erickson/ttsjq/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body background-color: ivory; padding:50px; 
</style>

<script>
$(function()

    var canvas=document.createElement("canvas");
    canvas.width=1000;
    canvas.height=50;
    var ctx=canvas.getContext("2d");

    function measureTextHeight(left, top, width, height,text) 

        // Draw the text in the specified area
        ctx.save();
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.fillText(text, 0, 35); // This seems like tall text...  Doesn't it?
        ctx.restore();

        // Get the pixel data from the canvas
        var data = ctx.getImageData(left, top, width, height).data,
            first = false, 
            last = false,
            r = height,
            c = 0;

        // Find the last line with a non-white pixel
        while(!last && r) 
            r--;
            for(c = 0; c < width; c++) 
                if(data[r * width * 4 + c * 4 + 3]) 
                    last = r;
                    break;
                
            
        

        // Find the first line with a non-white pixel
        while(r) 
            r--;
            for(c = 0; c < width; c++) 
                if(data[r * width * 4 + c * 4 + 3]) 
                    first = r;
                    break;
                
            

            // If we've got it then return the height
            if(first != r) return last - first;
        

        // We screwed something up...  What do you expect from free code?
        return 0;
    

    ctx.font='32px Arial';
    var start = new Date().getTime();
    var text="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for(var x=0;x<1000;x++)
      var height = measureTextHeight(0, 0, canvas.width, canvas.height,text);
    
    console.log(measureTextHeight(0, 0, canvas.width, canvas.height,text));
    console.log((new Date().getTime()-start));



); // end $(function());
</script>

</head>

<body>
    <div>Stress testing Prestaul's measureTextHeight function...</div>
</body>
</html>

【讨论】:

感谢 MarkE 的洞察力!在画布上创建垂直对齐功能时,我一定会使用 Prestaul 的方法。如果没有人发表任何关于如何做到这一点的见解,我将继续使用这种方法,我将发布我的解决方案供大家查看:) 我编辑了我的答案以包含我的压力测试代码...希望这会有所帮助! 我注意到您上面的代码的一件事是,我认为您需要将画布尺寸扩大到 50,50 以上,因为这个小矩形不会捕获您提供的 - Z 字符串。如果我要将区域扩展到功能以匹配画布,它将改变文本的高度!除此之外,该功能效果很好。 糟糕...谢谢...已编辑!重新运行并获得新的时间后,现在计算文本高度只需不到 2 毫秒 - 仍然非常快。【参考方案2】:

要在画布中设置垂直对齐文本,请使用textBaseline。例如:

ctx.beginPath();
ctx.font = "10px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle"; // set text in center of place vertically
ctx.fillText("sample text", 100, 100);
ctx.closePath();

【讨论】:

更多信息参见例如mozilla docs【参考方案3】:
ctx.textAlign = "center";
ctx.textBaseline = "middle"; 
const fix = ctx.measureText("H").actualBoundingBoxDescent / 2; // Notice Here
ctx.fillText("H", convasWidth / 2, canvaseHeight / 2 + fix);

见codepen demo

【讨论】:

``` const fix = ctx.measureText("H").fontBoundingBoxDescent; ctx.fillText("H", width / 2, height - fix); ```对我来说效果更好

以上是关于HTML 5 Canvas 上的垂直对齐文本的主要内容,如果未能解决你的问题,请参考以下文章

canvas之文本

如何使用骨架网格垂直对齐图像旁边的文本

无法垂直对齐文本 - angularJS + bootstrap + flexbox

无法在颤动的容器中垂直对齐文本

如何将 UIButton 文本与 UILabel 文本垂直对齐?

将左浮动 div 中的图像与右浮动 div 上的文本垂直对齐