142

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?

kangax
  • 38,898
  • 13
  • 99
  • 135
brainjam
  • 18,863
  • 8
  • 57
  • 82

8 Answers8

221

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()){ ...
Paul Irish
  • 47,354
  • 22
  • 98
  • 132
  • 15
    Why does the double negation (!!) stands for? –  Mar 04 '13 at 18:22
  • 16
    If Canvas isn't there, `elem.getContext == undefined`. `!undefined = true`, and `!true = false`, so this lets us return a bool, rather than undefined or the context. – Rich Bradshaw Mar 24 '13 at 13:35
  • 1
    @2astalavista The double negative (!!) is like casting. It turns a truey or falsey statement into a boolean. For example: `var i = 0`. i evaluates to false, but typeof i returns "number". typeof !!i returns "boolean". – User2 Jun 12 '14 at 15:41
  • Another way to "cast" to boolean is: `undefined ? true : false` (though a little bit more lengthy). – vcapra1 Sep 01 '14 at 14:46
  • 1
    I should be noted that there are different types of canvas support. Early browser implementations did not support `toDataURL`. And Opera Mini only supports basic canvas rendering with [no text API support](http://stackoverflow.com/q/12510482/1647538). Opera Mini can be excluded [this way](http://stackoverflow.com/a/30612337/1647538), just for cross reference. – hexalys Jan 13 '16 at 01:27
104

There are two popular methods of detecting canvas support in browsers:

  1. 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;
    
  2. 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.

    Simple comparison table, click to run a test in your browser

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Andy E
  • 338,112
  • 86
  • 474
  • 445
  • 10
    Nokia S60 and the Blackberry Storm are among some of the devices that will false positive on your proposed 2D canvas detects. Unfortunately, mobile gets very hairy and vendors don't follow rules. :( So we end up with more complete (i.e. slower) tests to assure accurate results. – Paul Irish Apr 25 '12 at 16:07
  • @Paul: that's interesting, I tested the BlackBerry Storm emulators, all of them returned `false` for both your example and mine, it seems they don't provide the `CanvasRenderingContext2D` interface. I've been unable to test the S60 as of yet, I'm still very curious and may do so soon, though. – Andy E Apr 25 '12 at 16:18
  • 1
    This is interesting, but as long as the test comes in under a hundred or so millis, isn't that fine? I imagine that they're all much faster than that anyway. If you memoise a function that tests for this then you only need to pay the cost once. – Drew Noakes Jan 16 '13 at 12:08
  • 1
    I ran your benchmark and even the 'slow' approach can be done ~800,000 times a second. Again, if the result is cached then the decision over which approach to use should be based upon robustness, not performance (assuming there's a difference in robustness.) – Drew Noakes Jan 16 '13 at 12:11
  • @DrewNoakes: yes, you should almost always go for compatibility over speed. My argument is that I'm refuting the compatibility claims by Paul, based on my own testing in at least one of the problem browsers he mentioned in his comment. I've been unable to test the other browser but I remain unconvinced that there's an issue. You should always aim to get the best performance possible, without sacrificing compatibility. I'm not talking about micro-optimising, but if you're running hundreds of tests and they're all unoptimised then, yes, it can make a difference. – Andy E Jan 16 '13 at 13:02
  • @DrewNoakes: another thing to bear in mind is that you ran your test on a desktop browser with a nice, fast CPU and plenty of RAM to push those numbers. Imagine the performance difference if you ran your test on something like the Nintendo 3DS's browser, or another device with low specifications. – Andy E Jan 16 '13 at 13:07
  • Solved issues that I have on Android 2.2-2.3 browsers. Thanks! – Christian Martinez Mar 30 '15 at 15:26
13

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.

Matt
  • 43,482
  • 6
  • 101
  • 102
  • Is that a stray `, context` on the second line? – brainjam Apr 30 '10 at 15:42
  • 7
    @brainjam - No, I use that variable near the end of the code. I try to follow the [JSLint](http://jslint.com) 'recommendations' (in this case.. only 1 `var` statement per function). – Matt Apr 30 '10 at 15:56
6

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!

Frozenskys
  • 4,290
  • 4
  • 28
  • 28
  • 2
    The test we use in modernizr is this: `return !!document.createElement('canvas').getContext` That is definitely the best way to test. – Paul Irish Apr 30 '10 at 17:19
  • 4
    Modernizr is a useful library, but it would be a bit of a waste to pull in the whole library just to detect canvas support. If you need to detect other features too then I’d recommend it. – Daniel Cassidy Jul 23 '10 at 16:18
5
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.");
}
Community
  • 1
  • 1
Sheikh Ali
  • 1,514
  • 1
  • 10
  • 8
1

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)
kennebec
  • 102,654
  • 32
  • 106
  • 127
0

You can use canisuse.js script to detect if your browsers supports canvas or not

caniuse.canvas()
Beka Tomashvili
  • 2,171
  • 5
  • 21
  • 27
0

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 :(*/
}