How to limit entered text by pixel width, not character count - javascript

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.

Related

Using getComputedStyle with border-box should return height sans border and padding

EDIT 3/Final: Th Computed Style problem/question is explained below but, for the benefit of others coming later, my real problem is solved with Flex Boxes and Vx measurements in conjunction with border-box. IMHO "display: flex;" is the answer to many questions and, although I'm struggling to get it to do what I want, stops you having to work against CSS!
EDIT 2: The following undoubtedly needs refactoring but if you can tell me that it does what I was asking for that'd be great. The change I had to make was to add clientTop in with offsetTop in the equation: -
function resizeContent()
{
var browserHeight = window.outerHeight;
var offesetHeight, offsetWidth;
var viewHeight = window.innerHeight;
var viewWidth = window.innerWidth;
var chromeFootPrint = browserHeight - viewHeight;
var tmpHeight = viewHeight;
if (window.matchMedia("(orientation: portrait)").matches) {
if (viewWidth > viewHeight) {
viewHeight = viewWidth - chromeFootPrint;
viewWidth = tmpHeight + chromeFootPrint;
}
} else {
if (viewWidth < viewHeight) {
viewHeight = viewWidth - chromeFootPrint;
viewWidth = tmpHeight + chromeFootPrint;
}
}
var dimTarget = logScroll;
var offsetTop = dimTarget.offsetTop + dimTarget.clientTop;
var offsetLeft = dimTarget.offsetLeft + dimTarget.clientLeft;
while (dimTarget = dimTarget.offsetParent) {
offsetTop += dimTarget.offsetTop + dimTarget.clientTop;
offsetLeft += dimTarget.offsetLeft + dimTarget.clientLeft;
}
logScrollHeight = viewHeight - (offsetTop + fireBreak);
logScroll.style.height = logScrollHeight + "px";
logScroll.style.width = getStyle(contentDiv).width;
logWindow.style.height = logScroll.style.height;
logWindow.style.width = logScroll.style.width;
logWindow.scrollTop = logWindow.scrollHeight - logScrollHeight;
contentDiv.style.visibility = "visible"; // Now we're presentable
}
and this is the fire-break calculation: -
var outerStyle = getStyle(wholeScreen);
fireBreak = Number(outerStyle.paddingBottom.match("[0-9.]*"))
+ Number(outerStyle.borderBottomWidth.match("[0-9.]*"))
;
resizeContent();
EDIT 1: Ok, let me re-phrase the question: - How to I find out the height of my DIVs content with: -
width: 250px;
border: 3px solid green;
padding: 0.5em;
box-sizing: border-box;
I am currently having to do this: -
logScrollHeight = viewHeight -
(offsetTop + Number(getStyle(headerDiv).paddingBottom.match("[0-9.]*")));
Original question: -
This is bound to be a duplicate but after nearly an hour of looking I have found many similar/identical questions but no real answer :-(
Why aren't the boundryWith and padding deducted from height?
Thankfully the boundryBottomWidth and PaddingBottom return have been converted to pixels (including the "px" string sadly) but doesn't the standard say that the usable height should be returned?
To get the height of an element, you don't use getComputedStyle.
getComputedStyle should only be used to get the parsed values that are currently applied from different style-sheets. In other words, you can see it as a live style-sheet, only targeted to a single element, with standardized units.
But in no way it should be used to get the current height or width of an element. Too many factors may interfere with the set value, and an element can even have an height without having any CSS height rule set.
So yes... when the height CSS rule is set to auto, you will get the computed value, which may coincide with the real height of the element, but it also may not be so.
So in order to get the displayed height of an element, without the border and padding, we will need to do some calculations ourself.
Element#getBoundingClientRect() will give us the real displayed dimensions of our element, transformations included. .offsetHeight will give us the untransformed height including the border-box, and .clientHeight will give us the untransformed height with the padding-box.
This means that we will first have to get all the border and padding computed values, then get the current scale of our element, and finally remove the scaled padding + border boxes from the values we get with getBoundingClientRect.
Here is an example, which will draw a new rectangle div atop the element's bounding-box without padding and border boxes.
let marker;
scale.onclick = e => {
element.classList.toggle('scaled');
drawRect();
}
boxSizing.onclick = e => {
element.classList.toggle('boxSized');
drawRect();
}
function drawRect() {
// remove previous marker if any
if (marker && marker.parentNode) {
marker.remove();
marker = null;
}
// first get the border and padding values
let computed = getComputedStyle(element);
let borderLeft = parseFloat(computed.borderLeftWidth);
let borderWidth = borderLeft + parseFloat(computed.borderRightWidth);
let borderTop = parseFloat(computed.borderTopWidth);
let borderHeight = borderTop + parseFloat(computed.borderBottomWidth);
let paddingLeft = parseFloat(computed.paddingLeft);
let paddingWidth = paddingLeft + parseFloat(computed.paddingRight)
let paddingTop = parseFloat(computed.paddingTop);
let paddingHeight = paddingTop + parseFloat(computed.paddingBottom);
// get the current bounding rect, including the border-box
let rect = element.getBoundingClientRect();
// we need to get the current scale since the computed values don't know about it...
let scale = 1 / (element.offsetHeight / rect.height);
// the real displayed height and width without border nor padding
let height = rect.height - ((borderHeight + paddingHeight) * scale);
let width = rect.width - ((borderWidth + paddingWidth) * scale);
// create our rectangle
marker = document.createElement('div');
marker.classList.add('marker');
marker.style.height = height + 'px';
marker.style.width = width + 'px';
// we need to scale border + padding again
marker.style.top = (rect.top + (borderTop + paddingTop) * scale) + 'px';
marker.style.left = (rect.left + (borderLeft + paddingLeft) * scale) + 'px';
document.body.append(marker);
}
#element {
width: 250px;
border: 0.5em solid green;
padding: 0.5em;
margin-top: 12px;
}
#element.scaled {
transform: scale(2);
transform-origin: top left;
}
#element.boxSized {
box-sizing: border-box;
}
.marker {
position: fixed;
width: 3px;
background: rgba(0, 0, 0, .3)
}
<label>scale
<input id="scale" type="checkbox">
</label>
<label>box-sizing
<input id="boxSizing" type="checkbox">
</label>
<div id="element">
Hello
<br> world
</div>
when you set box-sizing as border-box:
The width and height properties include the content, the padding and border, but not the margin
So when you use getComputedStyle to get a element's height, of course it includes the height of padding and border.
you can have a look at box-sizing property and css box model

