The standard way to deal with situations where the browser does not support the HTML5 <canvas> tag is to embed some fallback content like:
<canvas>Your browser doesn't support "canvas".</canvas>
But the rest of the page remains the same, which may be inappropriate or misleading. I'd like some way of detecting canvas non-support so that I can present the rest of my page accordingly. What would you recommend?
This is the technique used in Modernizr and basically every other library that does canvas work:
function isCanvasSupported(){
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
Since your question was for detection when it's not supported, I recommend using it like so:
if (!isCanvasSupported()){ ...
There are two popular methods of detecting canvas support in browsers:
Matt's suggestion of checking for the existence of getContext, also used in a similar fashion by the Modernizr library:
var canvasSupported = !!document.createElement("canvas").getContext;
Checking the existence of the HTMLCanvasElement interface, as defined by the WebIDL and HTML specifications. This approach was also recommended in a blog post from the IE 9 team.
var canvasSupported = !!window.HTMLCanvasElement;
My recommendation is a variation of the latter (see Additional Notes), for several reasons:
Every known browser supporting canvas ― including IE 9 ― implements this interface;
It's more concise and instantly obvious what the code is doing;
The getContext approach is significantly slower across all browsers, because it involves creating an HTML element. This is not ideal when you need to squeeze as much performance as possible (in a library like Modernizr, for example).
There are no noticeable benefits to using the first method. Both approaches can be spoofed, but this not likely to happen by accident.
Additional Notes
It may still be necessary to check that a 2D context can be retrieved. Reportedly, some mobile browsers can return true for both above checks, but return null for .getContext('2d'). This is why Modernizr also checks the result of .getContext('2d'). However, WebIDL & HTML ― again ― gives us another better, faster option:
var canvas2DSupported = !!window.CanvasRenderingContext2D;
Notice that we can skip checking for the canvas element entirely and go straight to checking for 2D rendering support. The CanvasRenderingContext2D interface is also part of the HTML specification.
You must use the getContext approach for detecting WebGL support because, even though the browser may support the WebGLRenderingContext, getContext() may return null if the browser is unable to interface with the GPU due to driver issues and there is no software implementation. In this case, checking for the interface first allows you to skip checking for getContext:
var cvsEl, ctx;
if (!window.WebGLRenderingContext)
window.location = "http://get.webgl.org";
else {
cvsEl = document.createElement("canvas");
ctx = cvsEl.getContext("webgl") || cvsEl.getContext("experimental-webgl");
if (!ctx) {
// Browser supports WebGL, but cannot create the context
}
}
##Performance Comparison
Performance of the getContext approach is 85-90% slower in Firefox 11 and Opera 11 and about 55% slower in Chromium 18.
I usually run a check for getContext when I create my canvas object.
(function () {
var canvas = document.createElement('canvas'), context;
if (!canvas.getContext) {
// not supported
return;
}
canvas.width = 800;
canvas.height = 600;
context = canvas.getContext('2d');
document.body.appendChild(canvas);
}());
If it is supported, then you can continue the canvas setup and add it to the DOM. This is a simple example of Progressive Enhancement, which I (personally) prefer over Graceful Degradation.
Why not try modernizr ? It's a JS library that provides detection capability.
Quote:
Have you ever wanted to do
if-statements in your CSS for the
availability of cool features like
border-radius? Well, with Modernizr
you can accomplish just that!
try {
document.createElement("canvas").getContext("2d");
alert("HTML5 Canvas is supported in your browser.");
} catch (e) {
alert("HTML5 Canvas is not supported in your browser.");
}
There may be a gotcha here- some clients do not support all canvas methods.
var hascanvas= (function(){
var dc= document.createElement('canvas');
if(!dc.getContext) return 0;
var c= dc.getContext('2d');
return typeof c.fillText== 'function'? 2: 1;
})();
alert(hascanvas)
You can use canisuse.js script to detect if your browsers supports canvas or not
caniuse.canvas()
If you're going to get the context of your canvas, you might as well use it as the test:
var canvas = document.getElementById('canvas');
var context = (canvas.getContext?canvas.getContext('2d'):undefined);
if(!!context){
/*some code goes here, and you can use 'context', it is already defined*/
}else{
/*oof, no canvas support :(*/
}
Related
i have a chart that created with highchart.
i need to save svg to png in internet Explorer.
i use from follow code and exist security Error in ie11.
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var imgChart = document.createElement('img');
imgChart.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))));
imgChart.onload = function () {
ctx.drawImage(imgChart, 0, 0);
var blobObject = canvas.msToBlob();
window.navigator.msSaveBlob(blobObject, 'save.png');
})
I didn't found an satisfying dupe target, so I'll rewrite it as an answer :
Drawing an SVG image through drawImage method will taint the canvas in IE < Edge for security reasons.
This operation is somehow sensitive for browsers, since svg images imply to parse some XML, and that it can contain some tricky elements (though IE doesn't support <foreignObject>...)
So quite often, browsers will add security restrictions when SVG images are drawn to it, and will block all exporting methods.
This is the case in safari > 9 when an <foreignObject> is drawn on it, this was also the case in chrome, but only when the image comes from an Blob (an implementation bug, but they finally leveraged the security restriction altogether anyway).
And then in IE < Edge, with any SVG.
The only way to workaround this issue is to parse yourself the SVG, and then use the canvas' methods to reproduce it.
This is all doable, but can take some time to implement, so even though I don't really like it, you'd probably be better using an library like canvg, which does exactly this (parsing + rendering with canvas methods).
For example, let's say we have two versions of lazyload (see code below). In terms of performance, is versionII better than version I?
const imgs = document.querySelectorAll('img');
window.addEventListener('scroll' , lazyload);
// version I
function lazyload() {
imgs.forEach((img) => {
if (img.offsetTop < window.innerHeight + window.pageYOffset) {
img.src = img.dataset.src;
}
}
}
// version II
function lazyload() {
const innerHeight = window.innerHeight;
const pageYOffset = window.pageYOffset;
imgs.forEach((img) => {
if (img.offsetTop < innerHeight + pageYOffset) {
img.src = img.dataset.src;
}
}
Your specific question:
I'll rephrase your specific question like this:
Is it costly to access window.innerHeight and/or window.pageYOffset?
It can be. According to Paul Irish of the Google Chrome Developer Tooling team:
All of the below properties or methods, when requested/called in JavaScript, will trigger the browser to synchronously calculate the style and layout*. This is also called reflow or layout thrashing, and is common performance bottleneck.
...
window
window
window.scrollX, window.scrollY
window.innerHeight, window.innerWidth
window.getMatchedCSSRules() only forces style
-- What forces layout / reflow (emphasis mine)
At the bottom of that document, Paul indicates the layout reflow will only occur under certain circumstances. The portions below (with my added emphasis) answer your question better and more authoritatively than I could.
Reflow only has a cost if the document has changed and invalidated the style or layout. Typically, this is because the DOM was changed (classes modified, nodes added/removed, even adding a psuedo-class like :focus).
If layout is forced, style must be recalculated first. So forced layout triggers both operations. Their costs are very dependent on the content/situation, but typically both operations are similar in cost.
What should you do about all this? Well, the More on forced layout section below covers everything in more detail, but the
short version is:
for loops that force layout & change the DOM are the worst, avoid them.
Use DevTools Timeline to see where this happens. You may be surprised to see how often your app code and library code hits this.
Batch your writes & reads to the DOM (via FastDOM or a virtual DOM implementation). Read your metrics at the begininng of the frame (very very start of rAF, scroll handler, etc), when the numbers are still identical to the last time layout was done.
Changing the src attribute is probably sufficient to "invalidate the style or layout." (Although I suspect using something like correctly-dimensioned SVG placeholders for lazy-loaded images would mitigate or eliminate the cost of the reflows.)
In short, your "version I" implementation is preferable and has, as far as I can tell, no real disadvantages.
Your general question
As shown above, reading properties from the window object can be expensive. But others are right to point out a couple things:
Optimizing too early or too aggressively can cost you valuable time, energy, and (depending on your solution) maintainability.
The only way to be certain is to test. Try both versions of your code, and carefully analyze your favorite dev tool's output for performance differences.
This one seems better
// version III
function lazyload(){
const dimension = window.innerHeight + window.pageYOffset;
img.array.forEach(img => {
if (img.offsetTop < dimension) {
img.src = img.dataset.src;
}
});
}
It appears Microsoft Edge partially supports the Path2D API - it doesn't support the usage SVG path strings, i.e.,
const path = new Path2D('M20 L30');
Is there a way to feature detect whether or not passing in SVG paths is supported?
There is no catchable error that gets thrown in this case, and passing an invalid string does not throw either in supporting browsers. (This sounds like a specs flow to me btw...)
So to be able to detect this exact case of the constructor being supported, but the argument getting ignored, we have to actually check if something got drawn or not...
You can do so by stroking a simple M0,0H1 Path2D and then check if the pixel at coords 0,0 of you context has been painted.
function supports() {
// no simple support
if (typeof Path2D !== 'function') return false;
// create a new context
var x = document.createElement('canvas')
.getContext('2d');
// stroke a simple path
x.stroke(new Path2D('M0,0H1'));
// check it did paint something
return !!x.getImageData(0, 0, 1, 1).data[3];
};
console.log(supports());
But note that Edge does not seem to support either the Path2D.addPath() method, so if you include a polyfill like Google's canvas-5-polyfill which does include a Path2D polyfill, then the whole Path2D will use the polyfilled version (because they also check for addPath support).
I have the latest Chrome version and I see in specs that it should support .addHitRegion() method, as mentioned on MDN. For some reason I get Uncaught TypeError: context.addHitRegion is not a function error.
My code is as simple as this:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.beginPath();
context.rect(10,10,100,100);
context.fill();
context.addHitRegion({'id': 'The First Button', 'cursor': 'pointer'});
How do I fix it?
Go here with your browser: chrome://flags
and then
Set the flag Experimental Web Platform features to true to enable it.
As the other answers states, you can enable this through flags, however: you won't be able to ask your users to do the same. And the support is limited to a few browsers. I would therefor recommend looking to other solutions - I list some here:
A notch better approach is to use Path2D objects. They provide the same flexibility in terms of defining hit shapes. Use these with isPointInPath() which also takes a path object. Store each path in an array which you loop through using the position to test with. Unfortunately though, also this is limited to a few browsers, but you can at least use a poly-fill such as this to fix that to some extend (see notes in the link for limitations).
A better option perhaps in regards to support and availability, and the one requiring a bit more work, is to rebuild each single path you want to test on the context itself, then use as above the isPointInPath() to see if the mouse position is inside that path.
If the shapes are simple such as rectangles or circles, you can do simple mathematical tests which is a performant alternative.
So you need to set the experimental flag here
From The compatibility table at the bottom of the page you linked:
This feature is behind a feature flag. Set the flag
ExperimentalCanvasFeatures to true to enable it.
To turn on experimental canvas features browse to “chrome://flags“, turn on “Enable experimental canvas features” and relaunch.
Unfortunately the hit region feature is now obsolete and doesn't appear to be enable-able. You can use isPointInPath() as an alternative. You'll need to create a path object to be able to pass into that function. Something like:
const rectangle = new Path2D();
ctx.beginPath();
rectangle.rect(10, 10, 100, 100);
ctx.fill(rectangle);
...then to check, you could put it into an event listener:
canvas.addEventListener("mousemove", (e) => {
if (ctx.isPointInPath(rectangle, e.offsetX, e.offsetY)) {
console.log("rectangle is hit");
});
I'm using the Canvas object with javascript. Just doing some tests to see how fast I can set pixels in a draw loop.
On mac, it works great in FF, safari, chrome. On windows, I get a flickering effect on FF and chrome. It looks like somehow the canvas implementation on windows is different than on mac for the different browsers? (not sure if that's true).
This is the basic code I'm using to do the drawing (taken from the article below - I've optimized the below to tighten the draw loop, it runs pretty smooth now):
var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (var x = 0; x < canvasData.width; x++) {
for (var y = 0; y < canvasData.height; y++) {
// Index of the pixel in the array
var idx = (x + y * canvas.width) * 4;
canvasData.data[idx + 0] = 0;
canvasData.data[idx + 1] = 255;
canvasData.data[idx + 2] = 0;
canvasData.data[idx + 3] = 255;
}
}
ctx.putImageData(canvasData, 0, 0);
again, browers on windows will flicker a bit. It looks like the canvas implementation is trying to clear the canvas to white before the next drawing operation takes place (this does not happen on mac). I'm wondering if there is a setting I can change in the Canvas object to modify that value (double-buffering, clear before draw, etc)?
This is the article I am using as reference:
http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
Thanks
I think it's fairly clear that browsers who implement the Canvas object use DIBS (device independent bitmaps). The fact that you have access to the pixelbuffer without having to lock the handle first is proof of this. And Direct2D has nothing to do with JS in a browser thats for sure. GDI is different since it uses DDBs (device dependent bitmaps, i.e allocated from video memory rather than conventional ram). All of this however has nothing to do with optimal JS rendering speed. I think writing the RGBA values as you do is probably the best way.
The crucial factor in the code above is the call to putImageData(). This is where browsers can differ in their implementation. Are you in fact writing directly to the DIB, and putImageData is simply a wrapper around InvalidateRect? Or are you in fact writing to a duplicate in memory, which in turn is copied into the canvas device context? If you use linux or mac then this is still a valid question. Although device contexts etc. are typically "windows" terms, most OS'es deal with handles or structures in pretty much the same way. But once again, we are at the mercy of the browser vendor.
I think the following can be said:
If you are drawing many pixels in one go, then writing directly to the pixelbuffer as you do is probably the best. It is faster to "bitblt" (copy) the pixelbuffer in one go after X number of operations. The reason for this is that the native graphics functions like FillRect also calls "invalidate rectangle" which tells the system that a portion if the screen needs a re-draw (refresh). So if you call 100 line commands, then 100 update's will be issued - slowing down the process. Unless (and this is the catch) you use the beginPath/EndPath methods as they should be used. Then it's a whole different ballgame.
It's here that the Begin/End path "system" comes into play, and also the Stroke/Outline commands. They allow you to execute X number of drawing operations within a single update. But a lot of people get this wrong and issue a redraw for each call to line/fillrect etc.
Also, have you tried creating an invisible canvas object, drawing to that, and then copying to a visible canvas? This could be faster (proper double-buffering).
The problem is with the way the browsers use the native graphics APIs on the different OSes. And even on the same OS, using different APIs (for example GDI vs. Direct2D in Windows) would also produce different results.