i have a pretty simple but very annoying problem. I try to read the width of a string in a canvas. I know how to do this, and it works. But the results differ from browser to browser.
ctx.font = "10px Arial";
var txt = "This is a text demonstration. Why is the width of this text different in every browser??";
ctx.fillText("width:" + ctx.measureText(txt).width, 10, 50);
ctx.fillText(txt, 10, 100);
Here a little fiddle: http://jsfiddle.net/83v7c4jv/
Chrome: 390px, IE: 375px, Firefox: 394px. Only IE is accurate, since C# gives me the same result if i try this there. Does anybody know why and how i can get Chrome and Firefox to render and calculate the same values like IE?
you need to read this answer
it worked for me in some project , i used this code to get height of the text because it not exist it will work the same as text width
obj.lineHeight = function(){
var testDiv = document.createElement('div'); // creating div to measure text in
testDiv.style.padding= "0px";
testDiv.style.margin = "0px";
testDiv.style.backgroundColor = "white";
testDiv.textContent = "Hello World !";
testDiv.style.fontSize = obj.size+"px";
testDiv.style.fontFamily = obj.fontFamily;
testDiv.style.clear = "both";
testDiv.style.visibility="hidden";
document.body.appendChild(testDiv);
var __height__ = testDiv.clientHeight;
testDiv.style.display = "none";
document.body.removeChild(testDiv);
return __height__ ;
};
First of all, the same font may be rendered different in different browsers. Pay attention on the following picture. I just putted together screenshots of your JSFIDDLE running on Chrome (the first) and IE (the second). As you can see, the text width actually is not the same, and the numbers that ctx.measureText returns are correct in the both cases.
C# gives you the same number as IE because they use the same text rendering algorithm, but it has no meaning when your page runs on other browser.
You can found some tricks and hacks in this thread, but in fact you cannot really control the browser rendering mechanism. If you want to ensure your text to be shown exactly the same on all the browsers and only way is to turn it into an image.
Related
EDIT: originally I checked only desktop browsers - but with mobile browsers, the picture is even more complicated.
I came across a strange issue with some browsers and its text rendering capabilities and I am not sure if I can do anything to avoid this.
It seems WebKit and (less consistent) Firefox on Android are creating slightly larger text using the 2D Canvas library. I would like to ignore the visual appearance for now, but instead focus on the text measurements, as those can be easily compared.
I have used the two common methods to calculate the text width:
Canvas 2D API and measure text
DOM method
as outlined in this question: Calculate text width with JavaScript
however, both yield to more or less the same result (across all browsers).
function getTextWidth(text, font) {
// if given, use cached canvas for better performance
// else, create new canvas
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;
};
function getTextWidthDOM(text, font) {
var f = font || '12px arial',
o = $('<span>' + text + '</span>')
.css({'font': f, 'float': 'left', 'white-space': 'nowrap'})
.css({'visibility': 'hidden'})
.appendTo($('body')),
w = o.width();
return w;
}
I modified the fiddle a little using Google fonts which allows to perform text measurements for a set of sample fonts (please wait for the webfonts to be loaded first before clicking the measure button):
http://jsfiddle.net/aj7v5e4L/15/
(updated to force font-weight and style)
Running this on various browsers shows the problem I am having (using the string 'S'):
The differences across all desktop browsers are minor - only Safari stands out like that - it is in the range of around 1% and 4% what I've seen, depending on the font. So it is not big - but throws off my calculations.
UPDATE: Tested a few mobile browsers too - and on iOS all are on the same level as Safari (using WebKit under the hood, so no suprise) - and Firefox on Android is very on and off.
I've read that subpixel accuracy isn't really supported across all browsers (older IE's for example) - but even rounding doesn't help - as I then can end up having different width.
Using no webfont but just the standard font the context comes with returns the exact same measurements between Chrome and Safari - so I think it is related to webfonts only.
I am a bit puzzled of what I might be able to do now - as I think I just do something wrong as I haven't found anything on the net around this - but the fiddle is as simple as it can get. I have spent the entire day on this really - so you guys are my only hope now.
I have a few ugly workarounds in my head (e.g. rendering the text on affected browsers 4% smaller) - which I would really like to avoid.
It seems that Safari (and a few others) does support getting at sub-pixel level, but not drawing...
When you set your font-size to 9.5pt, this value gets converted to 12.6666...px.
Even though Safari does return an high precision value for this:
console.log(getComputedStyle(document.body)['font-size']);
// on Safari returns 12.666666984558105px oO
body{font-size:9.5pt}
it is unable to correctly draw at non-integer font-sizes, and not only on a canvas:
console.log(getRangeWidth("S", '12.3px serif'));
// safari: 6.673828125 | FF 6.8333282470703125
console.log(getRangeWidth("S", '12.4px serif'));
// safari: 6.673828125 | FF 6.883331298828125
console.log(getRangeWidth("S", '12.5px serif'));
// safari 7.22998046875 | FF 6.95001220703125
console.log(getRangeWidth("S", '12.6px serif'));
// safari 7.22998046875 | FF 7
// High precision DOM based measurement
function getRangeWidth(text, font) {
var f = font || '12px arial',
o = $('<span>' + text + '</span>')
.css({'font': f, 'white-space': 'nowrap'})
.appendTo($('body')),
r = document.createRange();
r.selectNode(o[0]);
var w = r.getBoundingClientRect().width;
o.remove();
return w;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
So in order to avoid these quirks,
Try to always use px unit with integer values.
I found below solution from MDN more helpful for scenarios where fonts are slanted/italic which was for me the case with some google fonts
copying the snippet from here - https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Measuring_text_width
const computetextWidth = (text, font) => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.font = font;
const { actualBoundingBoxLeft, actualBoundingBoxRight } = context.measureText(text);
return Math.ceil(Math.abs(actualBoundingBoxLeft) + Math.abs(actualBoundingBoxRight));
}
I'm running an Angular application that is returning two distinct values simultaneously. I'm curious if anyone has seen this:
function updateValues() {
var activeNavButton = pageNavButtons.eq(values.currentPage);
pageNavButtons.removeClass("active");
activeNavButton.addClass("active");
pageNavButtons.each(function () {
var forceRender = $(this).get(0).offsetLeft;
});
var w = 0;
$(".pages button").each(function () {
w = w + $(this).outerWidth(true)
});
var b=0;
completeHandler();
}
This is straightforward as can be. Switch which item is "active", and then force a render refresh. You'll notice none of this code is really doing anything, but thats okay. I left out some of the less important, unrelated stuff.
Yep, I'm frustrated enough that I'm trying to force the render refresh in multiple ways at once.
In the chrome debugger, if you break on this line:
var b = 0
the following occurs:
w = 790 //Watcher
However, if you open the console while the script is still at that break point and literally copy and paste the preceding 4 lines:
var w = 0;
$(".pages button").each(function () {
w = w + $(this).outerWidth(true)
});
It returns 800 for the value of w.
An important thing to note: the .active class gives the selected element a "bold" font, thus changing the element width. I'm positive this is related to the problem but I can't for the life of me figure out what the issue really is.
As you can see, I'm accessing offsetWidth to try to force the browser to update the elements but its not working.
Any ideas? This is driving me absolutely insane.
Okay. This may seem dumb but on large code bases it might not be terribly surprising:
Turns out that the button's base CSS class (a ways up in the hierarchy) had a transition: all 150ms on it.
This caused a delay, which caused widths to return incorrectly as you might expect because, and this is the important part, font weight is included in transition all.
Because of this, the font weight would change 150ms later, and those extra couple of pixels of width (in certain cases) would register "late". No wonder my timers seemed to arbitrarily succeed: I was guessing.
Lesson learned: Don't use transition: all unless you have a good reason. Target the things you want to actually transition.
Hope this helps somebody else! Cheers.
SVG stacking is a technique for stuffing multiple SVG images (like icons) into a single file, to enable downloading sets of icons in a single HTTP request. It's similar to a PNG sprite, except much easier to change/maintain.
The SVG to display is selected using the # fragment identifier in the SVG url. The technique is explained here.
While this technique is arguably on shaky grounds in terms of browser support, (and Chrome doesn't support it all in CSS background-image) it works surprisingly well in most browsers if done using an <img> tag. It works in IE9+, Chrome, and Firefox as an <img> tag, so a fallback to PNG is only required if you need to support much older browsers like IE8.
Except... Safari is a bit of a problem. Even though Safari supports SVGs back to version 5 and below, SVG stacking just doesn't work in versions < 7.1. A blank space is displayed where the icon should be.
So, as of now a fallback is probably necessary. But how can we use feature detection to determine whether we need to fallback to PNG sprites (or at least hide the SVG icon so that a blank space doesn't appear.) ?
The various articles discussing SVG stacks talk about providing fallback for browsers which don't support SVGs. Essentially, the most common technique is to simply use Modernizer to detect if SVGs are supported, and if not, use PNGs, as demonstrated here.
But as far as I can see, nobody is discussing the case where a browser DOES support SVGs, but doesn't support SVG stacking. And as far as I know, at least Safari 5 thru 7.0 fall into that category: these browsers support SVGs, but apparently don't support the :target pseudo selector that enables SVG stacking to work.
So how can this condition be detected? Do we have to rely on user agent sniffing here?
Interesting question!
In general, browser cannot answer regarding a feature it doesn't know about. However, some trick came to my mind.
When the image is OK it means that the pixels in it are different, right? And if we see a blank space it means, that all the pixels in it are the same, doesn't matter if they are white, transparent or something else.
So, we can load an image into canvas, take the first pixel and compare the rest with it. If somehing different is found, so the feature is supported, otherwise not. Something like the following code:
function isSVGLayersSupported(img)
{
// create canvas and draw image to it
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext("2d").drawImage(img, 0, 0);
// get cancas context and image data
var ctx = canvas.getContext("2d");
var imageData = ctx.getImageData(0, 0, img.width, img.height);
// Processing image data
var data = imageData.data;
var firstR = data[0];
var firstG = data[1];
var firstB = data[2];
var firstAlpha = data[3];
for (var i = 4; i < data.length; i += 4) {
if ((data[i] != firstR) ||
(data[i+1] != firstG) ||
(data[i+2] != firstB) ||
(data[i+3] != firstAlpha))
return true;
}
return false;
}
I hava JS script that takes an image and draws it to a canvas. The code works perfectly fine in all browsers except Chrome. I have included the relevant
this.tile = document.createElement('canvas');
var ctx = this.tile.getContext("2d");
this.tile.width = ((size.cm_size) * (((this.image.width) / this.app.dpi.active) * 2.54)).toFixed(2);
this.tile.height = ((size.cm_size) * (((this.image.height) / this.app.dpi.active) * 2.54)).toFixed(2);
if (this.scale > 0) {
// These 2 lines break it in Chrome.
this.tile.width = this.tile.width * this.scale;
this.tile.height = this.tile.height * this.scale;
}
ctx.drawImage(this.image, 0, 0, this.image.width, this.image.height, 0, 0, this.image.width, this.image.height);
I have commented the 2 lines that break things in Chrome. Removing these lines makes things work.
With these lines in place, nothing is displayed until I increase the 'this.app.dpi.active' variable to 350 (it starts at 150). As soon as this happens it displays how it does in Firefox.
There are no error messages, the tile is just empty.
Here's a list of values for 'this.app.dpi.active' and the resulting tile width and height. The tile doesn't display until the value DPI hits 350.
150 DPI = 9444 x 9444
200 DPI = 7084 x 7084
250 DPI = 5668 x 5668
300 DPI = 4720 x 4720
350 DPI = 4048 x 4048
I have checked these vales in Chrome and Firefox and they are identical but things work in Firefox. The this.scale value = 4 and when the width and height aren't multipled by this.scale, it works fine in FF and Chrome.
Please let me know if you need any more code or have any questions.
I don't think you can use the Element constructor. I've always used document.createElement('canvas'). In my FF console, using the Element constructor throws an error.
Okay, I figured this one out myself but thank you everyone for your input!
I noticed that once an image got to a certain size, Chrome display it correctly. This led me to believe that Chrome doesn't allow canvas elements over a certain size (probably for memory/efficiency reasons). To fix this I re-designed my system slightly so that canvas elements never get too big and therefore always displayed correctly. This fixed the issue in Chrome and made my system a fair bit quicker.
To demonstrate this (I'm working on a much more compressed Fiddle), open this in chrome, and move Jay-Z using your arrow keys and catch about 4 - 5 (sometimes more!) cakes.
You will notice that there is a massive cupcake on the left side of the screen now.
I update the cakes' positions in my handleTick function, and add new cakes on a time interval. Here are both of those:
/*This function must exist after the Stage is initialized so I can keep popping cakes onto the canvas*/
function make_cake(){
var path = queue.getItem("cake").src;
var cake = new createjs.Bitmap(path);
var current_cakeWidth = cake.image.width;
var current_cakeHeight = cake.image.height;
var desired_cakeWidth = 20;
var desired_cakeHeight = 20;
cake.x = 0;
cake.y = Math.floor((Math.random()*(stage.canvas.height-35))+1); //Random number between 1 and 10
cake.scaleX = desired_cakeWidth/current_cakeWidth;
cake.scaleY = desired_cakeHeight/current_cakeHeight;
cake.rotation = 0;
cake_tray.push(cake);
stage.addChild(cake);
}
And the setInterval part:
setInterval(function(){
if (game_on == true){
if (cake_tray.length < 5){
make_cake();
}
}
else{
;
}
},500);
stage.update is also called from handleTick.
Here is the entire JS file
Thanks for looking into this. Note once again that this only happens in Chrome, I have not seen it happen on Firefox. Not concerned with other browsers at this time.
Instead of using the source of your item, it might make more sense to use the actual loaded image. By passing the source, the image may have a 0 width/height at first, resulting in scaling issues.
// This will give you an actual image reference
var path = queue.getResult("cake");