Measuring length of string in pixel in javascript

How do I find the length of string in pixels in javascript , if I know the font-size and font-family?
The simplest solution is to create an in memory canvas (i.e. one that isn't added to the DOM) and then use the measureText function :
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
ctx.font = "11px Arial";
var width = ctx.measureText(str).width;
you can put your string into paragraph and use the jquery width function to get the width in pixel width
function showWidth(ele, w) {
$("div").text("The width for the " + ele +
" is " + w + "px.");
}
$("#getp").click(function () {
showWidth("paragraph", $("p").width());
});
check jsfiddle

Write text in random location

I am fairly new to javascript and I am trying to create an interface where multiple users input a string of text into a form, and after submitting, the text appears randomly on the page along with the previous users input. Is there a way to do this with javascript? I am mostly having issues with finding a way to write text to a specific location on the screen.
The idea is that you create a new element each time, and set the following CSS on it so as to be able to position it manually:
position: absolute;
left: _px;
top: _px;
where _ can be generated through Math.random(). This function returns a decimal value between 0 and 1, so stretch and round it appropriately to get random integer pixel coordinates on the screen: http://jsfiddle.net/6eTcD/2/.
​document.querySelector("form").addEventListener("submit", function(e) {
e.preventDefault();
var fullWidth = window.innerWidth;
var fullHeight = window.innerHeight;
var text = this.querySelector("input[type='text']").value;
var elem = document.createElement("div");
elem.textContent = text;
elem.style.position = "absolute";
elem.style.left = Math.round(Math.random() * fullWidth) + "px";
elem.style.top = Math.round(Math.random() * fullHeight) + "px";
document.body.appendChild(elem);
});
You can try a div with absoloute positioning like this:
in your html code:
my text
In you CSS
#mydiv {
position:absolute;
top:0;
right:0;
width:200px;
}
You can position your div with jquery
$('div.example').css('top', 100);
References:
Div Style
jQuery change style

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

Calculate text width with 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

Categories

Resources