【问题标题】:How can you find the height of text on an HTML canvas?如何在 HTML 画布上找到文本的高度?
【发布时间】:2010-11-11 04:46:36
【问题描述】:

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

【问题讨论】:

  • 我很想知道比最佳答案更好的方法。如果有一些算法可以采用任意点字体并找到它的最大/最小界限,那么我会很高兴听到它。 =)
  • @tjameson - 似乎有。请参阅 ellisbben 的回答(以及我对它的改进)。
  • 我想知道 Unicode 字符 'FULL BLOCK' (U+2588) 是否可以通过将其宽度乘以 2 来用作近似值。
  • 值得注意的是,答案在一定程度上取决于您的要求。例如,渲染字符“a”所需的高度与渲染字符“y”所需的高度不同,这是因为下降器延伸到字体基线下方。下面基于 HTML 的答案没有考虑到这一点,并且会为您提供适合任何文本的一般高度,而 @Noitidart 的答案会为特定文本提供更准确的高度。
  • 请记住,您可以拥有类似于 M̶̢̹̝͖̦̖̭͕̭̣͆̃̀̅̒̊͌̿ͅ 的字符,所以这是一个非常棘手的问题,因此请解决一般情况。

标签: javascript text canvas


【解决方案1】:

更新 - 作为这个工作的一个例子,我在Carota editor 中使用了这种技术。

根据 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;
};

除了文本元素之外,我还添加了一个带有display: inline-block 的div,这样我就可以设置它的vertical-align 样式,然后找出浏览器将它放在哪里。

所以你用ascentdescentheight 取回一个对象(为了方便起见,这只是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
  • @ellisbben 值得注意的是,此结果与您的结果略有不同,尽管我不知道为什么。例如,您的说 Courier New 8pt ==> 12 像素高,而这说:Courier New 8pt ==> 13 像素高。我在您的方法中添加了“g”,但这没有区别。有人想知道哪个值最有用(不一定在技术上正确)。
  • 只有将getTextHeight() 的第一行更改为var text = $('&lt;span&gt;Hg&lt;/span&gt;').css({ 'font-family': fontName, 'font-size' : fontSize });,即单独添加大小,才能正常工作。
  • 如何使它适用于非英文文本?见jsfiddle.net/siddjain/6vURk
  • 谢谢!将&lt;div&gt;&lt;/div&gt; 修改为&lt;div style="white-space : nowrap;"&gt;&lt;/div&gt; 以处理非常长的字符串
【解决方案2】:

浏览器开始支持advanced text metrics,当它得到广泛支持时,这将使这项任务变得微不足道:

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 年,Chromium 尚未支持 TextMetrics 对象(在 Electron 中运行)上的宽度。
  • 不幸的是,它在 ie11 中不起作用,因为不支持 metrics.fontBoundingBoxAscent
【解决方案3】:

您可以通过检查大写字母 M 的长度来获得非常接近的垂直高度。

ctx.font = 'bold 10px Arial';

lineHeight = ctx.measureText('M').width;

【讨论】:

  • 宽度如何为我们提供行高的近似值?
  • 它们意味着在给定字体大小下,单个大写“M”的宽度大约与行高相同。 (不知道这是不是真的,但这就是答案的意思)
  • 有趣的答案
【解决方案4】:

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

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

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; }
    }
};

【讨论】:

  • 我怀疑这就是编写 HTML5 规范的人的想法。
  • 这是一个非常可怕的黑客,我非常喜欢。 +1
  • 我不明白。字体上升和字母“m”的宽度之间的联系在哪里?
  • em 是一种相对字体度量,其中一个 em 等于默认字体大小中字母 M 的高度。
  • 对,高度不是宽度......我仍然对连接感到困惑。另外,我认为 em 无关紧要,因为我们只关心以像素为单位的高度。
【解决方案5】:

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

编辑#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.
*/

