views:

619

answers:

5

The standard way to deal with situations where the browser does not support the HTML5 <canvas> tag is to embed some fallback content, usually a polite version (and sometimes a less polite version) of

<canvas>Your browser sucks</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?

+10  A: 

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
Is that a stray `, context` on the second line?
brainjam
@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
+5  A: 

You can use Matt's suggestion. I've also found the following works:

var canvasSupported = "HTMLCanvasElement" in window;

Latest Firefox, Chrome & Opera return true, all IEs return false.

To clarify, this method makes use of the HTMLCanvasElement class that derives from the HTMLElement class exposed by the browser -- IE8 exposes these classes in standards mode only, IE9 will expose them in IE8 and IE9 standards modes.

This feels more like "feature detection" to me, you're asking if something exists, not trying to create it and then asking if creating it worked. The latter is also not as efficient. This is something to consider when including many similar methods in a large library that is for use along with other scripts on a page, you want to squeeze as much performance as possible.


A blog post from the IE9 team appeared today which reminded me of this answer. It's interesting to me that posting it caused a bit of controversy, considering that it was accepted and then later the acceptance was transferred to a different answer. Here we are 4 months later and an official blog post from the aforementioned vendor recommends this method:

In addition to HTML markup, support for , , and can also be detected from script. This detection can be performed many ways, but one of the simplest is to check for the existence of the appropriate interface object off of window.

// Example 7: Simple feature detection for 
if(window.HTMLCanvasElement) {
    // Code requiring canvas support
}

This is virtually identical to the method I suggested here. The only difference is the truthy/falsey check, instead of the property in object check. Granted, they do suggest the check for getContext as an alternative, but I thought it worth updating this answer for those who might want to use an alternative to creating a dummy element and not using it.

