Calculate text width with JavaScript - javascript
I'd like to use JavaScript to calculate the width of a string. Is this possible without having to use a monospace typeface?
If it's not built-in, my only idea is to create a table of widths for each character, but this is pretty unreasonable especially supporting Unicode and different type sizes (and all browsers for that matter).
In HTML 5, you can just use the Canvas.measureText method (further explanation here).
Try this fiddle:
/**
* Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
*
* #param {String} text The text to be rendered.
* #param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
*
* #see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
*/
function getTextWidth(text, font) {
// re-use canvas object for better performance
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
const context = canvas.getContext("2d");
context.font = font;
const metrics = context.measureText(text);
return metrics.width;
}
function getCssStyle(element, prop) {
return window.getComputedStyle(element, null).getPropertyValue(prop);
}
function getCanvasFont(el = document.body) {
const fontWeight = getCssStyle(el, 'font-weight') || 'normal';
const fontSize = getCssStyle(el, 'font-size') || '16px';
const fontFamily = getCssStyle(el, 'font-family') || 'Times New Roman';
return `${fontWeight} ${fontSize} ${fontFamily}`;
}
console.log(getTextWidth("hello there!", "bold 12pt arial")); // close to 86
If you want to use the font-size of some specific element myEl, you can make use of the getCanvasFont utility function:
const fontSize = getTextWidth(text, getCanvasFont(myEl));
// do something with fontSize here...
Explanation: The getCanvasFontSize function takes some element's (by default: the body's) font and converts it into a format compatible with the Context.font property. Of course any element must first be added to the DOM before usage, else it gives you bogus values.
More Notes
There are several advantages to this approach, including:
More concise and safer than the other (DOM-based) methods because it does not change global state, such as your DOM.
Further customization is possible by modifying more canvas text properties, such as textAlign and textBaseline.
NOTE: When you add the text to your DOM, remember to also take account of padding, margin and border.
NOTE 2: On some browsers, this method yields sub-pixel accuracy (result is a floating point number), on others it does not (result is only an int). You might want to run Math.floor (or Math.ceil) on the result, to avoid inconsistencies. Since the DOM-based method is never sub-pixel accurate, this method has even higher precision than the other methods here.
According to this jsperf (thanks to the contributors in comments), the Canvas method and the DOM-based method are about equally fast, if caching is added to the DOM-based method and you are not using Firefox. In Firefox, for some reason, this Canvas method is much much faster than the DOM-based method (as of September 2014).
Performance
This fiddle compares this Canvas method to a variation of Bob Monteverde's DOM-based method, so you can analyze and compare accuracy of the results.
Create a DIV styled with the following styles. In your JavaScript, set the font size and attributes that you are trying to measure, put your string in the DIV, then read the current width and height of the DIV. It will stretch to fit the contents and the size will be within a few pixels of the string rendered size.
var fontSize = 12;
var test = document.getElementById("Test");
test.style.fontSize = fontSize;
var height = (test.clientHeight + 1) + "px";
var width = (test.clientWidth + 1) + "px"
console.log(height, width);
#Test
{
position: absolute;
visibility: hidden;
height: auto;
width: auto;
white-space: nowrap; /* Thanks to Herb Caudill comment */
}
<div id="Test">
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
</div>
Here's one I whipped together without example. It looks like we are all on the same page.
String.prototype.width = function(font) {
var f = font || '12px arial',
o = $('<div></div>')
.text(this)
.css({'position': 'absolute', 'float': 'left', 'white-space': 'nowrap', 'visibility': 'hidden', 'font': f})
.appendTo($('body')),
w = o.width();
o.remove();
return w;
}
Using it is simple: "a string".width()
**Added white-space: nowrap so strings with width larger than the window width can be calculated.
I like your "only idea" of just doing a static character width map! It actually works well for my purposes. Sometimes, for performance reasons or because you don't have easy access to a DOM, you may just want a quick hacky standalone calculator calibrated to a single font. So here's one calibrated to Helvetica; pass a string and a font size:
const widths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2796875,0.2765625,0.3546875,0.5546875,0.5546875,0.8890625,0.665625,0.190625,0.3328125,0.3328125,0.3890625,0.5828125,0.2765625,0.3328125,0.2765625,0.3015625,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.2765625,0.2765625,0.584375,0.5828125,0.584375,0.5546875,1.0140625,0.665625,0.665625,0.721875,0.721875,0.665625,0.609375,0.7765625,0.721875,0.2765625,0.5,0.665625,0.5546875,0.8328125,0.721875,0.7765625,0.665625,0.7765625,0.721875,0.665625,0.609375,0.721875,0.665625,0.94375,0.665625,0.665625,0.609375,0.2765625,0.3546875,0.2765625,0.4765625,0.5546875,0.3328125,0.5546875,0.5546875,0.5,0.5546875,0.5546875,0.2765625,0.5546875,0.5546875,0.221875,0.240625,0.5,0.221875,0.8328125,0.5546875,0.5546875,0.5546875,0.5546875,0.3328125,0.5,0.2765625,0.5546875,0.5,0.721875,0.5,0.5,0.5,0.3546875,0.259375,0.353125,0.5890625]
const avg = 0.5279276315789471
function measureText(str, fontSize) {
return Array.from(str).reduce(
(acc, cur) => acc + (widths[cur.charCodeAt(0)] ?? avg), 0
) * fontSize
}
That giant ugly array is ASCII character widths indexed by character code. So this just supports ASCII (otherwise it assumes an average character width). Fortunately, width basically scales linearly with font size, so it works pretty well at any font size. It's noticeably lacking any awareness of kerning or ligatures or whatever.
To "calibrate" I just rendered every character up to charCode 126 (the mighty tilde) on an svg and got the bounding box and saved it to this array; more code and explanation and demo here.
This works for me...
// Handy JavaScript to measure the size taken to render the supplied text;
// you can supply additional style information too if you have it.
function measureText(pText, pFontSize, pStyle) {
var lDiv = document.createElement('div');
document.body.appendChild(lDiv);
if (pStyle != null) {
lDiv.style = pStyle;
}
lDiv.style.fontSize = "" + pFontSize + "px";
lDiv.style.position = "absolute";
lDiv.style.left = -1000;
lDiv.style.top = -1000;
lDiv.textContent = pText;
var lResult = {
width: lDiv.clientWidth,
height: lDiv.clientHeight
};
document.body.removeChild(lDiv);
lDiv = null;
return lResult;
}
jQuery:
(function($) {
$.textMetrics = function(el) {
var h = 0, w = 0;
var div = document.createElement('div');
document.body.appendChild(div);
$(div).css({
position: 'absolute',
left: -1000,
top: -1000,
display: 'none'
});
$(div).html($(el).html());
var styles = ['font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing'];
$(styles).each(function() {
var s = this.toString();
$(div).css(s, $(el).css(s));
});
h = $(div).outerHeight();
w = $(div).outerWidth();
$(div).remove();
var ret = {
height: h,
width: w
};
return ret;
}
})(jQuery);
The ExtJS javascript library has a great class called Ext.util.TextMetrics that "provides precise pixel measurements for blocks of text so that you can determine exactly how high and wide, in pixels, a given block of text will be". You can either use it directly or view its source to code to see how this is done.
http://docs.sencha.com/extjs/6.5.3/modern/Ext.util.TextMetrics.html
I wrote a little tool for that. Perhaps it's useful to somebody. It works without jQuery.
https://github.com/schickling/calculate-size
Usage:
var size = calculateSize("Hello world!", {
font: 'Arial',
fontSize: '12px'
});
console.log(size.width); // 65
console.log(size.height); // 14
Fiddle: http://jsfiddle.net/PEvL8/
<span id="text">Text</span>
<script>
var textWidth = document.getElementById("text").offsetWidth;
</script>
This should work as long as the <span> tag has no other styles applied to it.
offsetWidth will include the width of any borders, horizontal padding, vertical scrollbar width, etc.
You can use the canvas so you don't have to deal so much with css properties:
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.font = "20pt Arial"; // This can be set programmaticly from the element's font-style if desired
var textWidth = ctx.measureText($("#myElement").text()).width;
In case anyone else got here looking both for a way to measure the width of a string and a way to know what's the largest font size that will fit in a particular width, here is a function that builds on #Domi's solution with a binary search:
/**
* Find the largest font size (in pixels) that allows the string to fit in the given width.
*
* #param {String} text - The text to be rendered.
* #param {String} font - The css font descriptor that text is to be rendered with (e.g. "bold ?px verdana") -- note the use of ? in place of the font size.
* #param {Number} width - The width in pixels the string must fit in
* #param {Number} minFontPx - The smallest acceptable font size in pixels
* #param {Number} maxFontPx - The largest acceptable font size in pixels
**/
function GetTextSizeForWidth(text, font, width, minFontPx, maxFontPx) {
for (;;) {
var s = font.replace("?", maxFontPx);
var w = GetTextWidth(text, s);
if (w <= width) {
return maxFontPx;
}
var g = (minFontPx + maxFontPx) / 2;
if (Math.round(g) == Math.round(minFontPx) || Math.round(g) == Math.round(maxFontPx)) {
return g;
}
s = font.replace("?", g);
w = GetTextWidth(text, s);
if (w >= width) {
maxFontPx = g;
} else {
minFontPx = g;
}
}
}
You can also do this with createRange, which is more accurate, than the text cloning technique:
function getNodeTextWidth(nodeWithText) {
var textNode = $(nodeWithText).contents().filter(function () {
return this.nodeType == Node.TEXT_NODE;
})[0];
var range = document.createRange();
range.selectNode(textNode);
return range.getBoundingClientRect().width;
}
The code-snips below, "calculate" the width of the span-tag, appends "..." to it if its too long and reduces the text-length, until it fits in its parent (or until it has tried more than a thousand times)
CSS
div.places {
width : 100px;
}
div.places span {
white-space:nowrap;
overflow:hidden;
}
HTML
<div class="places">
<span>This is my house</span>
</div>
<div class="places">
<span>And my house are your house</span>
</div>
<div class="places">
<span>This placename is most certainly too wide to fit</span>
</div>
JavaScript (with jQuery)
// loops elements classed "places" and checks if their child "span" is too long to fit
$(".places").each(function (index, item) {
var obj = $(item).find("span");
if (obj.length) {
var placename = $(obj).text();
if ($(obj).width() > $(item).width() && placename.trim().length > 0) {
var limit = 0;
do {
limit++;
placename = placename.substring(0, placename.length - 1);
$(obj).text(placename + "...");
} while ($(obj).width() > $(item).width() && limit < 1000)
}
}
});
The better of is to detect whether text will fits right before you display the element. So you can use this function which doesn't requires the element to be on screen.
function textWidth(text, fontProp) {
var tag = document.createElement("div");
tag.style.position = "absolute";
tag.style.left = "-999em";
tag.style.whiteSpace = "nowrap";
tag.style.font = fontProp;
tag.innerHTML = text;
document.body.appendChild(tag);
var result = tag.clientWidth;
document.body.removeChild(tag);
return result;
}
Usage:
if ( textWidth("Text", "bold 13px Verdana") > elementWidth) {
...
}
You can use max-content to measure the pixel width of text.
Here is a utility function that does that. It optionally takes any node as a context to calculate the width in, taking into account any CSS like font-size, letter-spacing, etc.
function measureTextPxWidth(
text,
template = document.createElement("span")
) {
const measurer = template.cloneNode();
measurer.style.setProperty("all", "revert", "important");
measurer.style.setProperty("position", "position", "important");
measurer.style.setProperty("visibility", "hidden", "important");
measurer.style.setProperty("width", "max-content", "important");
measurer.innerText = text;
document.body.appendChild(measurer);
const { width } = measurer.getBoundingClientRect();
document.body.removeChild(measurer);
return width;
}
document.querySelector('.spanTextWidth').innerText =
`${measureTextPxWidth('one two three')}px`
document.querySelector('.h1TextWidth').innerText =
`${measureTextPxWidth('one two three', document.querySelector('h1'))}px`
h1 {
letter-spacing: 3px;
}
<span>one two three</span>
<div class="spanTextWidth"></div>
<h1>one two three</h1>
<div class="h1TextWidth"></div>
If you're okay with installing a package, and you want perhaps a more authoritative or precise answer, you can use opentype.js (surprised no one has mentioned this yet):
import { load } from "opentype.js";
const getWidth = async (text = "Hello World") => {
const font = await load("path/to/some/font");
const { x1, x2 } = font.getPath(text, 0, 0, 12).getBoundingBox();
return x2 - x1;
};
Naturally you'd want to only call load once per font, so you should pull that line out to a higher scope based on your circumstances.
Here's a Code Sandbox comparing this OpenType method to the Canvas and DOM methods:
https://codesandbox.io/s/measure-width-of-text-in-javascript-vctst2
On my machine, for 100 samples each, the typical results are:
OpenType: 5ms
Canvas: 3ms
DOM: 4ms
Another package I found is this one: https://github.com/sffc/word-wrappr
Try this code:
function GetTextRectToPixels(obj)
{
var tmpRect = obj.getBoundingClientRect();
obj.style.width = "auto";
obj.style.height = "auto";
var Ret = obj.getBoundingClientRect();
obj.style.width = (tmpRect.right - tmpRect.left).toString() + "px";
obj.style.height = (tmpRect.bottom - tmpRect.top).toString() + "px";
return Ret;
}
The width and heigth of a text can be obtained with clientWidth and clientHeight
var element = document.getElementById ("mytext");
var width = element.clientWidth;
var height = element.clientHeight;
make sure that style position property is set to absolute
element.style.position = "absolute";
not required to be inside a div, can be inside a p or a span
Building off of Deepak Nadar's answer, I changed the functions parameter's to accept text and font styles. You do not need to reference an element. Also, the fontOptions have defaults, so you to not need to supply all of them.
(function($) {
$.format = function(format) {
return (function(format, args) {
return format.replace(/{(\d+)}/g, function(val, pos) {
return typeof args[pos] !== 'undefined' ? args[pos] : val;
});
}(format, [].slice.call(arguments, 1)));
};
$.measureText = function(html, fontOptions) {
fontOptions = $.extend({
fontSize: '1em',
fontStyle: 'normal',
fontWeight: 'normal',
fontFamily: 'arial'
}, fontOptions);
var $el = $('<div>', {
html: html,
css: {
position: 'absolute',
left: -1000,
top: -1000,
display: 'none'
}
}).appendTo('body');
$(fontOptions).each(function(index, option) {
$el.css(option, fontOptions[option]);
});
var h = $el.outerHeight(), w = $el.outerWidth();
$el.remove();
return { height: h, width: w };
};
}(jQuery));
var dimensions = $.measureText("Hello World!", { fontWeight: 'bold', fontFamily: 'arial' });
// Font Dimensions: 94px x 18px
$('body').append('<p>').text($.format('Font Dimensions: {0}px x {1}px', dimensions.width, dimensions.height));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
The Element.getClientRects() method returns a collection of DOMRect objects that indicate the bounding rectangles for each CSS border box in a client. The returned value is a collection of DOMRect objects, one for each CSS border box associated with the element. Each DOMRect object contains read-only left, top, right and bottom properties describing the border box, in pixels, with the top-left relative to the top-left of the viewport.
Element.getClientRects() by Mozilla Contributors is licensed under CC-BY-SA 2.5.
Summing up all returned rectangle widths yields the total text width in pixels.
document.getElementById('in').addEventListener('input', function (event) {
var span = document.getElementById('text-render')
span.innerText = event.target.value
var rects = span.getClientRects()
var widthSum = 0
for (var i = 0; i < rects.length; i++) {
widthSum += rects[i].right - rects[i].left
}
document.getElementById('width-sum').value = widthSum
})
<p><textarea id='in'></textarea></p>
<p><span id='text-render'></span></p>
<p>Sum of all widths: <output id='width-sum'>0</output>px</p>
Rewritten my answer from scratch (thanks for that minus).
Now function accepts a text and css rules to be applied (and doesn't use jQuery anymore). So it will respect paddings too. Resulting values are being rounded (you can see Math.round there, remove if you want more that precise values)
function getSpan(){
const span = document.createElement('span')
span.style.position = 'fixed';
span.style.visibility = 'hidden';
document.body.appendChild(span);
return span;
}
function textWidth(str, css) {
const span = getSpan();
Object.assign(span.style, css || {});
span.innerText = str;
const w = Math.round(span.getBoundingClientRect().width);
span.remove();
return w;
}
const testStyles = [
{fontSize: '10px'},
{fontSize: '12px'},
{fontSize: '60px'},
{fontSize: '120px'},
{fontSize: '120px', padding: '10px'},
{fontSize: '120px', fontFamily: 'arial'},
{fontSize: '120px', fontFamily: 'tahoma'},
{fontSize: '120px', fontFamily: 'tahoma', padding: '5px'},
];
const ul = document.getElementById('output');
testStyles.forEach(style => {
const li = document.createElement('li');
li.innerText = `${JSON.stringify(style)} > ${textWidth('abc', style)}`;
ul.appendChild(li);
});
<ul id="output"></ul>
For any one out there using React and/or Typescript...
Try this Codepen!
export default function App() {
const spanRef = useRef<HTMLSpanElement>(null);
const [textWidth, setTextWidth] = useState(0);
const getTextWidthInPixels = (ref: HTMLSpanElement) =>
ref.getBoundingClientRect().width;
useEffect(() => {
setTextWidth(getTextWidthInPixels(spanRef.current!));
}, [spanRef]);
return (
<div className="App">
<span
ref={spanRef}
contentEditable
suppressContentEditableWarning
onInput={() => setTextWidth(getTextWidthInPixels(spanRef.current!))}
>
Edit Me!!!
</span>
{`textWidth: ${textWidth}px`}
</div>
);
}
It's a good idea to wrap our text in an inline-positioned element (like a <span>)
useRef is the React way to access a DOM element, the <span> in our case
getBoundingClientRect can get the total width of any DOM element.
contentEditable allows users to change the contents of an element ...which is a little unsafe (React will throw warnings!)
suppressContentEditableWarning will help us prevent these warnings
Use scrollWidth on the containing element of the text to get the minimum width of the element including hidden parts due to overflow. More information at https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth
If the element is not in the DOM, add it to some hidden area to do the measurement. For example:
function measureText(text) {
let div = document.createElement("div");
div.innerText = text;
div.style.whiteSpace = 'nowrap';
body.appendChild(div);
let width = div.scrollWidth;
body.removeChild(div);
return width;
}
The style (font-size, weight, etc.) will be inherited by the element and thus accounted in the width. You could also measure the size of more complex content with scrollWidth and scrollHeight.
var textWidth = (function (el) {
el.style.position = 'absolute';
el.style.top = '-1000px';
document.body.appendChild(el);
return function (text) {
el.innerHTML = text;
return el.clientWidth;
};
})(document.createElement('div'));
I guess this is prety similar to Depak entry, but is based on the work of Louis Lazaris published at an article in impressivewebs page
(function($){
$.fn.autofit = function() {
var hiddenDiv = $(document.createElement('div')),
content = null;
hiddenDiv.css('display','none');
$('body').append(hiddenDiv);
$(this).bind('fit keyup keydown blur update focus',function () {
content = $(this).val();
content = content.replace(/\n/g, '<br>');
hiddenDiv.html(content);
$(this).css('width', hiddenDiv.width());
});
return this;
};
})(jQuery);
The fit event is used to execute the function call inmediatly after the function is asociated to the control.
e.g.: $('input').autofit().trigger("fit");
Without jQuery:
String.prototype.width = function (fontSize) {
var el,
f = fontSize + " px arial" || '12px arial';
el = document.createElement('div');
el.style.position = 'absolute';
el.style.float = "left";
el.style.whiteSpace = 'nowrap';
el.style.visibility = 'hidden';
el.style.font = f;
el.innerHTML = this;
el = document.body.appendChild(el);
w = el.offsetWidth;
el.parentNode.removeChild(el);
return w;
}
// Usage
"MyString".width(12);
Fiddle of working example: http://jsfiddle.net/tdpLdqpo/1/
HTML:
<h1 id="test1">
How wide is this text?
</h1>
<div id="result1"></div>
<hr/>
<p id="test2">
How wide is this text?
</p>
<div id="result2"></div>
<hr/>
<p id="test3">
How wide is this text?<br/><br/>
f sdfj f sdlfj lfj lsdk jflsjd fljsd flj sflj sldfj lsdfjlsdjkf sfjoifoewj flsdjfl jofjlgjdlsfjsdofjisdojfsdmfnnfoisjfoi ojfo dsjfo jdsofjsodnfo sjfoj ifjjfoewj fofew jfos fojo foew jofj s f j
</p>
<div id="result3"></div>
JavaScript code:
function getTextWidth(text, font) {
var canvas = getTextWidth.canvas ||
(getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
context.font = font;
var metrics = context.measureText(text);
return metrics.width;
};
$("#result1")
.text("answer: " +
getTextWidth(
$("#test1").text(),
$("#test1").css("font")) + " px");
$("#result2")
.text("answer: " +
getTextWidth(
$("#test2").text(),
$("#test2").css("font")) + " px");
$("#result3")
.text("answer: " +
getTextWidth(
$("#test3").text(),
$("#test3").css("font")) + " px");
I'm using text-metrics package. Works really nice, I tried this solution but in some reasons, it counts it wrong.
textMetrics.init(document.querySelector('h1'), { fontSize: '20px' });
textMetrics.init({
fontSize: '14px',
lineHeight: '20px',
fontFamily: 'Helvetica, Arial, sans-serif',
fontWeight: 400,
width: 100,
});
Hey Everyone I know I'm a little late to the party but here we go
window.addEventListener("error",function(e){ alert(e.message); });
var canvas = new OffscreenCanvas(400, 50);
var ctx = canvas.getContext("2d");
ctx.font = "16px Ariel"; //this can be dynamic using getComputedStyle
const chars = ["a","b","c","d","e","f"," "," "];
const charWidths = new Map();
while(chars.length > 0){
var char = chars.shift();
var wide = ctx.measureText(char).width;
charWidths.set(char,wide);
}
and then you can use it with something like:
var pixelWidth = charWidths.get("0");
//fyi css properties like letter-spacing need to be accounted for
Related
How do i calculate the height of each letter from a string, NOT on a canvas? [duplicate]
The spec has a context.measureText(text) function that will tell you how much width it would require to print that text, but I can't find a way to find out how tall it is. I know it's based on the font, but I don't know to convert a font string to a text height.
Browsers are beginning to support advanced text metrics, which will make this task trivial when it's widely supported: let metrics = ctx.measureText(text); let fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent; let actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; fontHeight gets you the bounding box height that is constant regardless of the string being rendered. actualHeight is specific to the string being rendered. Spec: https://www.w3.org/TR/2012/CR-2dcontext-20121217/#dom-textmetrics-fontboundingboxascent and the sections just below it. Support status (20-Aug-2017): Chrome has it behind a flag (https://bugs.chromium.org/p/chromium/issues/detail?id=277215). Firefox has it in development (https://bugzilla.mozilla.org/show_bug.cgi?id=1102584). Edge has no support (https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/30922861-advanced-canvas-textmetrics). node-canvas (node.js module), mostly supported (https://github.com/Automattic/node-canvas/wiki/Compatibility-Status).
UPDATE - for an example of this working, I used this technique in the Carota editor. Following on from ellisbben's answer, here is an enhanced version to get the ascent and descent from the baseline, i.e. same as tmAscent and tmDescent returned by Win32's GetTextMetric API. This is needed if you want to do a word-wrapped run of text with spans in different fonts/sizes. The above image was generated on a canvas in Safari, red being the top line where the canvas was told to draw the text, green being the baseline and blue being the bottom (so red to blue is the full height). Using jQuery for succinctness: 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; }; In addition to a text element, I add a div with display: inline-block so I can set its vertical-align style, and then find out where the browser has put it. So you get back an object with ascent, descent and height (which is just ascent + descent for convenience). To test it, it's worth having a function that draws a horizontal line: 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(); }; Then you can see how the text is positioned on the canvas relative to the top, baseline and bottom: 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');
You can get a very close approximation of the vertical height by checking the length of a capital M. ctx.font = 'bold 10px Arial'; lineHeight = ctx.measureText('M').width;
The canvas spec doesn't give us a method for measuring the height of a string. However, you can set the size of your text in pixels and you can usually figure out what the vertical bounds are relatively easily. If you need something more precise then you could throw text onto the canvas and then get pixel data and figure out how many pixels are used vertically. This would be relatively simple, but not very efficient. You could do something like this (it works, but draws some text onto your canvas that you would want to remove): 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); For Bespin they do fake a height by measuring the width of a lowercase 'm'... I don't know how this is used, and I would not recommend this method. Here is the relevant Bespin method: 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; } } };
EDIT: Are you using canvas transforms? If so, you'll have to track the transformation matrix. The following method should measure the height of text with the initial transform. EDIT #2: Oddly the code below does not produce correct answers when I run it on this StackOverflow page; it's entirely possible that the presence of some style rules could break this function. The canvas uses fonts as defined by CSS, so in theory we can just add an appropriately styled chunk of text to the document and measure its height. I think this is significantly easier than rendering text and then checking pixel data and it should also respect ascenders and descenders. Check out the following: 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."); } } You'll have to make sure you get the font style correct on the DOM element that you measure the height of but that's pretty straightforward; really you should use something like 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. */
As JJ Stiff suggests, you can add your text to a span and then measure the offsetHeight of the span. 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); As shown on HTML5Rocks
Isn't the height of the text in pixels equal to the font size (in pts) if you define the font using context.font ?
Just to add to Daniel's answer (which is great! and absolutely right!), version without 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]; } I've just tested the code above and works great on latest Chrome, FF and Safari on Mac. EDIT: I have added font size as well and tested with webfont instead of system font - works awesome.
I solved this problem straitforward - using pixel manipulation. Here is graphical answer: Here is code: 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; }
one line answer var height = parseInt(ctx.font) * 1.2; CSS "line-height: normal" is between 1 and 1.2 read here for more info
I'm kind of shocked that there are no correct answers here. There is no need to make an estimate or a guess. Also, the font-size is not the actual size of the bounding box of the font. The font height depends on whether you have ascenders and descenders. To calculate it, use ctx.measureText() and add together the actualBoundingBoxAscent and the actualBoundingBoxDescent. That'll give you the actual size. You can also add together the font* versions to get the size that is used to calculate things like element height, but isn't strictly the height of the actual used space for the font. const text = 'Hello World'; const canvas = document.querySelector('canvas'); canvas.width = 500; canvas.height = 200; const ctx = canvas.getContext('2d'); const fontSize = 100; ctx.font = `${fontSize}px 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; // fallback to using fontSize if fontBoundingBoxAscent isn't available, like in Firefox. Should be close enough that you aren't more than a pixel off in most cases. const fontHeight = (metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent) ?? fontSize; 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); <canvas></canvas>
This is what I did based on some of the other answers here: 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));
I'm writing a terminal emulator so I needed to draw rectangles around characters. 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 Obviously if you want the exact amount of space the character takes up, it won't help. But it'll give you a good approximation for certain uses.
I have implemented a nice library for measuring the exact height and width of text using HTML canvas. This should do what you want. https://github.com/ChrisBellew/text-measurer.js
Here is a simple function. No library needed. I wrote this function to get the top and bottom bounds relative to baseline. If textBaseline is set to alphabetic. What it does is it creates another canvas, and then draws there, and then finds the top most and bottom most non blank pixel. And that is the top and bottom bounds. It returns it as relative, so if height is 20px, and there is nothing below the baseline, then the top bound is -20. You must supply characters to it. Otherwise it will give you 0 height and 0 width, obviously. Usage: alert(measureHeight('40px serif', 40, 'rg').height) Here is the 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. }; } relativeBot, relativeTop, and height are the useful things in the return object. Here is example usage: <!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> The relativeBot and relativeTop are what you see in this image here: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text
Funny that TextMetrics has width only and no height: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#textmetrics Can you use a Span as on this example? http://mudcu.be/journal/2011/01/html5-typographic-metrics/#alignFix
First of all, you need to set the height of a font size, and then according to the value of the font height to determine the current height of your text is how much, cross-text lines, of course, the same height of the font need to accumulate, if the text does not exceed the largest text box Height, all show, otherwise, only show the text within the box text. High values need your own definition. The larger the preset height, the greater the height of the text that needs to be displayed and intercepted. 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;};
I found that JUST FOR ARIAL the simplest, fastest and accuratest way to find height of bounding box is to use the width of certain letters. If you plan to use a certain font without letting user to choose one different, you can do a little research to find the right letter that do the job for that font. <!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>
setting the font size might not be practical though, since setting ctx.font = '' will use the one defined by CSS as well as any embedded font tags. If you use the CSS font you have no idea what the height is from a programmatic way, using the measureText method, which is very short sighted. On another note though, IE8 DOES return the width and height.
This works 1) for multiline text as well 2) and even in 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}; };
I know this is an old answered question, but for future reference I'd like to add a short, minimal, JS-only (no jquery) solution I believe people can benefit from: 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; };
I monkey patched CanvasRenderingContext2D.measureText() in one of my project to include actual height of the text. It's written in vanilla JS and has zero dependencies. /* * 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); You can use it like this ctx.font = "bold 64px Verdana, sans-serif"; // Automatically considers it as part of height calculation var textMetrics = ctx.measureText("Foobar"); var textHeight = textMetrics.height;
parseInt(ctx.font, 10) e.g. let text_height = parseInt(ctx.font, 10) e.g. returns 35
In normal situations the following should work: var can = CanvasElement.getContext('2d'); //get context var lineHeight = /[0-9]+(?=pt|px)/.exec(can.font); //get height from font variable
This is madding... The height of the text is the font size.. Didn't any of you read the documentation? context.font = "22px arial"; this will set the height to 22px. the only reason there is a.. context.measureText(string).width is because that the width of the string can not be determined unless it knows the string you want the width of but for all the strings drawn with the font.. the height will be 22px. if you use another measurement than px then the height will still be the same but with that measurement so at most all you would have to do is convert the measurement.
Approximate solution: 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; This will be accurate result in monospaced font.
How to limit entered text by pixel width, not character count
Given a fixed pixel width (text needs to fit on an 80px button; assume no padding), how can I limit the text a user enters into a text field to that limit (80px). If it helps, I know the font (Arial) and the font size (12pt). The limit should be enforced real time (user should be able to delete and add text without submission). For example, the text field should let the user enter 4 "W's" or 9 "I's" as long as it comes in under the 80px.
You can use canvas to measure pixel-perfect widths. Just create a wrapper object to do the measuring (so you don't need to recreate the canvas each time): function Measure(font) { this.canvas = document.createElement("canvas"); this.ctx = this.canvas.getContext("2d"); this.ctx.font = font; // method to call to measure width in pixels this.getWidth = function(txt) { return this.ctx.measureText(txt).width }; } // set up a global instance var m = new Measure("12pt sans-serif"); // get funky document.querySelector("input").onkeyup = function() { var w = Math.ceil(m.getWidth(this.value)); // round up in case of float document.querySelector("span").innerHTML = w; }; <input style="font:12pt sans-serif"> <span>0</span>
may be you need some thing like text.width() var test = document.getElementById("Test"); test.style.fontSize = fontSize; var height = (test.clientHeight + 1) + "px"; var width = (test.clientWidth + 1) + "px";
I think that code will help you: #id { width: ; height: ; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
Just throwing out an idea here but, why not use a Monospace font? Since monospace font characters are all a fixed width, given a font size, some experimentation, and a little math later, you could use character count to limit the input to 80px.
jQuery script efficiency
I use the below jQuery function named textfill on hundreds of divs. Basically it resizes the inner text to fit the enclosing div such that the font-size of the text is maximum, So longer texts are smaller than shorter ones but are at maximum font size that they can be without overflowing from the div. ; (function($) { /** * Resizes an inner element's font so that the inner element completely fills the outer element. * #version 0.1 * #param {Object} Options which are maxFontPixels (default=40), innerTag (default='span') * #return All outer elements processed * #example <div class='mybigdiv filltext'><span>My Text To Resize</span></div> */ $.fn.textfill = function(options) { var defaults = { maxFontPixels: 40, innerTag: 'span' }; var Opts = jQuery.extend(defaults, options); return this.each(function() { var fontSize = Opts.maxFontPixels; var ourText = $(Opts.innerTag + ':visible:first', this); var maxHeight = $(this).height(); var maxWidth = $(this).width(); var textHeight; var textWidth; do { ourText.css('font-size', fontSize); textHeight = ourText.height(); textWidth = ourText.width(); fontSize = fontSize - 1; } while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3); var pos = (maxHeight-textHeight)/2; ourText.css('top', pos +'px'); }); }; })(jQuery); Because I run this script on hundreds of divs that look like: <div class="textDiv"><span>text appears here</span></div> At the same time using: $('.textDiv').each(function() { $(this).textfill({ maxFontPixels: 28 })}); It takes 40 to 70 seconds depending on the amount of divs. I desperately need to tune the code so it will run faster. I've tried for the last two hours but can't seem to make it run faster. Can someone help? EDIT: Took some input from the comments and changed the code to: var items = document.getElementsByClassName("textDiv"); for (var i = items.length; i--;) { $(items[i]).textfill({ maxFontPixels: 28 }); } It seems to be a bit faster but still really slow.
$('.textDiv').each(function() { $(this).textfill({ maxFontPixels: 28 })}); You're using the function wrong. Every (proper) plugin does already work on jQuery collections, and has the each built-in so that you do not need to put it around the invocation. Just do $('.textDiv').textfill({ maxFontPixels: 28 }); Yet I think that is not your actual problem; loops are quite fast and even for hundred items it won't take seconds. The problem is ourText.css('font-size', fontSize); textHeight = ourText.height(); textWidth = ourText.width(); inside a loop (actually in two nested loops), as it requires a complete reflow by the browser. You will need to minimize the calls to this part, for example by using some kind of binary search (bisection) and/or by applying a interpolation metric that approximates the font size (number of characters divided by area for example?) to get a good starting value. Beside that, there might be other minor optimisations: cache $(this) $(Opts.innerTag + ':visible:first', this); looks like a quite complex selector. Is that really necessary, do you expect hidden elements? Move such extras to the options, and go with $(this).children().first() by default. I'm not sure about your CSS, but how do you set the dimensions of your divs (which you retrieve as maxHeight/maxWidth)? For reducing the reflow costs when changing the fontsize inside, an additional overflow:hidden can help.
Obviously the bottle neck is the inner-most looping (the next is parent of inner-most and so on). Why don't use "bisection" for finding out the font size?: For 200 divs: Bisect solution (needs some refactoring): http://jsfiddle.net/nx2n2/8/ Time: ~700 Current solution: http://jsfiddle.net/pXL5z/3/ Time: ~1400 The most important code: var change = Math.ceil(fontSize / 2); while(true) { change = Math.ceil(change / 2); var prev = fontSize; do { fontSize = fontSize - change; ourText.css('font-size', fontSize); textHeight = ourText.height(); textWidth = ourText.width(); } while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3); change = Math.ceil(change / 2); while (textHeight < maxHeight && textWidth < maxWidth) { fontSize = fontSize + change; ourText.css('font-size', fontSize); textHeight = ourText.height(); textWidth = ourText.width(); } var current = fontSize; if(prev == current) { break; } } // this is because you subtract after change in your original solution // only for 'compatibility' with original solution fontSize = fontSize - 1;
Letter spacing in canvas element
The question says it all pretty much. I've been searching around and starting to worry that it's impossible. I've got this canvas element that I'm drawing text to. I want to set the letter spacing similar to the CSS letter-spacing attribute. By that I mean increasing the amount of pixels between letters when a string is drawn. My code for drawing the text is like so, ctx is the canvas context variable. ctx.font = "3em sheepsans"; ctx.textBaseline = "middle"; ctx.textAlign = "center"; ctx.fillStyle = "rgb(255, 255, 255)"; ctx.fillText("Blah blah text", 1024 / 2, 768 / 2); I've tried adding ctx.letterSpacing = "2px"; before the drawing but with no avail. Is there a way to do this just with a simple setting, or will I have to make a function to individually draw each character with the spacing in mind?
You can't set the letter spacing property, but you you can accomplish wider letter spacing in canvas by inserting one of the various white spaces in between every letter in the string. For instance ctx.font = "3em sheepsans"; ctx.textBaseline = "middle"; ctx.textAlign = "center"; ctx.fillStyle = "rgb(255, 255, 255)"; var ctext = "Blah blah text".split("").join(String.fromCharCode(8202)) ctx.fillText(ctext, 1024 / 2, 768 / 2); This will insert a hair space between every letter. Using 8201 (instead of 8202) will insert the slightly wider thin space For more white space options, see this list of Unicode Spaces This method will help you to preserve the font's kerning much more easily than manually positioning each letter, however you wont be able to tighten your letter spacing this way.
I'm not sure if it should work (per specs), but in some browsers (Chrome) you can set the letter-spacing CSS property on the <canvas> element itself, and it will be applied to all text drawn on the context. (Works in Chrome v56, does not work in Firefox v51 or IE v11.) Note that in Chrome v56 you must re-get the canvas 2d context (and re-set any values you care about) after each change to the letter-spacing style; the spacing appears to be baked into the 2d context that you get. Example: https://jsfiddle.net/hg4pbsne/1/ var inp = document.querySelectorAll('input'), can = document.querySelector('canvas'), ctx = can.getContext('2d'); can.width = can.offsetWidth; [].forEach.call(inp,function(inp){ inp.addEventListener('input', redraw, false) }); redraw(); function redraw(){ ctx.clearRect(0,0,can.width,can.height); can.style.letterSpacing = inp[0].value + 'px'; ctx = can.getContext('2d'); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = '4em sans-serif'; ctx.fillText('Hello', can.width/2, can.height*1/4); can.style.letterSpacing = inp[1].value + 'px'; ctx = can.getContext('2d'); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = '4em sans-serif'; ctx.fillText('World', can.width/2, can.height*3/4); }; canvas { background:white } canvas, label { display:block; width:400px; margin:0.5em auto } <canvas></canvas> <label>hello spacing: <input type="range" min="-20" max="40" value="1" step="0.1"></label> <label>world spacing: <input type="range" min="-20" max="40" value="1" step="0.1"></label> Original, cross-browser answer: This is not possible; the HTML5 Canvas does not have all the text-transformation power of CSS in HTML. I would suggest that you should combine the appropriate technologies for each usage. Use HTML layered with Canvas and perhaps even SVG, each doing what it does best. Note also that 'rolling your own'—drawing each character with a custom offset—is going to produce bad results for most fonts, given that there are letter kerning pairs and pixel-aligned font hinting.
You can't set letter-spacing as a property of the Canvas context. You can only achieve the effect by doing manual spacing, sorry. (As in, drawing each letter manually increasing the x by some pixel amount on each) For the record, you can set a few text properties by using ctx.font but letter-spacing is not one of them. The ones you can set are: "font-style font-variant font-weight font-size/line-height font-family" For instance you can technically write ctx.font = "bold normal normal 12px/normal Verdana" (or any omission of any of those) and it will parse correctly.
To allow for 'letter kerning pairs' and the like, I've written the following. It should take that into account, and rough testing suggests it does. If you have any comments on it then I would point you to my question on the subject (Adding Letter Spacing in HTML Canvas) Basically it uses measureText() to get the width of the whole string, and then removes the first character of the string and measures the width of the remaining string, and uses the difference to calculate the correct positioning - thus taking into account kerning pairs and the like. See the given link for more pseudocode. Here's the HTML: <canvas id="Test1" width="800px" height="200px"><p>Your browser does not support canvas.</p></canvas> Here's the code: this.fillTextWithSpacing = function(context, text, x, y, spacing) { //Start at position (X, Y). //Measure wAll, the width of the entire string using measureText() wAll = context.measureText(text).width; do { //Remove the first character from the string char = text.substr(0, 1); text = text.substr(1); //Print the first character at position (X, Y) using fillText() context.fillText(char, x, y); //Measure wShorter, the width of the resulting shorter string using measureText(). if (text == "") wShorter = 0; else wShorter = context.measureText(text).width; //Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter wChar = wAll - wShorter; //Increment X by wChar + spacing x += wChar + spacing; //wAll = wShorter wAll = wShorter; //Repeat from step 3 } while (text != ""); } Code for demo/eyeball test: element1 = document.getElementById("Test1"); textContext1 = element1.getContext('2d'); textContext1.font = "72px Verdana, sans-serif"; textContext1.textAlign = "left"; textContext1.textBaseline = "top"; textContext1.fillStyle = "#000000"; text = "Welcome to go WAVE"; this.fillTextWithSpacing(textContext1, text, 0, 0, 0); textContext1.fillText(text, 0, 100); Ideally I'd throw multiple random strings at it and do a pixel by pixel comparison. I'm also not sure how good Verdana's default kerning is, though I understand it's better than Arial - suggestions on other fonts to try gratefully accepted. So... so far it looks good. In fact it looks perfect. Still hoping that someone will point out any flaws in the process. In the meantime I will put this here for others to see if they are looking for a solution on this.
here's some coffeescript that allows you to set kerning to your context like so tctx = tcanv.getContext('2d') tctx.kerning = 10 tctx.fillStyle = 'black' tctx.fillText 'Hello World!', 10, 10 the supporting code is: _fillText = CanvasRenderingContext2D::fillText CanvasRenderingContext2D::fillText = (str, x, y, args...) -> # no kerning? default behavior return _fillText.apply this, arguments unless #kerning? # we need to remember some stuff as we loop offset = 0 _.each str, (letter) => _fillText.apply this, [ letter x + offset + #kerning y ].concat args # in case any additional args get sent to fillText at any time offset += #measureText(letter).width + #kerning The javascript would be var _fillText, __slice = [].slice; _fillText = CanvasRenderingContext2D.prototype.fillText; CanvasRenderingContext2D.prototype.fillText = function() { var args, offset, str, x, y, _this = this; str = arguments[0], x = arguments[1], y = arguments[2], args = 4 <= arguments.length ? __slice.call(arguments, 3) : []; if (this.kerning == null) { return _fillText.apply(this, arguments); } offset = 0; return _.each(str, function(letter) { _fillText.apply(_this, [letter, x + offset + _this.kerning, y].concat(args)); offset += _this.measureText(letter).width + _this.kerning; }); };
Not true. You can add letter-spacing property to the canvas element in css and it works perfectly. No need for complicated workarounds. I just figured it out right now in my canvas project. i.e.: canvas { width: 480px; height: 350px; margin: 30px auto 0; padding: 15px 0 0 0; background: pink; display: block; border: 2px dashed brown; letter-spacing: 0.1em; }
This might an old question, but it's still relevant. I took Patrick Matte's expansion of James Carlyle-Clarke's response and got something that, I think, works quite well as is still plain-old-Javascript. The idea is to measure the space between two consecutive characters and "add" to it. Yes, negative numbers work. Here's what I've got (the heavily commented version): function fillTextWithSpacing (context, text, x, y, spacing) { // Total width is needed to adjust the starting X based on text alignment. const total_width = context.measureText (text).width + spacing * (text.length - 1); // We need to save the current text alignment because we have to set it to // left for our calls to fillText() draw in the places we expect. Don't // worry, we're going to set it back at the end. const align = context.textAlign; context.textAlign = "left"; // Nothing to do for left alignment, but adjustments are needed for right // and left. Justify defeats the purpose of manually adjusting character // spacing, and it requires a width to be known. switch (align) { case "right": x -= total_width; break; case "center": x -= total_width / 2; break; } // We have some things to keep track of and the C programmer in me likes // declarations on their own and in groups. let offset, pair_width, char_width, char_next_width, pair_spacing, char, char_next; // We're going to step through the text one character at a time, but we // can't use for(... of ...) because we need to be able to look ahead. for (offset = 0; offset < text.length; offset = offset + 1) { // Easy on the eyes later char = text.charAt (offset); // Default the spacing between the "pair" of characters to 0. We need // for the last character. pair_spacing = 0; // Check to see if there's at least one more character after this one. if (offset + 1 < text.length) { // This is always easier on the eyes char_next = text.charAt (offset + 1); // Measure to the total width of both characters, including the // spacing between them... even if it's negative. pair_width = context.measureText (char + char_next).width; // Measure the width of just the current character. char_width = context.measureText (char).width; // Measure the width of just the next character. char_next_width = context.measureText (char_next).width; // We can determine the kerning by subtracting the width of each // character from the width of both characters together. pair_spacing = pair_width - char_width - char_next_width; } // Draw the current character context.fillText (char, x, y); // Advanced the X position by adding the current character width, the // spacing between the current and next characters, and the manual // spacing adjustment (negatives work). x = x + char_width + pair_spacing + spacing; } // Set the text alignment back to the original value. context.textAlign = align; // Profit } And here's a demo: let canvas = document.getElementById ("canvas"); canvas.width = 600; canvas.height = 150; let context = canvas.getContext ("2d"); function fillTextWithSpacing (context, text, x, y, spacing) { const total_width = context.measureText (text).width + spacing * (text.length - 1); const align = context.textAlign; context.textAlign = "left"; switch (align) { case "right": x -= total_width; break; case "center": x -= total_width / 2; break; } let offset, pair_width, char_width, char_next_width, pair_spacing, char, char_next; for (offset = 0; offset < text.length; offset = offset + 1) { char = text.charAt (offset); pair_spacing = 0; if (offset + 1 < text.length) { char_next = text.charAt (offset + 1); pair_width = context.measureText (char + char_next).width; char_width = context.measureText (char).width; char_next_width = context.measureText (char_next).width; pair_spacing = pair_width - char_width - char_next_width; } context.fillText (char, x, y); x = x + char_width + pair_spacing + spacing; } context.textAlign = align; } function update () { let font = document.getElementById ("font").value, size = parseInt (document.getElementById ("size").value, 10), weight = parseInt (document.getElementById ("weight").value, 10), italic = document.getElementById ("italic").checked, spacing = parseInt (document.getElementById ("spacing").value, 10), text = document.getElementById ("text").value; context.textAlign = "center"; context.textBaseline = "alphabetic"; context.fillStyle = "#404040"; context.font = (italic ? "italic " : "") + weight + " " + size + "px " + font; context.clearRect (0, 0, canvas.width, canvas.height); fillTextWithSpacing (context, text, canvas.width / 2, (canvas.height + size) / 2, spacing); } document.getElementById ("font").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("size").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("weight").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("italic").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("spacing").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("text").addEventListener ( "input", (event) => { update (); } ); update (); select, input { display: inline-block; } input[type=text] { display: block; margin: 0.5rem 0; } canvas { border: 1px solid #b0b0b0; width: 600px; height: 150px; } <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8" /> </head> <body> <select id="font"> <option value="serif">Serif</option> <option value="sans-serif">Sans Serif</option> <option value="fixed-width">Fixed Width</option> </select> <label>Size: <input type="number" id="size" value="60" min="1" max="200" size="3" /></label> <label>Weight: <input type="number" id="weight" value="100" min="100" max="1000" step="100" size="4" /></label> <label>Italic: <input type="checkbox" id="italic" checked /></label> <label>Spacing: <input type="number" id="spacing" value="0" min="-200" max="200" size="4" /></label> <input type="text" id="text" placeholder="Text" value="hello" size="40"/> <canvas id="canvas"></canvas> </body> </html>
Actually letter spacing concept canvas is not supporting. So i used javascript to do this. var value = $('#sourceText1').val().split("").join(" "); OR var sample_text = "Praveen Chelumalla"; var text = sample_text.split("").join(" ");
I don't know about other people but I have adjusted line spacing by increasing the y value on the text that I am writing. I'm actually splitting a string by spaces and kicking each word down a line inside a loop. The numbers i use are based on the default font. If you use a different font that these numbers may need to be adjusted. // object holding x and y coordinates var vectors = {'x':{1:100, 2:200}, 'y':{1:0, 2:100} // replace the first letter of a word var newtext = YOURSTRING.replace(/^\b[a-z]/g, function(oldtext) { // return it uppercase return oldtext.toUpperCase(); }); // split string by spaces newtext = newtext.split(/\s+/); // line height var spacing = 10 ; // initial adjustment to position var spaceToAdd = 5; // for each word in the string draw it based on the coordinates + spacing for (var c = 0; c < newtext.length; c++) { ctx.fillText(newtext[c], vectors.x[i], vectors.y[i] - spaceToAdd); // increment the spacing spaceToAdd += spacing; }
Here's another method based on James Carlyle-Clarke's previous answer. It also lets you align the text left, center and right. export function fillTextWithSpacing(context, text, x, y, spacing, textAlign) { const totalWidth = context.measureText(text).width + spacing * (text.length - 1); switch (textAlign) { case "right": x -= totalWidth; break; case "center": x -= totalWidth / 2; break; } for (let i = 0; i < text.length; i++) { let char = text.charAt(i); context.fillText(char, x, y); x += context.measureText(char).width + spacing; } }
Letter spacing in canvas IS SUPPORTED, I used this canvas = document.getElementById('canvas'); canvas.style.letterSpacing = '2px';
I use: ctx.font = "32px Tahoma";//set font ctx.scale(0.75,1);//important! the scale ctx.fillText("LaFeteParFete test text", 2, 274);//draw ctx.setTransform(1,0,0,1,0,0);//reset transform
Real image width with JavaScript
I have the next function: function setImagesWidth(id,width) { var images = document.getElementById(id).getElementsByTagName("img"); for(var i = 0; i < images.length;i++) { // If the real width is bigger than width parameter images[i].style.width=width; //} } } I would like to set the css width attribute of all my img tags to a particular value only when the image real width is bigger than the attribute value. If it is possible, i would like a solution which does not use any particular framework. images[i].offsetWidth returns 111 for an image of 109px width. Is this because 1px each side border?
Here is, hopefully, enough sample code to give you what you want: var myImage = document.getElementById("myImagesId"); var imageWidth = myImage.offsetWidth; var imageHeight = myImage.offsetHeight; That should give you the numbers you need to derive the solution you want. I think you can write the rest of the code yourself. :) EDIT: Here, I couldn't help myself - is this what you are after? function setImagesWidth(id,width) { var images = document.getElementById(id).getElementsByTagName("img"); for(var i = 0; i < images.length;i++) { if(images[i].offsetWidth > width) { images[i].style.width= (width + "px"); } } }
#Sergio del Amo: Indeed, if you check out my link you'll see that you want clientWidth instead. #Sergio del Amo: You cannot, unfortunately, accept your own answer. But you do have an extraneous period in the "px" suffix, so let's go with this, including the clientWidth change: // width in pixels function setImagesWidth(id, width) { var images = document.getElementById(id).getElementsByTagName("img"); var newWidth = width + "px"; for (var i = 0; i < images.length; ++i) { if (images[i].clientWidth > width) { images[i].style.width = newWidth; } } }
Careful, it looks like you might rather want clientWidth: http://developer.mozilla.org/en/Determining_the_dimensions_of_elements
EDIT: Can i accept somehow this answer as the final one? Since offsetWidth does not return any unit, the .px ending must be concatenated for the css attribute. // width in pixels function setImagesWidth(id,width) { var images = document.getElementById(id).getElementsByTagName("img"); for(var i = 0; i < images.length;i++) { if(images[i].offsetWidth > width) { images[i].style.width= (width+".px"); } } }
Just in case you, the reader, came here from google looking for a way to tell what is actual image file pixel width and height, this is how: var img = new Image("path..."); var width = image.naturalWidth; var height = image.naturalHeight; This becomes quite usefull when dealing with all kinds of drawing on scaled images. var img = document.getElementById("img"); var width = img.naturalWidth; var height = img.naturalHeight; document.getElementById("info").innerHTML = "HTML Dimensions: "+img.width+" x "+img.height + "\nReal pixel dimensions:"+ width+" x "+height; <img id="img" src="http://upload.wikimedia.org/wikipedia/commons/0/03/Circle-withsegments.svg" width="100"> <pre id="info"> </pre>