randomly mapping divs - javascript

I am creating a new "whack-a-mole" style game where the children have to hit the correct numbers in accordance to the question. So far it is going really well, I have a timer, count the right and wrong answers and when the game is started I have a number of divs called "characters" that appear in the container randomly at set times.
The problem I am having is that because it is completely random, sometimes the "characters" appear overlapped with one another. Is there a way to organize them so that they appear in set places in the container and don't overlap when they appear.
Here I have the code that maps the divs to the container..
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function scramble() {
var children = $('#container').children();
var randomId = randomFromTo(1, children.length);
moveRandom('char' + randomId);
}
function moveRandom(id) {
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var pad = parseInt($('#container').css('padding-top').replace('px', ''));
var bHeight = $('#' + id).height();
var bWidth = $('#' + id).width();
maxY = cPos.top + cHeight - bHeight - pad;
maxX = cPos.left + cWidth - bWidth - pad;
minY = cPos.top + pad;
minX = cPos.left + pad;
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX);
$('#' + id).css({
top: newY,
left: newX
}).fadeIn(100, function () {
setTimeout(function () {
$('#' + id).fadeOut(100);
window.cont++;
}, 1000);
});
I have a fiddle if it helps.. http://jsfiddle.net/pUwKb/8/

As #aug suggests, you should know where you cannot place things at draw-time, and only place them at valid positions. The easiest way to do this is to keep currently-occupied positions handy to check them against proposed locations.
I suggest something like
// locations of current divs; elements like {x: 10, y: 40}
var boxes = [];
// p point; b box top-left corner; w and h width and height
function inside(p, w, h, b) {
return (p.x >= b.x) && (p.y >= b.y) && (p.x < b.x + w) && (p.y < b.y + h);
}
// a and b box top-left corners; w and h width and height; m is margin
function overlaps(a, b, w, h, m) {
var corners = [a, {x:a.x+w, y:a.y}, {x:a.x, y:a.y+h}, {x:a.x+w, y:a.y+h}];
var bWithMargins = {x:b.x-m, y:b.y-m};
for (var i=0; i<corners.length; i++) {
if (inside(corners[i], bWithMargins, w+2*m, h+2*m) return true;
}
return false;
}
// when placing a new piece
var box;
while (box === undefined) {
box = createRandomPosition(); // returns something like {x: 15, y: 92}
for (var i=0; i<boxes.length; i++) {
if (overlaps(box, boxes[i], boxwidth, boxheight, margin)) {
box = undefined;
break;
}
}
}
boxes.push(box);
Warning: untested code, beware the typos.

The basic idea you will have to implement is that when a random coordinate is chosen, theoretically you SHOULD know the boundaries of what is not permissible and your program should know not to choose those places (whether you find an algorithm or way of simply disregarding those ranges or your program constantly checks to make sure that the number chosen isn't within the boundary is up to you. the latter is easier to implement but is a bad way of going about it simply because you are entirely relying on chance).
Let's say for example coordinate 50, 70 is selected. If the picture is 50x50 in size, the range of what is allowed would exclude not only the dimensions of the picture, but also 50px in all directions of the picture so that no overlap may occur.
Hope this helps. If I have time, I might try to code an example but I hope this answers the conceptual aspect of the question if that is what you were having trouble with.
Oh and btw forgot to say really great job on this program. It looks awesome :)

You can approach this problem in at least two ways (these two are popped up in my head).
How about to create a 2 dimensional grid segmentation based on the number of questions, the sizes of the question panel and an array holding the position of each question coordinates and then on each time frame to position randomly these panels on one of the allowed coordinates.
Note: read this article for further information: http://eloquentjavascript.net/chapter8.html
The second approach follow the same principle, but this time to check if the panel overlap the existing panel before you place it on the canvas.
var _grids;
var GRID_SIZE = 20 //a constant holding the panel size;
function createGrids() {
_grids = new Array();
for (var i = 0; i< stage.stageWidth / GRID_SIZE; i++) {
_grids[i] = new Array();
for (var j = 0; j< stage.stageHeight / GRID_SIZE; j++) {
_grids[i][j] = new Array();
}
}
}
Then on a separate function to create the collision check. I've created a gist for collision check in Actionscript, but you can use the same principle in Javascript too. I've created this gist for inspirational purposes.

Just use a random number which is based on the width of your board and then modulo with the height...
You get a cell which is where you can put the mole.
For the positions the x and y should never change as you have 9 spots lets say where the mole could pop up.
x x x
x x x
x x x
Each cell would be sized based on % rather then pixels and would allow re sizing the screen
1%3 = 1 (x)
3%3 = 0 (y)
Then no overlap is possible.
Once the mole is positioned it can be show or hidden or moved etc based on some extended logic if required.
If want to keep things your way and you just need a quick re-position algorithm... just set the NE to the SW if the X + width >= x of the character you want to check by setting the x = y+height of the item which overlaps. You could also enforce that logic in the drawing routine by caching the last x and ensuring the random number was not < last + width of the item.
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX); if(newX > lastX + characterWidth){ /*needful*/}
There could still however be overlap...
If you wanted to totally eliminate it you would need to keep track of state such as where each x was and then iterate that list to find a new position or position them first and then all them to move about randomly without intersecting which would would be able to control with just padding from that point.
Overall I think it would be easier to just keep X starting at 0 and then and then increment until you are at a X + character width > greater then the width of the board. Then just increase Y by character height and Set X = 0 or character width or some other offset.
newX = 0; newX += characterWidth; if(newX + chracterWidth > boardWidth) newX=0; newY+= characterHeight;
That results in no overlap and having nothing to iterate or keep track of additional to what you do now, the only downside is the pattern of the displayed characters being 'checker board style' or right next to each other (with possible random spacing in between horizontal and vertical placement e.g. you could adjust the padding randomly if you wanted too)
It's the whole random thing in the first place that adds the complexity.
AND I updated your fiddle to prove I eliminated the random and stopped the overlap :)
http://jsfiddle.net/pUwKb/51/

