规范有一个context.measureText(文本)函数,它将告诉你打印该文本需要多少宽度,但我找不到一种方法来找出它有多高.我知道它基于字体,但我不知道将字体字符串转换为文本高度.
更新 - 对于这个工作的一个例子,我在Carota编辑器中使用了这种技术.
继ellisbben的回答之后,这里有一个增强版本,用于从基线上升和下降,即与Win32的GetTextMetric API 相同tmAscent
和tmDescent
返回.如果您想要使用不同字体/大小的跨度进行自动换行的文本,则需要这样做.
上面的图像是在Safari的画布上生成的,红色是画布被告知绘制文本的顶行,绿色是基线,蓝色是底部(所以红色到蓝色是整个高度).
使用jQuery简洁:
var getTextHeight = function(font) {
var text = $('Hg').css({ fontFamily: font });
var block = $('');
var 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;
};
除了一个文本元素,我添加一个div,display: inline-block
所以我可以设置它的vertical-align
样式,然后找出浏览器放置它的位置.
所以你得到一个对象ascent
,descent
并且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');
画布规范没有给我们一个测量字符串高度的方法.但是,您可以设置文本的大小(以像素为单位),通常可以相对容易地确定垂直边界的大小.
如果你需要的东西更精确的,那么你可以扔文本到画布上,然后获取像素数据,找出多少像素垂直使用.这将相对简单,但效率不高.你可以做这样的事情(它可以工作,但在画布上绘制一些你想删除的文字):
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'; lineM').width;
编辑:你使用画布变换? 如果是这样,您将必须跟踪转换矩阵.以下方法应使用初始变换测量文本的高度.
编辑#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. */
浏览器开始支持高级文本指标,当广泛支持时,这将使这项任务变得微不足道:
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日):
Chrome有一面旗帜(https://bugs.chromium.org/p/chromium/issues/detail?id=277215).
Firefox正在开发中(https://bugzilla.mozilla.org/show_bug.cgi?id=1102584).
Edge没有支持(https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/30922861-advanced-canvas-textmetrics).
node-canvas(node.js模块),大多数都支持(https://github.com/Automattic/node-canvas/wiki/Compatibility-Status).
正如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所示
如果使用context.font定义字体,则文本的高度(以像素为单位)是否等于字体大小(以磅为单位)?
只是为了添加丹尼尔的答案(这很棒!绝对正确!),没有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而不是系统字体测试 - 工作真棒.
我解决了这个问题 - 使用像素操作.
这是图形答案:
这是代码:
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; }