如何在 HTML 画布上找到文本的高度?

IT技术 javascript text canvas
2021-02-03 04:29:34

该规范有一个 context.measureText(text) 函数,它会告诉您打印该文本需要多少宽度,但我找不到一种方法来找出它有多高。我知道它基于字体,但我不知道将字体字符串转换为文本高度。

6个回答

更新- 作为这个工作的一个例子,我在Carota 编辑器中使用了这个技术

继 ellisbben 的回答之后,这里是一个增强版本,用于从基线获取上升和下降,即与Win32 的GetTextMetric API相同tmAscenttmDescent返回如果您想使用不同字体/大小的跨度进行自动换行的文本运行,则需要这样做。

画布上带有公制线条的大文本

上面的图像是在 Safari 中的画布上生成的,红色是画布被告知要绘制文本的顶行,绿色是基线,蓝色是底部(所以红色到蓝色是整个高度)。

使用 jQuery 简洁:

var getTextHeight = function(font) {

  var text = $('<span>Hg</span>').css({ fontFamily: font });
  var block = $('<div style="display: inline-block; width: 1px; height: 0px;"></div>');

  var div = $('<div></div>');
  div.append(text, block);

  var body = $('body');
  body.append(div);

  try {

    var result = {};

    block.css({ verticalAlign: 'baseline' });
    result.ascent = block.offset().top - text.offset().top;

    block.css({ verticalAlign: 'bottom' });
    result.height = block.offset().top - text.offset().top;

    result.descent = result.height - result.ascent;

  } finally {
    div.remove();
  }

  return result;
};

除了文本元素之外,我还添加了一个 divdisplay: inline-block以便我可以设置它的vertical-align样式,然后找出浏览器放置它的位置。

所以你用ascent, descentand取回一个对象height为了方便,它只是ascent+ descent)。为了测试它,值得拥有一个绘制水平线的函数:

var testLine = function(ctx, x, y, len, style) {
  ctx.strokeStyle = style; 
  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.lineTo(x + len, y);
  ctx.closePath();
  ctx.stroke();
};

然后您可以看到文本相对于顶部、基线和底部如何在画布上定位:

var font = '36pt Times';
var message = 'Big Text';

ctx.fillStyle = 'black';
ctx.textAlign = 'left';
ctx.textBaseline = 'top'; // important!
ctx.font = font;
ctx.fillText(message, x, y);

// Canvas can tell us the width
var w = ctx.measureText(message).width;

// New function gets the other info we need
var h = getTextHeight(font);

testLine(ctx, x, y, w, 'red');
testLine(ctx, x, y + h.ascent, w, 'green');
testLine(ctx, x, y + h.height, w, 'blue');
为什么不使用此文本来确定高度?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 根据字体的不同,您的字符可能远高于或低于 g 和 M
2021-03-14 04:29:34
@ellisbben 值得注意的是,这个结果与你的略有不同,尽管我不知道为什么。例如,你的说 Courier New 8pt ==> 12 像素高,而这说:Courier New 8pt ==> 13 像素高。我在你的方法中添加了“g”,但这不是区别。有人想知道哪个值最有用(不一定在技术上是正确的)。
2021-03-21 04:29:34
当我将第一行更改为getTextHeight()to时,我只能让事情正常工作var text = $('<span>Hg</span>').css({ 'font-family': fontName, 'font-size' : fontSize });,即分别添加大小。
2021-03-22 04:29:34
谢谢 !修改<div></div><div style="white-space : nowrap;"></div>处理很长的字符串
2021-03-26 04:29:34
如何使它适用于非英文文本?参见jsfiddle.net/siddjain/6vURk
2021-04-10 04:29:34

浏览器开始支持高级文本度量,当它得到广泛支持时,这将使这项任务变得微不足道:

let metrics = ctx.measureText(text);
let fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
let actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;

fontHeight无论呈现的字符串如何,都会为您提供恒定的边界框高度。actualHeight特定于正在呈现的字符串。

规范:https : //www.w3.org/TR/2012/CR-2dcontext-20121217/#dom-textmetrics-fontboundingboxascent及其下方的部分。

支持状态(2017 年 8 月 20 日):

遗憾的是,现在是 2021 年,并且铬还没有支持超过宽度的 TextMetrics 对象(在 Electron 中运行)。
2021-03-30 04:29:34
不幸的是它在 ie11 中不起作用,因为不支持 metrics.fontBoundingBoxAscent
2021-03-30 04:29:34
大家请在错误页面上投票,以便更快地实现这些功能
2021-04-10 04:29:34

通过检查大写 M 的长度,您可以获得非常接近的垂直高度近似值。

ctx.font = 'bold 10px Arial';

lineHeight = ctx.measureText('M').width;
有趣的答案
2021-03-31 04:29:34
他们的意思是一个大写“M”在给定的字体大小的宽度大约相同的行高。(不知道这是否属实,但这就是答案所说的)
2021-04-01 04:29:34
宽度如何为我们提供行高的近似值?
2021-04-02 04:29:34

画布规范没有给我们测量字符串高度的方法。但是,您可以以像素为单位设置文本的大小,并且通常可以相对容易地确定垂直边界。

