Get div's width for p5.js - javascript

I'm learning on my own JavaScript so I'm doing something like a website using p5.js
The thing is that a div holding my canvas for p5.js and I want it to be responsive. In this canvas, I do have an object that needs the div width and height to be constructed.
Problem is that I don't know how to get this information. I tried jQuery but I don't know how to extract value out of a jQuery function and I'm don't know if it's an excessive way to do it.
//--------------------------constant--------------------------------------------
//Canvas
const DROPNUMBER = 1500;
//--------------------------classe---------------------------------------------
function Drop(width, heigth) {
//declaring and setting drop's attribute
this.spawn = function(width) {
//size and position
this.x = Math.random() * -width*1.5;
this.y = Math.random() * heigth;
this.size = Math.random() * 20 ;
//color
this.colorR = 138 + Math.random() * 50;
this.colorV = 43 + Math.random() * 50;
this.colorB = 226 + Math.random() * 50;
this.colorA = Math.random() * 127 +50;
//speed and landing
this.speed = Math.random();
this.hasLanded = false;
}
//call func to set the attribute
this.spawn(width);
//make the drop falls
this.fall = function() {
//if the drop can fall
if (this.x < width) {
this.x = this.x + this.speed;
this.speed = this.speed + 0.01;
//if the drop did land
if (this.y + this.size > width && this.hasLanded == false) {
this.hasLanded = true;
}
}
//if the drop did fall
else {
this.spawn(width);
}
}
//display the drop
this.display = function() {
noStroke();
//Some kind of purple color
fill(this.colorR, this.colorV, this.colorB, this.colorA);
rect(this.x, this.y, this.size, this.size)
}
}
//--------------------------setup---------------------------------------------
function setup() {
clientHeight = document.getElementById('header').clientHeight;
clientWidth = document.getElementById('header').clientWidth;
canvas = createCanvas(clientWidth, clientHeight);
canvas.parent('sketch-holder');
window.canvas = canvas;
}
//-------------------------Variable---------------------------------------------
var n = DROPNUMBER;
var drops = new Array();
//creating an array of drop for the rain
for (let i = 0; i < n; i++) {
//800 800 is height and the width that i want to change !
drops.push(new Drop(800,800));
}
//--------------------------draw------------------------------------------------
function draw() {
background(48, 64, 96);
//each drop
for (let i = 0; i < n; i++) {
//Make them falling
drops[i].fall();
//display the result
drops[i].display();
}
}
The code is just showing that drops ( the object that needs height and width) are being constructed out of draw or setup function.
I did search already on Stack Overflow to this kind of problem too.

use a regular DOM js it's worked:
let b = document.getElementById("parentElement");
let w = b.clientWidth;
let h = b.clientHeight;
console.log(w, h);

You don't need to use JQuery for this. Take a look at the P5.dom library that provides a bunch of functionality for manipulating HTML elements in P5.js.
I'm not sure exactly what you're trying to do, but here's a simple example. If we have a div on the page with and ID of myId, then to get its width we could use P5.dom to do this:
var myDiv = select('myId');
var myWidth = myDiv.style.width;
console.log('width: ' + myWidth);

To get the width of an element in JavaScript, you can select that element using document.querySelector(). Any CSS selector is a valid first argument to that function. E.g. the following will select the <body> tag:
document.querySelector('body')
Once you have an element selected, you can get it's width by accessing the clientWidth property. E.g. the following tells you the width of the <body> tag:
document.querySelector('body').clientWidth
So just replace body with a CSS selector for the element you want to select.

Related

Add class to canvas element HTML5 & CreateJS