【讨论】:

  • +1 更好的解决方案 IMO。应该也可以得到基线的位置。
  • 已添加获得基线的答案。
  • 这行得通吗?我什至没有考虑将其粘贴在 div 中。这可能甚至不必添加到 DOM 中,不是吗?
  • 当节点不是文档的一部分时,我完全不知道节点的哪些大小和位置字段存在。如果您知道的话,我会非常有兴趣阅读解决该问题的参考。
  • +1 表示一屏复杂的代码,在具有更好的 Canvas API 的平行宇宙中,这只是 context.measureText(text).height
【解决方案6】:

正如 JJ Stiff 建议的那样,您可以将文本添加到跨度,然后测量跨度的 offsetHeight。

var d = document.createElement("span");
d.font = "20px arial";
d.textContent = "Hello world!";
document.body.appendChild(d);
var emHeight = d.offsetHeight;
document.body.removeChild(d);

HTML5Rocks所示

【讨论】:

  • 这是一个很好的解决方案,谢谢......但我不知道为什么如果这个跨度没有添加到页面并且在我得到它的 offsetHeight 之前可见!它总是在 Chrome 和 Firefox 中返回高度为零!
  • 你是对的,我猜它必须添加到 dom 中才能占用空间。这是这个工作的 JS Fiddle:jsfiddle.net/mpalmerlee/4NfVR/4 我还更新了上面的代码。
  • 使用 clientHeight 也是一种可能。尽管这个答案是解决问题的方法,但它是一个丑陋的解决方法。尽管如此,还是 +1 了。
  • 这不考虑可见文本的实际高度,通常会遇到文本顶部的额外边距......
【解决方案7】:

如果您使用 context.font 定义字体,文本的高度(以像素为单位)是否等于字体大小(以 pts 为单位)?

【讨论】:

  • 对于简单的情况:您总是可以从字体名称中解析出高度:parseInt(ctx.font.split(' ')[0].replace('px', '')) ; //解析字符串:“10px Verdana”
  • 您可以使用 px、pt、em 和 % 作为字体大小。 This is precisely why 这个答案具有误导性。
  • @Jacksonkr,是的,但您仍然可以解析它们并进行相应调整,对吗?或者这种方法是否存在固有限制?
  • @Pacerier 限制是您可能会引入一些导致您拔头发的错误。请记住,混合单元类型会导致错误/意大利面条代码。也就是说,只要问题的风险很低,我就不会超越偶尔的黑客攻击。
【解决方案8】:

只是添加到丹尼尔的答案(这很棒!而且绝对正确!),没有 JQuery 的版本:

function objOff(obj)
{
    var currleft = currtop = 0;
    if( obj.offsetParent )
    { do { currleft += obj.offsetLeft; currtop += obj.offsetTop; }
      while( obj = obj.offsetParent ); }
    else { currleft += obj.offsetLeft; currtop += obj.offsetTop; }
    return [currleft,currtop];
}
function FontMetric(fontName,fontSize) 
{
    var text = document.createElement("span");
    text.style.fontFamily = fontName;
    text.style.fontSize = fontSize + "px";
    text.innerHTML = "ABCjgq|"; 
    // if you will use some weird fonts, like handwriting or symbols, then you need to edit this test string for chars that will have most extreme accend/descend values

    var block = document.createElement("div");
    block.style.display = "inline-block";
    block.style.width = "1px";
    block.style.height = "0px";

    var div = document.createElement("div");
    div.appendChild(text);
    div.appendChild(block);

    // this test div must be visible otherwise offsetLeft/offsetTop will return 0
    // but still let's try to avoid any potential glitches in various browsers
    // by making it's height 0px, and overflow hidden
    div.style.height = "0px";
    div.style.overflow = "hidden";

    // I tried without adding it to body - won't work. So we gotta do this one.
    document.body.appendChild(div);

    block.style.verticalAlign = "baseline";
    var bp = objOff(block);
    var tp = objOff(text);
    var taccent = bp[1] - tp[1];
    block.style.verticalAlign = "bottom";
    bp = objOff(block);
    tp = objOff(text);
    var theight = bp[1] - tp[1];
    var tdescent = theight - taccent;

    // now take it off :-)
    document.body.removeChild(div);

    // return text accent, descent and total height
    return [taccent,theight,tdescent];
}