如果您需要更精确的东西,那么您可以将文本扔到画布上,然后获取像素数据并计算出垂直使用了多少像素。这将相对简单,但效率不高。你可以做这样的事情(它有效,但在你的画布上绘制了一些你想要删除的文本):

function measureTextHeight(ctx, left, top, width, height) {

    // Draw the text in the specified area
    ctx.save();
    ctx.translate(left, top + Math.round(height * 0.8));
    ctx.mozDrawText('gM'); // 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;
}

// Set the font
context.mozTextStyle = '32px Arial';

// Specify a context and a rect that is safe to draw in when calling measureTextHeight
var height = measureTextHeight(context, 0, 0, 50, 50);
console.log(height);

对于 Bespin,他们通过测量小写“m”的宽度来伪造高度……我不知道这是如何使用的,我不推荐这种方法。这是相关的 Bespin 方法:

var fixCanvas = function(ctx) {
    // upgrade Firefox 3.0.x text rendering to HTML 5 standard
    if (!ctx.fillText && ctx.mozDrawText) {
        ctx.fillText = function(textToDraw, x, y, maxWidth) {
            ctx.translate(x, y);
            ctx.mozTextStyle = ctx.font;
            ctx.mozDrawText(textToDraw);
            ctx.translate(-x, -y);
        }
    }

    if (!ctx.measureText && ctx.mozMeasureText) {
        ctx.measureText = function(text) {
            ctx.mozTextStyle = ctx.font;
            var width = ctx.mozMeasureText(text);
            return { width: width };
        }
    }

    if (ctx.measureText && !ctx.html5MeasureText) {
        ctx.html5MeasureText = ctx.measureText;
        ctx.measureText = function(text) {
            var textMetrics = ctx.html5MeasureText(text);

            // fake it 'til you make it
            textMetrics.ascent = ctx.html5MeasureText("m").width;

            return textMetrics;
        }
    }

    // for other browsers
    if (!ctx.fillText) {
        ctx.fillText = function() {}
    }

    if (!ctx.measureText) {
        ctx.measureText = function() { return 10; }
    }
};
这是一个我非常喜欢的可怕的黑客攻击。+1
2021-03-14 04:29:34
em是一种相对字体度量,其中一个 em 等于M默认字体大小的字母高度
2021-03-23 04:29:34
我不明白。字体上升和字母“m”的宽度之间的联系在哪里?
2021-03-24 04:29:34
是的,高度不是宽度......我仍然对连接感到困惑。另外,我认为 em 无关紧要,因为我们只关心像素的高度。
2021-04-05 04:29:34
我怀疑这就是编写 HTML5 规范的人的想法。
2021-04-10 04:29:34

编辑:您是否使用画布转换? 如果是这样,您将必须跟踪转换矩阵。以下方法应使用初始变换测量文本的高度。

编辑#2:奇怪的是,当我在这个 StackOverflow 页面上运行它时,下面的代码没有产生正确的答案;某些样式规则的存在完全有可能破坏此功能。

画布使用由 CSS 定义的字体,因此理论上我们可以向文档添加适当样式的文本块并测量其高度。我认为这比渲染文本然后检查像素数据要容易得多,它也应该尊重上升和下降。查看以下内容:

var determineFontHeight = function(fontStyle) {
  var body = document.getElementsByTagName("body")[0];
  var dummy = document.createElement("div");
  var dummyText = document.createTextNode("M");
  dummy.appendChild(dummyText);
  dummy.setAttribute("style", fontStyle);
  body.appendChild(dummy);
  var result = dummy.offsetHeight;
  body.removeChild(dummy);
  return result;
};

//A little test...
var exampleFamilies = ["Helvetica", "Verdana", "Times New Roman", "Courier New"];
var exampleSizes = [8, 10, 12, 16, 24, 36, 48, 96];
for(var i = 0; i < exampleFamilies.length; i++) {
  var family = exampleFamilies[i];
  for(var j = 0; j < exampleSizes.length; j++) {
    var size = exampleSizes[j] + "pt";
    var style = "font-family: " + family + "; font-size: " + size + ";";
    var pixelHeight = determineFontHeight(style);
    console.log(family + " " + size + " ==> " + pixelHeight + " pixels high.");
  }
}

您必须确保在测量高度的 DOM 元素上获得正确的字体样式,但这非常简单;真的你应该使用类似的东西

var canvas = /* ... */
var context = canvas.getContext("2d");
var canvasFont = " ... ";
var fontHeight = determineFontHeight("font: " + canvasFont + ";");
context.font = canvasFont;
/*
  do your stuff with your font and its height here.
*/
添加了一个获得基线的答案。
2021-03-13 04:29:34
这行得通吗?我什至没有想过把它放在一个 div 中。这可能甚至不必添加到 DOM 中,不是吗?
2021-03-16 04:29:34
当节点不是文档的一部分时,我完全不知道节点的哪些大小和位置字段存在。如果您知道,我会非常有兴趣阅读解决该问题的参考资料。
2021-03-28 04:29:34
+1 更好的解决方案 IMO。也应该可以获得基线的位置。
2021-03-31 04:29:34
+1 对于一整屏复杂的代码,这些代码在具有更好 Canvas API 的平行宇宙中只是 context.measureText(text).height
2021-04-10 04:29:34