I am generating 5 circles with a for loop in a canvas and I want to give them a class so I can control them with jquery, but I am doing something wrong. Can you guys figure out what's happening?
var stage;
var quantity = 6,
width = 60,
height = 60,
circles = [];
function init(){
stage = new createjs.Stage("myCanvas");
stage.width = 500;
stage.height = 600;
createjs.Ticker.setFPS(60);
createjs.Ticker.addEventListener("tick", onTick);
setupGame();
}
function setupGame() {
for(var i = 0; i < quantity; i++) {
var circle = document.createElement("img");
circle.setAttribute('src', 'images/circles/circle'+i+'.png');
circle.className = "circle";
circle.style.position = "absolute";
circle.style.left = Math.floor((Math.random() * 100)) + "%";
circle.style.top = Math.floor((Math.random() * 100)) + "%";
circle.style.width = width + "px";
circle.style.height = height + "px";
document.body.appendChild(circle);
circles.push(circle);
}
}
function onTick(e){
stage.update(e);
}
NEW VERSION. With the help from JonnyD, I now have a functional loop. The only problem is that the images get appended to the body, and not to my stage. I have tried stage.appendChild(circle), but it's not working.
Here is a link to an online source so you guys can check it out = LINK
A lot is wrong with your code.
You are trying to add properties to strings within an array which is not possible. Properties are added to objects using dot or bracket notation..
Dot notation
foo.bar.baz
Square bracket notation
foo['bar']['baz']
What I think you want to do is create five circles on the 'screen' or more technically correct DOM (https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) at random positions with set H&W of 60px with classnames of myClass..
I have rewritten your code for you, you can remove the style javascript lines and add them in the CSS if you wish.. All you were really doing wrong was attempting to add properties to array values, wrong technique for the code and missing off .style before width, height. Note You add className's and width and height attributes to DOM elements only.
You can now access the individual circles through a for loop and the circles array or by using the nth-child selector with CSS. e.g .circle:nth-child(1) {animation/transition}
var quantity = 5,
width = 60,
height = 60
circles = [];
function setUp() {
for(var i = 0; i < quantity; i++) {
var circle = document.createElement("div");
circle.className = "circle";
circle.style.position = "absolute";
circle.style.left = Math.floor((Math.random() * 100)) + "%";
circle.style.top = Math.floor((Math.random() * 100)) + "%";
circle.style.backgroundColor = "black";
circle.style.width = width + "px";
circle.style.height = height + "px";
circles.push(circle);
document.body.appendChild(circle);
}
}
setUp();
.circle {
border-radius: 50%;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
}
I didn't see you were using CreateJS.. in that case using the notation like so is okay..
var circle = new createjs.Shape();
circle.graphics.beginFill("DeepSkyBlue").drawCircle(0, 0, 50);
circle.x = 100;
circle.y = 100;
stage.addChild(circle);
ensure that you update the stage as well.
stage.update();
I realize this question has been answered, but since I clicked on this for trying to find out how to add a class to a canvas object in jquery, I'll post how to do that.
var thing = canvas_object;
$('body').append(thing);
var canvas = $('canvas');
canvas.addClass('test');
Things inside canvas are not in DOM, but elements in Scalable Vector Graphics images are, and can be manipulated this way.
Try using SVG if convenient. svg.js is a lightweight library to manipulate SVG.

Bad zooming with pixi.js / WebGL

