你怎么能findHTMLcanvas上的文字的高度?

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

更新 – 对于这个工作的一个例子,我在Carota编辑器中使用了这种技术。

接下来从ellisbben的回答,这里是一个增强的版本,以获得从基线的上升和下降,即相同的Win32的GetTextMetric API返回的tmAscenttmDescent 。 如果您想要以不同字体/大小的跨度进行文字包装的文本运行,则需要此选项。

在与米制线的画布上的大文本

上面的图像是在Safari上的一个canvas上生成的,红色是canvas被告知绘制文本的顶部线,绿色是基线,蓝色是底部(红色到蓝色是整个高度)。

使用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 )。 为了testing它,有一个函数可以绘制一条水平线:

 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'); 

canvas规范不给我们一个测量string高度的方法。 但是,您可以以像素为单位设置文本的大小,通常可以比较容易地计算出垂直边界的大小。

如果你需要更精确的东西,那么你可以把文本放到canvas上,然后获取像素数据,并找出垂直使用的像素数。 这将是相对简单的,但不是很有效。 你可以做这样的事情(它可以工作,但是在你的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; } } }; 

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

 ctx.font='bold 10px Arial'; lineHeight=ctx.measureText('M').width; 

编辑: 你使用canvas转换? 如果是这样,你将不得不跟踪转换matrix。 下面的方法应该用初始变换来测量文本的高度。

编辑#2:奇怪的是下面的代码不会产生正确的答案,当我在这个StackOverflow页面上运行它; 一些样式规则的存在完全可能会打破这个function。

canvas使用由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. */ 

我不确定,但是我记得Dion Almaer在一个关于Mozilla Bespin 的Google IO介绍中谈到了这件事。 这是关于翻转M字母然后测量它的宽度的东西。 但我可能是错的。

如果您使用context.font定义字体,那么文本的高度不等于字体大小(以磅为单位)?

正如JJ Stiff所build议的那样,您可以将文本添加到跨度,然后测量跨度的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所示

我直截了当地解决了这个问题 – 使用像素操作。

这里是graphics答案:

这是代码:

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

只是添加到丹尼尔的答案(这是伟大的,绝对正确!),没有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]; } 

我刚刚testing了上面的代码,并且在Mac上的最新Chrome,FF和Safari上效果很好。

编辑:我已经添加了字体大小以及与webfont而不是系统字体testing – 工程真棒。

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

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

无论呈现的string如何, fontHeight都可以获得常量的边框高度。 actualHeight是特定于正在呈现的string。

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

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

我已经实现了一个很好的库,用HTML canvas来测量文本的高度和宽度。 这应该做你想要的。

ChrisBellew/text-measurer.html

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

 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 

显然,如果你想要angular色占用的空间的确切数量,它将无济于事。 但它会给你一个很好的近似值用于某些用途。

这是一个简单的function。 没有图书馆需要。

我写这个函数来获得相对于基线的上下界。 如果textBaseline设置为alphabetic 。 它所做的是创build另一个canvas,然后在那里绘制,然后find最上面和最下面的非空白像素。 这就是顶部和底部的界限。 它将它作为相对值返回,所以如果height是20px,并且没有低于基线,那么上限是-20

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

用法:

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

这是function:

 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> 

设置字体大小可能不实际

ctx.font =''

将使用由CSS定义的以及任何embedded式字体标签。 如果使用CSS字体,则不知道程序化方式的高度是多less,而使用measureText方法,这是非常短视的。 在另一个说明虽然,IE8 DOES返回的宽度和高度。

首先,你需要设置一个字体大小的高度,然后根据字体高度的值来确定你的文本的当前高度是多less,跨越文本的线条当然是相同的高度字体需要积累,如果文字不超过文本框的最大高度,全部显示,否则只显示框内的文字文字。 高值需要你自己的定义。 预设高度越大,需要显示和截取的文字高度越大。

效果处理完成后(解决)

在处理效果之前(未解决)

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

这是疯狂的…文本的高度是字体大小..你没有任何人阅读文档?

 context.font = "22px arial"; 

这将设置高度为22px。

唯一的原因是有..

 context.measureText(string).width 

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

如果您使用另一个测量比px,那么高度将仍然是相同的,但是与测量,所以你最多只需要转换测量。

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

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