我刚刚测试了上面的代码,并且在 Mac 上最新的 Chrome、FF 和 Safari 上运行良好。

编辑:我还添加了字体大小,并使用 webfont 而不是系统字体进行了测试 - 效果很棒。

【讨论】:

    【解决方案9】:

    我解决了这个问题 - 使用像素操作。

    这是图形答案:

    代码如下:

        function textHeight (text, font) {
    
        var fontDraw = document.createElement("canvas");
    
        var height = 100;
        var width = 100;
    
        // here we expect that font size will be less canvas geometry
        fontDraw.setAttribute("height", height);
        fontDraw.setAttribute("width", width);
    
        var ctx = fontDraw.getContext('2d');
        // black is default
        ctx.fillRect(0, 0, width, height);
        ctx.textBaseline = 'top';
        ctx.fillStyle = 'white';
        ctx.font = font;
        ctx.fillText(text/*'Eg'*/, 0, 0);
    
        var pixels = ctx.getImageData(0, 0, width, height).data;
    
        // row numbers where we first find letter end where it ends 
        var start = -1;
        var end = -1;
    
        for (var row = 0; row < height; row++) {
            for (var column = 0; column < width; column++) {
    
                var index = (row * width + column) * 4;
    
                // if pixel is not white (background color)
                if (pixels[index] == 0) {
                    // we havent met white (font color) pixel
                    // on the row and the letters was detected
                    if (column == width - 1 && start != -1) {
                        end = row;
                        row = height;
                        break;
                    }
                    continue;
                }
                else {
                    // we find top of letter
                    if (start == -1) {
                        start = row;
                    }
                    // ..letters body
                    break;
                }
    
            }
    
        }
       /*
        document.body.appendChild(fontDraw);
        fontDraw.style.pixelLeft = 400;
        fontDraw.style.pixelTop = 400;
        fontDraw.style.position = "absolute";
       */
    
        return end - start;
    
    }
    

    【讨论】:

    • 我相信这个解决方案没有考虑像小写 i 和 j 这样的虚线字母
    【解决方案10】:

    一行回答

    var height = parseInt(ctx.font) * 1.2; 
    

    CSS "line-height: normal" 介于 1 和 1.2 之间

    阅读here了解更多信息

    【讨论】:

      【解决方案11】:

      我正在编写一个终端模拟器,所以我需要在字符周围绘制矩形。

      var size = 10
      var lineHeight = 1.2 // CSS "line-height: normal" is between 1 and 1.2
      context.font = size+'px/'+lineHeight+'em monospace'
      width = context.measureText('m').width
      height = size * lineHeight
      

      显然,如果您想要角色占用的确切空间量,那将无济于事。但它会为您提供某些用途的良好近似值。

      【讨论】:

        【解决方案12】:

        这是我根据这里的其他一些答案所做的:

        function measureText(text, font) {
        	const span = document.createElement('span');
        	span.appendChild(document.createTextNode(text));
        	Object.assign(span.style, {
        		font: font,
        		margin: '0',
        		padding: '0',
        		border: '0',
        		whiteSpace: 'nowrap'
        	});
        	document.body.appendChild(span);
        	const {width, height} = span.getBoundingClientRect();
        	span.remove();
        	return {width, height};
        }
        
        var font = "italic 100px Georgia";
        var text = "abc this is a test";
        console.log(measureText(text, font));

        【讨论】:

          【解决方案13】:

          我已经实现了一个很好的库,用于使用 HTML 画布测量文本的确切高度和宽度。这应该可以满足您的需求。

          https://github.com/ChrisBellew/text-measurer.js

          【讨论】:

          【解决方案14】:

          这是一个简单的函数。不需要库。

          我编写了这个函数来获取相对于基线的顶部和底部边界。如果textBaseline 设置为alphabetic。它所做的是创建另一个画布,然后在那里绘制,然后找到最顶部和最底部的非空白像素。这就是上限和下限。它作为相对值返回,因此如果高度为 20px,并且基线下方没有任何内容,则上限为 -20

          您必须为其提供字符。否则它显然会给你 0 高度和 0 宽度。

          用法:

          alert(measureHeight('40px serif', 40, 'rg').height)
          

          函数如下:

          function measureHeight(aFont, aSize, aChars, aOptions={}) {
              // if you do pass aOptions.ctx, keep in mind that the ctx properties will be changed and not set back. so you should have a devoted canvas for this
              // if you dont pass in a width to aOptions, it will return it to you in the return object
              // the returned width is Math.ceil'ed
              console.error('aChars: "' + aChars + '"');
              var defaultOptions = {
                  width: undefined, // if you specify a width then i wont have to use measureText to get the width
                  canAndCtx: undefined, // set it to object {can:,ctx:} // if not provided, i will make one
                  range: 3
              };
          
              aOptions.range = aOptions.range || 3; // multiples the aSize by this much
          
              if (aChars === '') {
                  // no characters, so obviously everything is 0
                  return {
                      relativeBot: 0,
                      relativeTop: 0,
                      height: 0,
                      width: 0
                  };
                  // otherwise i will get IndexSizeError: Index or size is negative or greater than the allowed amount error somewhere below
              }
          
              // validateOptionsObj(aOptions, defaultOptions); // not needed because all defaults are undefined
          
              var can;
              var ctx; 
              if (!aOptions.canAndCtx) {
                  can = document.createElement('canvas');;
                  can.mozOpaque = 'true'; // improved performanceo on firefox i guess
                  ctx = can.getContext('2d');
          
                  // can.style.position = 'absolute';
                  // can.style.zIndex = 10000;
                  // can.style.left = 0;
                  // can.style.top = 0;
                  // document.body.appendChild(can);
              } else {
                  can = aOptions.canAndCtx.can;
                  ctx = aOptions.canAndCtx.ctx;
              }
          
              var w = aOptions.width;
              if (!w) {
                  ctx.textBaseline = 'alphabetic';
                  ctx.textAlign = 'left'; 
                  ctx.font = aFont;
                  w = ctx.measureText(aChars).width;
              }
          
              w = Math.ceil(w); // needed as i use w in the calc for the loop, it needs to be a whole number
          
              // must set width/height, as it wont paint outside of the bounds
              can.width = w;
              can.height = aSize * aOptions.range;
          
              ctx.font = aFont; // need to set the .font again, because after changing width/height it makes it forget for some reason
              ctx.textBaseline = 'alphabetic';
              ctx.textAlign = 'left'; 
          
              ctx.fillStyle = 'white';
          
              console.log('w:', w);
          
              var avgOfRange = (aOptions.range + 1) / 2;
              var yBaseline = Math.ceil(aSize * avgOfRange);
              console.log('yBaseline:', yBaseline);
          
              ctx.fillText(aChars, 0, yBaseline);
          
              var yEnd = aSize * aOptions.range;
          
              var data = ctx.getImageData(0, 0, w, yEnd).data;
              // console.log('data:', data)
          
              var botBound = -1;
              var topBound = -1;
          
              // measureHeightY:
              for (y=0; y<=yEnd; y++) {
                  for (var x = 0; x < w; x += 1) {
                      var n = 4 * (w * y + x);
                      var r = data[n];
                      var g = data[n + 1];
                      var b = data[n + 2];
                      // var a = data[n + 3];
          
                      if (r+g+b > 0) { // non black px found
                          if (topBound == -1) { 
                              topBound = y;
                          }
                          botBound = y; // break measureHeightY; // dont break measureHeightY ever, keep going, we till yEnd. so we get proper height for strings like "`." or ":" or "!"
                          break;
                      }
                  }
              }
          
              return {
                  relativeBot: botBound - yBaseline, // relative to baseline of 0 // bottom most row having non-black
                  relativeTop: topBound - yBaseline, // relative to baseline of 0 // top most row having non-black
                  height: (botBound - topBound) + 1,
                  width: w// EDIT: comma has been added to fix old broken code.
              };
          }
          

          relativeBotrelativeTopheight是返回对象中有用的东西。

          这里是示例用法:

          <!DOCTYPE html>
          <html>
          <head>
          <title>Page Title</title>
          <script>
          function measureHeight(aFont, aSize, aChars, aOptions={}) {
          	// if you do pass aOptions.ctx, keep in mind that the ctx properties will be changed and not set back. so you should have a devoted canvas for this
          	// if you dont pass in a width to aOptions, it will return it to you in the return object
          	// the returned width is Math.ceil'ed
          	console.error('aChars: "' + aChars + '"');
          	var defaultOptions = {
          		width: undefined, // if you specify a width then i wont have to use measureText to get the width
          		canAndCtx: undefined, // set it to object {can:,ctx:} // if not provided, i will make one
          		range: 3
          	};
          	
          	aOptions.range = aOptions.range || 3; // multiples the aSize by this much
          	
          	if (aChars === '') {
          		// no characters, so obviously everything is 0
          		return {
          			relativeBot: 0,
          			relativeTop: 0,
          			height: 0,
          			width: 0
          		};
          		// otherwise i will get IndexSizeError: Index or size is negative or greater than the allowed amount error somewhere below
          	}
          	
          	// validateOptionsObj(aOptions, defaultOptions); // not needed because all defaults are undefined
          	
          	var can;
          	var ctx; 
          	if (!aOptions.canAndCtx) {
          		can = document.createElement('canvas');;
          		can.mozOpaque = 'true'; // improved performanceo on firefox i guess
          		ctx = can.getContext('2d');
          		
          		// can.style.position = 'absolute';
          		// can.style.zIndex = 10000;
          		// can.style.left = 0;
          		// can.style.top = 0;
          		// document.body.appendChild(can);
          	} else {
          		can = aOptions.canAndCtx.can;
          		ctx = aOptions.canAndCtx.ctx;
          	}
          	
          	var w = aOptions.width;
          	if (!w) {
          		ctx.textBaseline = 'alphabetic';
          		ctx.textAlign = 'left';	
          		ctx.font = aFont;
          		w = ctx.measureText(aChars).width;
          	}
          	
          	w = Math.ceil(w); // needed as i use w in the calc for the loop, it needs to be a whole number
          	
          	// must set width/height, as it wont paint outside of the bounds
          	can.width = w;
          	can.height = aSize * aOptions.range;
          	
          	ctx.font = aFont; // need to set the .font again, because after changing width/height it makes it forget for some reason
          	ctx.textBaseline = 'alphabetic';
          	ctx.textAlign = 'left';	
          	
          	ctx.fillStyle = 'white';
          	
          	console.log('w:', w);
          	
          	var avgOfRange = (aOptions.range + 1) / 2;
          	var yBaseline = Math.ceil(aSize * avgOfRange);
          	console.log('yBaseline:', yBaseline);
          	
          	ctx.fillText(aChars, 0, yBaseline);
          	
          	var yEnd = aSize * aOptions.range;
          	
          	var data = ctx.getImageData(0, 0, w, yEnd).data;
          	// console.log('data:', data)
          	
          	var botBound = -1;
          	var topBound = -1;
          	
          	// measureHeightY:
          	for (y=0; y<=yEnd; y++) {
          		for (var x = 0; x < w; x += 1) {
          			var n = 4 * (w * y + x);
          			var r = data[n];
          			var g = data[n + 1];
          			var b = data[n + 2];
          			// var a = data[n + 3];
          			
          			if (r+g+b > 0) { // non black px found
          				if (topBound == -1) { 
          					topBound = y;
          				}
          				botBound = y; // break measureHeightY; // dont break measureHeightY ever, keep going, we till yEnd. so we get proper height for strings like "`." or ":" or "!"
          				break;
          			}
          		}
          	}
          	
          	return {
          		relativeBot: botBound - yBaseline, // relative to baseline of 0 // bottom most row having non-black
          		relativeTop: topBound - yBaseline, // relative to baseline of 0 // top most row having non-black
          		height: (botBound - topBound) + 1,
          		width: w
          	};
          }
          
          </script>
          </head>
          <body style="background-color:steelblue;">
          <input type="button" value="reuse can" onClick="alert(measureHeight('40px serif', 40, 'rg', {canAndCtx:{can:document.getElementById('can'), ctx:document.getElementById('can').getContext('2d')}}).height)">
          <input type="button" value="dont reuse can" onClick="alert(measureHeight('40px serif', 40, 'rg').height)">
          <canvas id="can"></canvas>
          <h1>This is a Heading</h1>
          <p>This is a paragraph.</p>
          </body>
          </html>

          relativeBotrelativeTop 是您在此图片中看到的内容:

          https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text

          【讨论】:

            【解决方案15】:

            【讨论】:

            【解决方案16】:

            首先你需要设置一个字体大小的高度,然后根据字体高度的值来判断你的文字当前高度是多少,横文本行,当然一样字体的高度需要累加,如果文字没有超过最大文字框的高度,则全部显示,否则,只显示文字框内的文字。高价值需要你自己的定义。预设高度越大,需要显示和截取的文字高度就越大。

            After the effect is processed(solve)

            Before the effect is processed( unsolved)

              AutoWrappedText.auto_wrap = function(ctx, text, maxWidth, maxHeight) {
            var words = text.split("");
            var lines = [];
            var currentLine = words[0];
            
            var total_height = 0;
            for (var i = 1; i < words.length; i++) {
                var word = words[i];
                var width = ctx.measureText(currentLine + word).width;
                if (width < maxWidth) {
                    currentLine += word;
                } else {
                    lines.push(currentLine);
                    currentLine = word;
                    // TODO dynamically get font size
                    total_height += 25;
            
                    if (total_height >= maxHeight) {
                      break
                    }
                }
            }
            if (total_height + 25 < maxHeight) {
              lines.push(currentLine);
            } else {
              lines[lines.length - 1] += "…";
            }
            return lines;};
            

            【讨论】:

              【解决方案17】:

              我发现JUST FOR ARIAL找到边界框高度的最简单、最快和最准确的方法是使用某些字母的宽度。如果您打算在不让用户选择不同字体的情况下使用某种字体,您可以进行一些研究以找到适合该字体的正确字母。

              <!DOCTYPE html>
              <html>
              <body>
              
              <canvas id="myCanvas" width="700" height="200" style="border:1px solid #d3d3d3;">
              Your browser does not support the HTML5 canvas tag.</canvas>
              
              <script>
              var c = document.getElementById("myCanvas");
              var ctx = c.getContext("2d");
              ctx.font = "100px Arial";
              var txt = "Hello guys!"
              var Hsup=ctx.measureText("H").width;
              var Hbox=ctx.measureText("W").width;
              var W=ctx.measureText(txt).width;
              var W2=ctx.measureText(txt.substr(0, 9)).width;
              
              ctx.fillText(txt, 10, 100);
              ctx.rect(10,100, W, -Hsup);
              ctx.rect(10,100+Hbox-Hsup, W2, -Hbox);
              ctx.stroke();
              </script>
              
              <p><strong>Note:</strong> The canvas tag is not supported in Internet 
              Explorer 8 and earlier versions.</p>
              
              </body>
              </html>

              【讨论】:

                【解决方案18】:

                设置字体大小可能不切实际,因为设置

                ctx.font = ''

                将使用由 CSS 定义的字体以及任何嵌入的字体标签。如果您使用 CSS 字体,您不知道高度是多少,以编程方式使用 measureText 方法,这是非常短视的。但另一方面,IE8 确实返回宽度和高度。

                【讨论】:

                  【解决方案19】:

                  这适用于 1) 多行文本 2) 甚至在 IE9 中!

                  <div class="measureText" id="measureText">
                  </div>
                  
                  
                  .measureText {
                    margin: 0;
                    padding: 0;
                    border: 0;
                    font-family: Arial;
                    position: fixed;
                    visibility: hidden;
                    height: auto;
                    width: auto;
                    white-space: pre-wrap;
                    line-height: 100%;
                  }
                  
                  function getTextFieldMeasure(fontSize, value) {
                      const div = document.getElementById("measureText");
                  
                      // returns wrong result for multiline text with last line empty
                      let arr = value.split('\n');
                      if (arr[arr.length-1].length == 0) {
                          value += '.';
                      }
                  
                      div.innerText = value;
                      div.style['font-size']= fontSize + "px";
                      let rect = div.getBoundingClientRect();
                  
                      return {width: rect.width, height: rect.height};
                  };
                  

                  【讨论】:

                    【解决方案20】:

                    我知道这是一个老问题,但为了将来参考,我想添加一个简短的、最小的、仅限 JS(无 jquery)的解决方案,我相信人们可以从中受益:

                    var measureTextHeight = function(fontFamily, fontSize) 
                    {
                        var text = document.createElement('span');
                        text.style.fontFamily = fontFamily;
                        text.style.fontSize = fontSize + "px";
                        text.textContent = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
                        document.body.appendChild(text);
                        var result = text.getBoundingClientRect().height;
                        document.body.removeChild(text);
                        return result;
                    };
                    

                    【讨论】:

                      【解决方案21】:

                      我在我的一个项目中修补了 CanvasRenderingContext2D.measureText() 以包含文本的实际高度。它是用 vanilla JS 编写的,并且具有零依赖关系。

                      /*
                       * Monkeypatch CanvasRenderingContext2D.measureText() to include actual height of the text
                       */
                      ; (function (global) {
                        "use strict";
                      
                        var _measureText = global.CanvasRenderingContext2D.prototype.measureText;
                      
                        global.CanvasRenderingContext2D.prototype.measureText = function () {
                          var textMetrics = _measureText.apply(this, arguments);
                      
                          var _getHeight = function (text) {
                            var $span = global.document.createElement("span");
                            var spanTextNode = global.document.createTextNode(text);
                            $span.appendChild(spanTextNode);
                            $span.setAttribute("style", `font: ${this.font}`);
                      
                            var $div = global.document.createElement("div");
                            $div.setAttribute("style", "display: inline-block; width: 1px; height: 0; vertical-align: super;");
                      
                            var $parentDiv = global.document.createElement("div");
                            $parentDiv.appendChild($span);
                            $parentDiv.appendChild($div);
                      
                            var $body = global.document.getElementsByTagName("body")[0];
                            $body.appendChild($parentDiv);
                      
                            var divRect = $div.getBoundingClientRect();
                            var spanRect = $span.getBoundingClientRect();
                            var result = {};
                      
                            $div.style.verticalAlign = "baseline";
                            result.ascent = divRect.top - spanRect.top;
                      
                            $div.style.verticalAlign = "bottom";
                            result.height = divRect.top - spanRect.top;
                      
                            result.descent = result.height - result.ascent;
                      
                            $body.removeChild($parentDiv);
                      
                            return result.height - result.descent;
                          }.bind(this);
                      
                          var height = _getHeight(arguments[0]);
                      
                          global.Object.defineProperty(textMetrics, "height", { value: height });
                      
                          return textMetrics;
                        };
                      
                      })(window);
                      

                      你可以这样使用它

                      ctx.font = "bold 64px Verdana, sans-serif"; // Automatically considers it as part of height calculation
                      var textMetrics = ctx.measureText("Foobar");
                      var textHeight = textMetrics.height;
                      

                      【讨论】:

                        【解决方案22】:

                        我有点震惊,这里没有正确的答案。无需进行估计或猜测。此外,字体大小不是字体边界框的实际大小。字体高度取决于你是否有上升和下降。

                        要计算它,请使用ctx.measureText() 并将actualBoundingBoxAscentactualBoundingBoxDescent 相加。这会给你实际的大小。您还可以将font* 版本相加,以获得用于计算元素高度等内容的大小,但严格来说并不是字体实际使用空间的高度。

                        const text = 'Hello World';
                        const canvas = document.querySelector('canvas');
                        canvas.width = 500;
                        canvas.height = 200;
                        
                        const ctx = canvas.getContext('2d');
                        
                        ctx.font = '100px Arial, Helvetica, sans-serif';
                        // top is critical to the fillText() calculation
                        // you can use other positions, but you need to adjust the calculation
                        ctx.textBaseline = 'top';
                        ctx.textAlign = 'center';
                        
                        const metrics = ctx.measureText(text);
                        const width = metrics.width;
                        const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
                        const fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
                        
                        ctx.fillStyle = '#00F'; // blue
                        ctx.fillRect((canvas.width / 2) - (width / 2), (canvas.height / 2) - (fontHeight / 2), width, fontHeight);
                        
                        ctx.fillStyle = '#0F0'; // green
                        ctx.fillRect((canvas.width / 2) - (width / 2), (canvas.height / 2) - (actualHeight / 2), width, actualHeight);
                        
                        // canvas.height / 2 - actualHeight / 2 gets you to the top of
                        // the green box. You have to add actualBoundingBoxAscent to shift
                        //  it just right
                        ctx.fillStyle = '#F00'; // red
                        ctx.fillText(text, canvas.width / 2, canvas.height / 2 - actualHeight / 2 + metrics.actualBoundingBoxAscent);
                        &lt;canvas&gt;&lt;/canvas&gt;

                        【讨论】:

                          【解决方案23】:

                          parseInt(ctx.font, 10)

                          例如

                          let text_height = parseInt(ctx.font, 10)

                          例如返回 35

                          【讨论】:

                            【解决方案24】:

                            在正常情况下,以下应该起作用:

                            var can = CanvasElement.getContext('2d');          //get context
                            var lineHeight = /[0-9]+(?=pt|px)/.exec(can.font); //get height from font variable
                            

                            【讨论】:

                            • 完全完全错误。点大小和像素大小之间存在巨大差异。磅值会根据呈现页面的 DPI 导致不同大小的文本,其中像素大小没有考虑到这一点。
                            • 你熟悉屏幕像素的概念吗?你可能会发现它很有启发性。无论如何,pt 和 px 确实表示不同的高度。值得注意的是,字体可以填充小于它的“高度”,或者更多,无论如何。我不确定画布像素是否被缩放,但我认为是这样。不过,这是一个“错误”的答案。它很简单,可以在很多情况下使用。
                            【解决方案25】:

                            这太疯狂了……文字的高度就是字体大小……你们没有人读过文档吗?

                            context.font = "22px arial";
                            

                            这会将高度设置为 22px。

                            存在的唯一原因..

                            context.measureText(string).width
                            

                            是因为字符串的宽度无法确定,除非它知道你想要宽度的字符串但是对于所有用字体绘制的字符串..高度将为22px。

                            如果您使用除 px 之外的其他测量值,那么高度仍将相同,但使用该测量值,因此您最多只需转换测量值即可。

                            【讨论】:

                            • 有些字母可以超出或低于限制,请参阅whatwg.org/specs/web-apps/current-work/images/baselines.png
                            • 措辞不好,但通常仍然正确。
                            • 阅读所有这些答案,但对于我的简单应用来说,这是正确答案,pt === px
                            • 完全不正确。尝试使用不同字体提出的一些解决方案,您会发现差异很大。
                            【解决方案26】:

                            近似解:

                            var c = document.getElementById("myCanvas");
                            var ctx = c.getContext("2d");
                            ctx.font = "100px Arial";
                            var txt = "Hello guys!"
                            var wt = ctx.measureText(txt).width;
                            var height = wt / txt.length;
                            

                            这将是等宽字体的准确结果。

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 2012-03-10
                              • 2012-05-17
                              • 1970-01-01
                              • 2020-06-06
                              • 1970-01-01
                              • 2020-03-31
                              • 2018-05-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多