Andy E
All three answers to date are great, but I like this one best because it is light-weight, and deals with both tag-created and script-created canvas situations.
brainjam
You can save a couple characters-if(window.HTMLCanvasElement){}
kennebec
@kennebec: Indeed, there are a few ways you can write it. Usually when I write feature detection I use `in` wherever possible -- it's just my personal preference but I think it looks cleaner and it yields a boolean (not that that matters so much).
Andy E
@brainjam: thanks. It made more sense to me than going the `createElement` route - AFAIK all browsers that support canvas or will support it in the future (see IE9 Platform Preview in standards mode) support element "constructor" functions.
Andy E
@downvoter: Is there a reason for the downvote? It would be nice of you to share, so that both myself and the OP might know why this is not a good method of canvas detection.
Andy E
This test will fail when IE eventually supports canvas as it toString()s elements differently than all other browsers. Since canvas is a scriptable element, it's best to test the scripting capabilities of it.Therefore, `return !!document.createElement('canvas').getContext` is the best way to test for canvas support.
Paul Irish
@Paul Irish: I'm not sure I understand why you think my method involves `toString()`. The `in` operator in JavaScript checks for the existence of a property on an object. For instance: `"hello" in { "hello": false; }` will yield true. All modern browsers expose constructor-style functions for elements. For instance, `HTMLElement`, `HTMLDivElement` and `HTMLCanvasElement`. This allows us to check for the existence of those functions as properties of the windows object. IE has supported these since IE8 - http://msdn.microsoft.com/en-us/library/dd347058(v=VS.85).aspx
Andy E
@Paul Irish: I definitely don't think the downvote was warranted. You can try the method I listed for yourself in **IE8 standards mode** : `javascript:alert("HTMLDivElement" in window);`.
Andy E
`'getContext' in document.createElement('canvas')` is a standards compliant way of checking for the existence of a canvas implementation. It is debatable whether the W3C DOM dictates that ECMAScript bindings must expose their constructor functions or their prototype (eg `HTMLCanvasElement`) to user scripts. What you have here is clever, but I'd go for the standards-based approach. For one it would be immune to a script defining `function HTMLCanvasElement(){}` in the global scope, much like `typeof(foo) === 'undefined'` is immune to a script overwriting `undefined = ...`.
Crescent Fresh
For example, the global `CSSFontFaceRule` exists in many browsers. In earlier versions of Chrome it was present event though it did nothing. Similarly, that global did not exist in IE, which supports the @font-face feature.Ask yourself, do IE6 and IE7 support the `<div>` tag? If so, should `'HTMLDivElement' in window` return true? I bet you can guess what it does return...
Paul Irish
I'm sold on @Paul Irish's argument, and am switching my accept to his answer.
brainjam
@Crescent Fresh: I'm happy to +1 your comment because I almost see where you're coming from. Element constructor functions are already exposed in all modern popular browsers and are extremely unlikely to go away. If anything, it's more likely that the spec would be updated to clarify/include DOM prototypes. Secondly, not just due to popular scripting naming conventions, it's unlikely that someone would create a `HTMLCanvasElement`, although I will concede the possibility.
Andy E
@Paul Irish: If you happened to read my comment properly, you will see that I mentioned element prototypes weren't exposed until IE8 Standards Mode. That is completely irrelevant in this case because canvas won't be implemented until IE9. Canvas will only be available in **IE9 standards mode** and therefore, the constructor/prototype for the element will also be exposed.
Andy E
@brainjam: that's fair enough, but Paul Irish is wrong in his assumption that my code will not work in IE9. Also, his solution is identical to Matt's answer (create the element and check for the existence of `getContext`) which was posted first and probably deserves the mark more.
Andy E
@Andy E's head: Sure sure. But i like detections that are fair to all browsers versions (including ie6 and 7). Anyway, you're v smart, clearly. I'm just not super keen on those global constructors on a way of intuiting full support.Maybe its the Friday in me, but whatevs; these are just points on a website on the internet. :)
Paul Irish
@Paul Irish: The whole point of the detection was that it would return true for browsers that supported canvas element and false for browsers that didn't support the canvas element. I believe it does that just fine and I didn't take fairness into account when I thought of it (and lets face it, when is IE ever fair to us? ;-)) I apologize for my seemingly cranky comments.
Andy E
@Andy E head, your comment is noted. Matt's answer was indeed posted earlier, but the object of SO is to accept the 'best' answer. I was asking for a simple boolean test, and Matt's has side effects that are unnecessary. I also agree that your test is likely to work in IE9.
brainjam
@brainjam: I was reminded of this answer today when I read a blog post from the IE team regarding detection of canvas support. I've updated my answer, if you're interested :-)
Andy E
@Andy E: awesome, thanks for the update. I always thought your answer was great, but in this case I decided to go with the standards-based frameworks guys' arguments (with other questions I've done the opposite). And your assertion that it would work in IE9 has been vindicated.
brainjam
@brainjam: that's cool, I just thought I'd give you a heads up on the update. I know my post got down voted a few times, and Paul didn't even understand what was happening with my code (which is surprising to say he's co-developed a standards-checking library), but as long as you got at least some good information from it, then that's all that matters in the end :-)
Andy E
I'm a little late to the party here, but thought I'd make my point anyway. Just as defining a globally scoped `HTMLCanvasElement` function breaks Andy E's feature detection, you can break Paul Irish's implementation by defining `HTMLElement.prototype.getContext = function() { }`. Potato Potahto.
gilly3
@gilly3: thanks, you're right of course. I still maintain that this answer didn't warrant down votes. @Paul down voted my post because he didn't understand my answer, ironically his Modernizr library uses the slower method - anybody can check the benchmarks of `createElement` vs the `in` operator. This is still a very valid method of feature detection for canvas support.
Andy E
+5  A: 

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
The test we use in modernizr is this: `return !!document.createElement('canvas').getContext` That is definitely the best way to test.
Paul Irish
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
+1  A: 

There may be a gotcha here- some clients do not support all canvas methods I like to know right off if the canvas text api is functional

document.canvas= (function(){
    var dc= document.createElement('canvas');
    if(!dc.getContext) return 0;
    var c= dc.getContext('2d');
    return typeof c.fillText== 'function'? 2: 1;
})();

alert(document.canvas)

kennebec
+9  A: 

This is the technique used in Modernizr and basically every other library that does canvas work:

function isCanvasSupported(){
  return !!document.createElement('canvas').getContext;
}

Since your question was for detection when it's not supported, I recommend using it like so:

if (!isCanvasSupported()){ ...
Paul Irish
@Paul: interesting that the IE team posted on their blog today, showing both my method and your method as good examples of checking for `<canvas>` support :-)
Andy E