If you put some text on a pixi.js / WebGL canvas, and zoom on it, here is what happens : http://jsbin.com/qeqoneselelo/1/.
The result is bad : blurred / pixelized like if we were zooming on a bitmap.
Instead I would like to be able to zoom on this text, as if it was vector graphics (text actually is !), ie no blur at all, like here for example (you can zoom infinitely many times, no blur !) : http://s419743653.onlinehome.fr/things/test2.htm
How to do a proper zooming on text with pixi.js ? (or, if impossible with pixi.js, with another WebGL canvas javascript toolkit ?)
Here is the code I used (available only on http://jsbin.com/qeqoneselelo/1/) :
var text = new PIXI.Text("Hello World", {font:"50px Arial", fill:"black"});
var scrollArea = new PIXI.DisplayObjectContainer();
scrollArea.scale.x = 10;
scrollArea.scale.y = 10;
scrollArea.addChild(text);
stage.addChild(scrollArea);
As far as I know there is no way.
That's the whole point of pixi.js. It gets its speed by using bitmap sprites. The consequence of which you get the effect you see when you scale but you get super speed.
If you want smooth text you don't need pixi.js. Just you use the canvas API. Of course you'll give up some of the speed and other features of pixi.js but you'll get smooth text.
If you want to keep using pixi.js, one solution is to use LODs. Make multiple sprites with progressively larger text on them and as you zoom in use a progressively larger sprite with higher res text but with its individual scale so it keeps the same size. Unfortunately because font sizes are slightly unpredictable getting the sprites to transition flawlessly might require some trial and error.
var texts = [];
for (var ii = 0; ii < 15; ++ii) {
var text = new PIXI.Text("Hello World", {font: (ii * 10) +"px Arial", fill:"black"});
text.scale.x = 1 / (1 + ii);
text.scale.y = 1 / (1 + ii);
texts.push(text);
}
...
text = undefined;
function animate() {
var t = Date.now() * 0.001;
var scale = 1 + 14 * (Math.sin(t) * 0.5 + 0.5);
if (text) {
scrollArea.removeChild(text);
}
text = texts[Math.floor(scale)];
scrollArea.addChild(text);
scrollArea.scale.x = scale;
scrollArea.scale.y = scale;
renderer.render(stage);
requestAnimFrame(animate);
}
Here's an example
var stage = new PIXI.Stage(0xFFFFFF);
var renderer = PIXI.autoDetectRenderer(800, 600);
document.body.appendChild(renderer.view);
var texts = [];
for (var ii = 0; ii < 15; ++ii) {
var text = new PIXI.Text("Hello World", {font: (ii * 10) +"px Arial", fill:"black"});
text.scale.x = 1 / (1 + ii);
text.scale.y = 1 / (1 + ii);
texts.push(text);
}
var scrollArea = new PIXI.DisplayObjectContainer();
scrollArea.interactive = true;
scrollArea.buttonMode = true;
// scrollArea.addChild(text);
stage.addChild(scrollArea);
scrollArea.mousedown = function(data) {
data.originalEvent.preventDefault();
this.data = data;
this.dragging = true;
}
scrollArea.mouseup = scrollArea.mouseupoutside = function(data) {
this.dragging = false;
this.data = null;
}
scrollArea.mousemove = function(data) {
if (this.dragging) {
var newPos = this.data.getLocalPosition(this.parent);
this.position.x = newPos.x;
this.position.y = newPos.y;
}
}
text = undefined;
function animate() {
var t = Date.now() * 0.001;
var scale = 1 + 14 * (Math.sin(t) * 0.5 + 0.5);
if (text) {
scrollArea.removeChild(text);
}
text = texts[Math.floor(scale)];
scrollArea.addChild(text);
scrollArea.scale.x = scale;
scrollArea.scale.y = scale;
renderer.render(stage);
requestAnimFrame(animate);
}
animate();
<script src="//cdnjs.cloudflare.com/ajax/libs/pixi.js/1.6.1/pixi.js"></script>
Override Pixi's default renderer resolution of the Text instance by setting the resolution property:
var text = new PIXI.Text("Hello World", {font:"50px Arial", fill:"black"});
text.resolution = 8;

bounce check function for HTML5 canvas fails at corners

I have a HTML5 canvas that generates a bouncing box every time you click on it. The box array stores the x-value, y-value, x-velocity, and y-velocity of each box created. The box will travel in a random direction at first and will bounce of the sides of the canvas but if it hits a corner the box dissappears instead of bouncing back. EDIT: I answered my own question noticing that the soundY and soundX functions were causing the problem.
var box = new Array();
var width = window.innerWidth;
var height = window.innerHeight;
var field = document.getElementById('canvas');
field.width = width;
field.height = height;
field.ctx = field.getContext('2d');
field.ctx.strokeStyle = 'rgba(255,255,255,1)';
setInterval('redraw()', 200);
addEventListener('click', createBox, false);
function createBox(e) { // this box will always fail collision detection at the upper-left corner
box.push(100); // x-value is normally mouse position
box.push(100); // y-value is normally mouse position
box.push(-5); // x-speed is normally random
box.push(-5); // y-speed is normally random
}
function redraw() {
field.ctx.clearRect(0,0,width,height);
for(var i = 0; i < box.length; i+=4) {
if(box[i] < 0) { box[i+2] *= -1; soundY(box[i+1]); } // parameter of soundY is less than 0
else if(box[i] > width) { box[i+2] *= -1; soundY(box[i+1]); } // which is invalid and causes this to break
if(box[i+1] < 0) { box[i+3] *= -1; soundX(box[i]); }
else if(box[i+1] > height) { box[i+3] *= -1; soundX(box[i]); }
box[i] += box[i+2];
box[i+1] += box[i+3];
field.ctx.strokeRect(box[i], box[i+1], 4, 4);
}
}
function soundX(num) {
// play a sound file based on a number between 0 and width
}
function soundY(num) {
// play a sound file based on a number between 0 and height
}
The only way I could recreate the problem was by generating the box in one of the corners so that with the right x and y velocity the box was initially created outside the bounds of the canvas. When that happens, the inversion of the velocity isn't enough to bring the item back in bounds and so on the next frame the velocity is inverted again (and so on).
I think this might solve your problem:
var boxes = [];
var boxSize = 4;
var width = window.innerWidth;
var height = window.innerHeight;
var field = document.getElementById('canvas');
function redraw() {
field.ctx.clearRect(0, 0, width, height);
var box;
for (var i = 0; i < boxes.length; i++) {
box = boxes[i];
field.ctx.strokeRect(box.x, box.y, boxSize, boxSize);
if (box.x < 0) {
box.x = 0;
box.dx *= -1;
} else if (box.x > width - boxSize) {
box.x = width - boxSize;
box.dx *= -1;
}
if (box.y < 0) {
box.y = 0;
box.dy *= -1;
} else if (box.y > height - boxSize) {
box.y = height - boxSize;
box.dy *= -1;
}
box.x += box.dx;
box.y += box.dy;
}
}
field.width = width;
field.height = height;
field.ctx = field.getContext('2d');
field.ctx.strokeStyle = 'rgba(0,0,0,1)';
setInterval(redraw, 200);
addEventListener('click', createBox, false);
function createBox(e) {
boxes.push({
x: e.clientX - 10,
y: e.clientY - 10, // arbitrary offset to place the new box under the mouse
dx: Math.floor(Math.random() * 8 - boxSize),
dy: Math.floor(Math.random() * 8 - boxSize)
});
}
I fixed a few errors in your code and made some changes to make it a bit more readable (I hope). Most importantly, I extended your collision detection so that it resets the coordinates of the box to the bounds of your canvas should the velocity take it outside.
Created a jsfiddle which might be handy if further discussion is needed.
It was additional code (see edit) that I left out assuming it was unrelated to the issue, but removing the code solved the problem as it appears this use-case would cause an invalid input in this part of the code.

Creating Mouse Event Handlers For Canvas Shapes

I'm coding a tile based game in javascript using canvas and was wondering how I could create a simple event handler for when the mouse enters the dimensions of a tile.
I've used jquery's http://api.jquery.com/mousemove/ in the past but for a very simple application but can't seem to wrap my head around how I'll do it in this case (quickly).
Hmm..
I started writing this post without a clue of how to do it, but I just tried using the jquery mousemove like I started above. I have a working version, but it seems 'slow' and very clunky. It's doesn't seem smooth or accurate.
I put all mode code into a js fiddle to share easily:
http://jsfiddle.net/Robodude/6bS6r/1/
so what's happening is:
1) jquery's mousemove event handler fires
2) Sends the mouse object info to the GameBoard
3) Sends the mouse object info to the Map
4) Loops through all the tiles and sends each one the mouse object
5) the individual tile then determines if the mouse coords are within its boundaries. (and does something - in this case, I just change the tiles properties to white)
but here are the sections I'm most concerned about.
$("#canvas").mousemove(function (e) {
mouse.X = e.pageX;
mouse.Y = e.pageY;
game.MouseMove(mouse);
Draw();
});
function GameBoard() {
this.Map = new Map();
this.Units = new Units();
this.MouseMove = function (Mouse) {
this.Map.MouseMove(Mouse);
};
}
function Map() {
this.LevelData = Level_1(); // array
this.Level = [];
this.BuildLevel = function () {
var t = new Tile();
for (var i = 0; i < this.LevelData.length; i++) {
this.Level.push([]);
for (var a = 0; a < this.LevelData[i].length; a++) {
var terrain;
if (this.LevelData[i][a] == "w") {
terrain = new Water({ X: a * t.Width, Y: i * t.Height });
}
else if (this.LevelData[i][a] == "g") {
terrain = new Grass({ X: a * t.Width, Y: i * t.Height });
}
this.Level[i].push(terrain);
}
}
};
this.Draw = function () {
for (var i = 0; i < this.Level.length; i++) {
for (var a = 0; a < this.Level[i].length; a++) {
this.Level[i][a].Draw();
}
}
};
this.MouseMove = function (Mouse) {
for (var i = 0; i < this.Level.length; i++) {
for (var a = 0; a < this.Level[i].length; a++) {
this.Level[i][a].MouseMove(Mouse);
}
}
};
this.BuildLevel();
}
function Tile(obj) {
//defaults
var X = 0;
var Y = 0;
var Height = 40;
var Width = 40;
var Image = "Placeholder.png";
var Red = 0;
var Green = 0;
var Blue = 0;
var Opacity = 1;
// ...
this.Draw = function () {
ctx.fillStyle = "rgba(" + this.Red + "," + this.Green + "," + this.Blue + "," + this.Opacity + ")";
ctx.fillRect(this.X, this.Y, this.Width, this.Height);
};
this.MouseMove = function (Mouse) {
if ((Mouse.X >= this.X) && (Mouse.X <= this.Xmax) && (Mouse.Y >= this.Y) && (Mouse.Y <= this.Ymax)) {
this.Red = 255;
this.Green = 255;
this.Blue = 255;
}
};
}
If you have a grid of tiles, then given a mouse position, you can retrieve the X and Y index of the tile by dividing the X mouse position by the width of a tile and Y position with the height and flooring both.
That would make Map's MouseMove:
this.MouseMove = function (Mouse) {
var t = new Tile();
var tileX = Math.floor(mouse.X / t.Width);
var tileY = Math.floor(mouse.Y / t.Height);
this.Level[tileY][tileX].MouseMove(Mouse);
};
Edit: You asked for some general suggestions. Here you go:
It's more common to use initial uppercase letters for only classes in JavaScript.
Mouse is a simple structure; I don't think it needs to have its own class. Perhaps use object literals. (like {x: 1, y: 2})
You may want to use JavaScript's prototype objects, rather than using this.method = function() { ... } for every method. This may increase performance, since it only has to create the functions once, and not whenever a new object of that class is made.

How can you find the height of text on an HTML canvas?

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

Categories