Related

How do you avoid the "RangeError: Maximum call stack size exceeded" error?

I'm currently working on a maze generating algorithm called recursive division. The algorithm is quite simple to understand: Step 1: if the height of your chamber is smaller than the width, divide your grid/chamber with a vertical line. If the height is greater than the width, then divide your chamber with a horizontal line. Step 2: Repeat step 1 with the sub-chambers that were created by the lines. You want to repeat these steps until you get a maze (until the width or height equals 1 unit).
The problem that I have with this algorithm is that JavaScript prints out a RangeError, meaning that I called the function that creates the maze too many times (I'm trying to implement this algorithm with a recursive function). Is there any way to avoid/prevent this from happening? Or am I missing something important in my code that makes the algorithm not work properly?
I have tried to implement a trampoline function, but since I'm a beginner I just don't understand it well enough to implement my self. I have also restarted my entire project ruffly 3 times with some hope that I will come up with a different approach to this problem, but I get the same error every time.
My code here:
//leftCord = the left most x coordinate of my chamber/grid, upCord = the upmost y coordinate of my
grid etc.
//(0, 0) IS POSITIONED IN THE LEFT TOP NODE OF MY GRID
function createMaze(leftCord, rightCord, upCord, downCord) {
var height = Math.abs(downCord - upCord);
var width = Math.abs(rightCord - leftCord);
if (height < 2 || width < 2) {
//The maze is completed!
return;
} else {
if (height < width) {
//cut the chamber/grid vertically
//Getting a random number that's EVEN and drawing the function x = 'random number' on the grid
var x = randomNum(leftCord / 2, rightCord / 2) * 2;
var lineX = [];
for (i = upCord; i < downCord; i++) {
lineX.push(grid[i][x]);
}
//Making a random door/passage and making sure it's ODD
var randomDoor = randomNum(0, lineX.length / 2) * 2 + 1;
lineX.splice(randomDoor, 1);
//Drawing the line
for (i = 0; i < lineX.length; i++) {
lineX[i].className = "wall";
}
//Making the same thing again, but with the left and right sub-chambers that were created by the line
createMaze(leftCord, x, upCord, downCord);
createMaze(x, rightCord, upCord, downCord);
} else {
//cut the chamber/grid horizontally
//Getting a random number that's EVEN and drawing the function y = 'random number' on the grid
var y = randomNum(0, downCord / 2) * 2;
var lineY = [];
for (i = leftCord; i < rightCord; i++) {
lineY.push(grid[y][i]);
}
//Making a random door/passage and making sure it's ODD
var randomDoor = randomNum(0, lineY.length / 2) * 2 + 1;
lineY.splice(randomDoor, 1);
//Drawing the line
for(i = 0; i < lineY.length; i++){
lineY[i].className = "wall";
}
//Making the same thing again, but with the upper and lower-chambers that were created by the line
createMaze(leftCord, rightCord, upCord, y);
createMaze(leftCord, rightCord, y, downCord);
}
}
}
This is happening because you never initialize i with var- it is sent into the global scope and is overwritten each function call.

javascript game sprite positioning

I'm trying to create a chess board, and place it in the middle of the screen, so far i cannot get it to be directly in the center. i don't want to hard code the position to the screen because i'm going to be dealing with different screen sizes.
var winsize = cc.director.getWinSize();
var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
for (i=0; i<64; i++){
var tile = cc.Sprite.create(res.tile_png);
this.addChild(tile,0);
tile.setPosition(winsize.width+i%8*50/-10, winsize.height-Math.floor(i/8)*50);
}
But the tiles and positioning is completely off
#jumpman8947, if you're using Cocos2d js perhaps you have a similar line: cc.view.setDesignResolutionSize(480, 320, cc.ResolutionPolicy.SHOW_ALL);
In this particular case the game will scale to any sceeen, but still run in 480x320 resolution, so no matter what screen resoultion you use, the center in the cocos world would always be cc.p(240, 160) so no matter what's the window size or the screen resolution, the resolution of the game stays the same
You can read more about resolution policies here (and in official js-doc):
http://www.cocos2d-x.org/wiki/Multiple_Resolution_Policy_for_Cocos2d-JS
Also please be aware, that the Sprite position in Cocos is the position of the centre of the sprite, not bottom left corner
In your question it's not completely clear exactly what you want. However, I made some assumptions. The explanation for my solution is embedded in the comments in the code below.
// var winsize = cc.director.getWinSize();
// Here is some example hard-coded return values:
var winsize = {width: 600, height: 400};
// You can change these numbers to see how they influence
// the outcome.
// var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
// This line doesn't seem relevant for the question you asked.
// Or, rather, the following calculations will result in the tiles
// being centred on the screen anyway, so this calculation here
// is unnecessary.
// Being a chess board, I assume that you want the tiles to be square,
// i.e. to have the same width and height.
// If so, first find out which is the minimum dimension
// and calculate the tile size as being 1/8 of that.
var minDimn = Math.min(winsize.width, winsize.height);
var tileSize = minDimn / 8;
// Find out how far in from the left and how far down from the top
// you need the upper left corner of the upper left tile to start.
// This assumes that you don't need any "margin" around the board.
// (If you do need such a "margin", basically subtract it twice
// from each of winsize.width and winsize.height above.)
// Start with default values of 0 for each, but then add in the
// excess for the longer dimension, but divide it by two
// because that excess will be split between either
// the top and bottom or the left and right.
var xStart = 0, yStart = 0;
if (winsize.width > winsize.height) {
xStart = (winsize.width - winsize.height) / 2;
} else if (winsize.height > winsize.width) {
yStart = (winsize.height - winsize.width) / 2;
}
// Instead of looping through all 64 positions in one loop,
// loop through all the horizontal positions in an outer loop
// and all the vertical positions in an inner loop.
for (i = 0; i < 8; i++) {
// For the horizontal dimension, calculate x for each tile
// as the starting position of the left-most tile plus
// the width of the tile multiplied by the number of tiles (0-based)
var x = xStart + i * tileSize;
// Now the inner loop
for (j = 0; j < 8; j++) {
// Same type of calculation for the y value.
var y = yStart + j * tileSize;
// You can see the values in this demo here.
document.write("<pre>(" + x + ", " + y + ")</pre>");
// The following two lines don't seem to be relevant to the question.
// var tile = cc.Sprite.create(res.tile_png);
// this.addChild(tile,0);
// Use your calculated values in your function call.
// tile.setPosition(x, y);
}
}

Determining the of grid used by circle

I would like to determine the proportion of a grid cell occupied by one (or more) circles. So, for example, the top left grid cell below would have a small value (~0.1) and the center grid cell (7,7) would have a value of 1, as it is entirely occupied by the circle.
At present I am doing this with canvas.context2d.getImageData, by sampling the cell's content to determine what is present. This works but is way too slow. This is this method:
var boxRadius = 6;
var boxSize = boxRadius * 2 + 1;
var cellWidth = gridWidth / boxSize;
var cellHeight = gridHeight / boxSize;
var scanInterval = 10;
var scanCount = 10;
for (var x = viewcenterpoint.x - (gridWidth / 2); x <= viewcenterpoint.x + (gridWidth / 2) -1; x += cellWidth) {
for (var y = viewcenterpoint.y - (gridHeight / 2) ; y <= viewcenterpoint.y + (gridHeight / 2) -1; y += cellHeight) {
var cellthreatlevel = 0.0;
for (var cellx = x; cellx < x + cellWidth; cellx += scanInterval){
for (var celly = y; celly < y + cellHeight; celly += scanInterval){
var pixeldata = context.getImageData(cellx, celly, 1, 1).data;
cellthreatlevel += ((pixeldata[0] + pixeldata[1] + pixeldata[2])/765 * -1) + 1;//255; //grey tone
scancount += 1;
}
}
cellthreatlevel = cellthreatlevel / scanCount; //mean
}
}
The getImageData call is the source of the problem - it is way too slow.
Given that I have an array of circles, each with their x, y and radius how can I calculate this? If possible I would like each value to be a decimal fraction (between 0 and 1).
The grid is static, but the circles may move within it. I would be happy to get a rough estimate for the value, it doesnt need to be 100% accurate.
You can use the Monte Carlo Method to get an approximate solution. It is a probability based method, in which you generate random samples in order to estimate some value. In this case, given the coordinates of the circle center, the circle radius and the boundaries of the grid cell, you can estimate the proportion of the grid cell occupied by the circle by generating K random samples (all contained inside the grid cell), and verify the proportion of the samples that are also inside the circle. The more samples you generate, the more accurate your result will be.
Remember: to verify if a given sample P is inside a circle with center C and radius R, all you have to do is check if the equation sqrt((Px-Cx)^2 + (Py-Cy)^2) <= R is true
You only need to call getImageData once, to obtain the entire canvas.
Once you have the image data you can access the bytes at offset 4 * (celly * width + cellx) to get the RGB(A) data.
This should be massively faster since it only makes one call to the graphics hardware instead of 10s of thousands.

How do I determine a movable area in game squares

I'm building a turn based HTML game based on a 2D square grid. Each grid square could take a variable number of movement points to cross (IE: 1 MP for roads, 1.5 MP for grasslands, 2 MP for forests, etc). When the user clicks on a unit I want to determine all possible movable spaces with said unit's allotted movement points so that I can highlight them and make them clickable.
Is there a free library available to do this? I've seen a few pathing algorithms but nothing about determining movable area. How do other game developers handle this problem? I'm open to both vanilla JS and JQuery solutions.
Well, I decided to try and attack this myself. I've never been great at these sorts of algorithms so I'm sure there's a more efficient way to handle it than what I've done. However, for my purposes it runs quickly enough and is very simple and easy to understand.
In case it's helpful to anyone else looking to do the same, I've included the code below. This is an updated version of my original answer, which I modified to also store the path taken so that you can show the units moving through the correct spaces. This answer uses JQuery in the lower examples, but only in a few places; you can easily enough replace them with vanilla JS. And the first block of code, containing the actual path/area finding functionality, is pure JS.
<script>
var possibleMovementAreaArray = new Array(); // This array will hold our allowable movement tiles. Your other functions can access this after running possibleMovementArea().
function possibleMovementArea(unitIndex) {
// I'm storing each unit in my game in an array. So I pass in the index of the unit I want to determine the movement area for.
var x = unitList[unitIndex][10]; // x coordinate on the playgrid
var y = unitList[unitIndex][11]; // y coordinate on the playgrid
var mp = unitList[unitIndex][15]; // number of movement points
possibleMovementAreaArray.length = 0; // Clear our array so previous runs don't interfere.
findPossibleMovement(x, y, mp);
}
function findPossibleMovement(x, y, mp, prevStepX, prevStepY) {
// This is a recursive function; something I'm not normally too good at.
for (var d=1; d<=4; d++) {
// We run through each of the four cardinal directions. Bump this to 8 and add 4 more cases to include corners.
if (d == 1) {
// Check Up
var newX = x;
var newY = y - 1;
} else if (d == 2) {
// Check Down
var newX = x;
var newY = y + 1;
} else if (d == 3) {
// Check Left
var newX = x - 1;
var newY = y;
} else if (d == 4) {
// Check Right
var newX = x + 1;
var newY = y;
}
// Check to see if this square is occupied by another unit. Two units cannot occupy the same space.
spaceOccupied = false;
for (var j=1; j<=numUnits; j++) {
if (unitList[j][10] == newX && unitList[j][11] == newY)
spaceOccupied = true;
}
if (!spaceOccupied) {
// Modify this for loop as needed for your usage. I have a 2D array called mainMap that holds the ID of a type of terrain for each tile.
// I then have an array called terList that holds all the details for each type of terrain, such as movement points needed to get past.
// This for loop is just looking up the ID of the terrain for use later. Sort of like a "SELECT * FROM terrainInfo WHERE ID=terrainOfCurrentTile".
for (var j=1; j<=numTerrains; j++) {
if (newX > 0 && newX <= mapWidth && newY > 0 && newY <= mapHeight && terList[j][1] == mainMap[newX][newY])
break; // After finding the index of terList break out of the loop so j represents the correct index.
}
if (j <= numTerrains) { // Run if an actual terrain is found. No terrain is found if the search runs off the sides of the map.
var newMp = mp - terList[j][7]; // Decrement the movement points for this particular path.
if (newMp >= 0) { // Only continue if there were enough movement points to move to this square.
// Check to see if this square is already logged. For both efficiency and simplicity we only want each square logged once.
var newIndex = possibleMovementAreaArray.length
var alreadyLogged = false
if (possibleMovementAreaArray.length > 0) {
for (var j=0; j<possibleMovementAreaArray.length; j++) {
if (possibleMovementAreaArray[j][1] == newX && possibleMovementAreaArray[j][2] == newY) {
alreadyLogged = true;
var alreadyLoggedIndex = j;
}
}
}
if (!alreadyLogged) {
// This adds a row to the array and records the x and y coordinates of this tile as movable
possibleMovementAreaArray[newIndex] = new Array(6);
possibleMovementAreaArray[newIndex][1] = newX;
possibleMovementAreaArray[newIndex][2] = newY;
possibleMovementAreaArray[newIndex][3] = prevStepX; // This tracks the x coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][4] = prevStepY; // This tracks the y coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][5] = newMp; // Records remaining MP after the previous steps have been taken.
}
if (alreadyLogged && newMp > possibleMovementAreaArray[alreadyLoggedIndex][5]) {
// If this tile was already logged, but there was less MP remaining on that attempt, then this one is more efficient. Update the old path with this one.
possibleMovementAreaArray[alreadyLoggedIndex][3] = prevStepX;
possibleMovementAreaArray[alreadyLoggedIndex][4] = prevStepY;
possibleMovementAreaArray[alreadyLoggedIndex][5] = newMp;
}
if (newMp > 0) {
// Now update the list of previous steps to include this tile. This list will be passed along to the next call of this function, thus building a path.
if (prevStepX == '') {
var newPrevStepX = [newX];
var newPrevStepY = [newY];
} else {
// This code is required to make a full copy of the array holding the existing list of steps. If you use a simple equals then you just create a reference and
// subsequent calls are all updating the same array which creates a chaotic mess. This way we store a separate array for each possible path.
var newPrevStepX = prevStepX.slice();
newPrevStepX.push(newX);
var newPrevStepY = prevStepY.slice();
newPrevStepY.push(newY);
}
// If there are still movement points remaining, check and see where we could move next.
findPossibleMovement(newX, newY, newMp, newPrevStepX, newPrevStepY);
}
}
}
}
}
}
</script>
After running the above, you can then loop through the array to find all usable tiles. Here is how I did it:
<script>
// Shows the movement area based on the currently selected unit.
function showMovement() {
var newHTML = "";
curAction = "move";
possibleMovementArea(curUnit); // See above code
for (x=0; x<possibleMovementAreaArray.length; x++) {
// Loop over the array and do something with each tile. In this case I'm creating an overlay that I'll fade in and out.
var tileLeft = (possibleMovementAreaArray[x][1] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
var tileTop = (possibleMovementAreaArray[x][2] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
newHTML = newHTML + "<img id='path_" + possibleMovementAreaArray[x][1] + "_" + possibleMovementAreaArray[x][2] + "' onClick='mapClk(" + possibleMovementAreaArray[x][1] + ", " + possibleMovementAreaArray[x][2] + ", 0);' src='imgs/path.png' class='mapTile' style='left:" + tileLeft + "px; top:" + tileTop + "px;'>";
}
$("#movementDiv").html(newHTML); // Add all those images into a preexisting div.
$("#movementDiv").css("opacity", "0.5"); // Fade the div to 50%
$("#movementDiv").show(); // Make the div visible.
startFading(); // Run a routine to fade the div in and out.
}
</script>
Since we determined the path, we can easily show movement as well by looping through the stored information:
<script>
for (j=0; j<possibleMovementAreaArray[areaIndex][3].length; j++) {
// This loop moves the unit img to each tile on its way to its destination. The final destination tile is not included.
var animSpeed = 150; // Time in ms that it takes to move each square.
var animEase = "linear"; // We want movement to remain a constant speed through each square in this case.
var targetLeft = (possibleMovementAreaArray[areaIndex][3][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new horizonal position.
var targetTop = (possibleMovementAreaArray[areaIndex][4][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new vertical position.
$("#char_"+curUnit).animate({"left":targetLeft, "top":targetTop}, animSpeed, animEase); // Do the animation. Subsequent animations get queued.
}
// Now we need to move to that last tile.
newLeft = (x-1) * mapTileSize;
newTop = (y-1) * mapTileSize;
$("#char_"+curUnit).animate({"left":newLeft, "top":newTop}, 400, "easeOutCubic"); // Slow unit at the end of journey for aesthetic purposes.
$("#char_"+curUnit).addClass("unitMoved", 250); // Turns the image grayscale so it can easily be seen that it has already moved.
</script>
Hopefully this is helpful to others.

Calculate letter size in javascript

I need to calculate the exact size of the letter in javascript. The letter can have different font-size or font-family attributes, etc.
I tried to use div element for this, but this method gives only the size of the div, not letter.
<div style="display: inline; background-color: yellow; font-size: 53px; line-height:32px">A</div>
Does anybody know how to solve this issue?
This is basically not possible for the general case. Font kerning will result in variable "widths" of letters depending on the operating system, browser, etc etc, and based on which letters are next to each other. Font substitution may happen if the os+browser don't have the font you specify.
Perhaps re-asking the question with the higher-level goal you're shooting for might result in proposed other approaches to your problem that might be more fruitful?
As others have mentioned, this isn't possible to measure directly. But you can get at it in a more roundabout way: draw the letter onto a canvas and determine which pixels are filled in.
Here's a demo that does this. The meat is this function:
/**
* Draws a letter in the given font to an off-screen canvas and returns its
* size as a {w, h, descenderH} object.
* Results are cached.
*/
function measureLetter(letter, fontStyle) {
var cacheKey = letter + ' ' + fontStyle;
var cache = measureLetter.cache;
if (!cache) {
measureLetter.cache = cache = {};
}
var v = cache[cacheKey];
if (v) return v;
// Create a reasonably large off-screen <canvas>
var cnv = document.createElement('canvas');
cnv.width = '200';
cnv.height = '200';
// Draw the letter
var ctx = cnv.getContext('2d');
ctx.fillStyle = 'black';
ctx.font = fontStyle;
ctx.fillText(letter, 0.5, 100.5);
// See which pixels have been filled
var px = ctx.getImageData(0, 0, 200, 200).data;
var minX = 200, minY = 200, maxX = 0, maxY = 0;
var nonZero = 0;
for (var x = 0; x < 200; x++) {
for (var y = 0; y < 200; y++) {
var i = 4 * (x + 200 * y);
var c = px[i] + px[i + 1] + px[i + 2] + px[i + 3];
if (c === 0) continue;
nonZero++;
minX = Math.min(x, minX);
minY = Math.min(y, minY);
maxX = Math.max(x, maxX);
maxY = Math.max(y, maxY);
}
}
var o = {w: maxX - minX, h: maxY - minY, descenderH: maxY - 100};
cache[cacheKey] = o;
return o;
}
Note that this is sensitive to antialiasing—the results might be off by a pixel.
#Irongaze.com is right that your fonts, depending on conditions, will have varying actual sizes.
If you want to calibrate for a specific letter, I believe element.getBoundingClientRect() will give you useful coordinates. Be sure to fully reset the container wich you are using as a control box. Mind that on different systems you might get different results.
jsFiddle
Please note that this will not give you the size of the actual visible part of the letter, but the size of the container it determines. line-height for example, will not change the actual letter size, but it will affect other letters' positioning. Be aware of that.
It will help us if you describe the problem you are trying to solve. There might be better solutions.

Categories