85

I wanted to use the Modernizr JS library to detect for some browser properties to determine what content to show or not show.

I have an app called Pano2VR which outputs both HTML5 and SWF. I need the HTML5 for iOS device users.

However, IE does not render this "HTML5" output at all. It seems their output uses CSS3 3D transforms and WebGL, one or more apparently unsupported in IE9.

So, for those users I need to display the Flash version. I was planning to use an IFRAME and either pass the SRC via a Modernizr script or document.write out the correct IFRAME code depending on browser.

All of which leads to how do I use Modernizr to detect simply IE or not IE? Or detect for CSS 3d transforms?

Or is there another way to do this?

isherwood
  • 58,414
  • 16
  • 114
  • 157
Steve
  • 14,401
  • 35
  • 125
  • 230

9 Answers9

197

Modernizr doesn't detect browsers as such, it detects which feature and capability are present and this is the whole jist of what it's trying to do.

You could try hooking in a simple detection script like this and then using it to make your choice. I've included Version Detection as well just in case that's needed. If you only want to check of any version of IE you could just look for the navigator.userAgent having a value of "MSIE".

var BrowserDetect = {
        init: function () {
            this.browser = this.searchString(this.dataBrowser) || "Other";
            this.version = this.searchVersion(navigator.userAgent) || this.searchVersion(navigator.appVersion) || "Unknown";
        },
        searchString: function (data) {
            for (var i = 0; i < data.length; i++) {
                var dataString = data[i].string;
                this.versionSearchString = data[i].subString;

                if (dataString.indexOf(data[i].subString) !== -1) {
                    return data[i].identity;
                }
            }
        },
        searchVersion: function (dataString) {
            var index = dataString.indexOf(this.versionSearchString);
            if (index === -1) {
                return;
            }

            var rv = dataString.indexOf("rv:");
            if (this.versionSearchString === "Trident" && rv !== -1) {
                return parseFloat(dataString.substring(rv + 3));
            } else {
                return parseFloat(dataString.substring(index + this.versionSearchString.length + 1));
            }
        },

        dataBrowser: [
            {string: navigator.userAgent, subString: "Edge", identity: "MS Edge"},
            {string: navigator.userAgent, subString: "MSIE", identity: "Explorer"},
            {string: navigator.userAgent, subString: "Trident", identity: "Explorer"},
            {string: navigator.userAgent, subString: "Firefox", identity: "Firefox"},
            {string: navigator.userAgent, subString: "Opera", identity: "Opera"},  
            {string: navigator.userAgent, subString: "OPR", identity: "Opera"},  

            {string: navigator.userAgent, subString: "Chrome", identity: "Chrome"}, 
            {string: navigator.userAgent, subString: "Safari", identity: "Safari"}       
        ]
    };
    
    BrowserDetect.init();
    document.write("You are using <b>" + BrowserDetect.browser + "</b> with version <b>" + BrowserDetect.version + "</b>");

You can then simply check for:

BrowserDetect.browser == 'Explorer';
BrowserDetect.version <= 9;
Code Uniquely
  • 6,356
  • 4
  • 30
  • 40
  • 2
    Thanks. I wound up finally tracking down that the issue was their file needed webgl support. So, I could use Modernizer to test for that and do a document.write of one code block of the other. But this is an excellent solution for browser detection. Thanks again. – Steve Nov 20 '12 at 22:11
  • 2
    One thing to remember: The UA string is completely user-configurable.. So checking the UA string is NOT a consistent way of checking the browser. https://developer.mozilla.org/en-US/docs/DOM/window.navigator.userAgent In the "Notes" section: `Browser identification based on detecting the user agent string is unreliable and is not recommended, as the user agent string is user configurable.` – Andrew Senner May 05 '13 at 00:00
  • 51
    Yes, but what percentage of users are browsing the web with a modified/spoofed/incorrect UA string? How much engineering time do you want to spend ensuring that tiny minority has an optimal experience on your site? Browser sniffing via UA string is a practical and sane approach. – Jed Richards May 15 '13 at 15:37
  • 17
    @Wintamute, can't agree more. Get sick of "feature detection is evil" kind of lecture. We are doing engineering, not pursuing art – Philip007 Jul 03 '13 at 10:40
  • You have a trailing comma after the last item in dataBrowser, that should be removed. – Scott Jul 21 '13 at 07:05
  • I should have also mentioned that I only found the comma because I'm copy/pasting your code word-for-word to use in my current project and jslint squawked at me for it. Thanks! – Scott Jul 22 '13 at 07:23
  • 9
    It is the general assumption that if someone modifies their UA string, then they know what they're doing and they can deal with the consequences. In fact this is exactly what the UA string exists for - to declare browser version to the server. If the client wants to lie about that, then well, that's just life! Of course there is a small possibility of the string being change without the user's consent but in practice that's not a real concern - and hey are we supposed to feed the user and rub their back too? – Rolf Nov 26 '13 at 21:26
  • 1
    Added support for IE 11 (Trident). Gets the browser version (11) instead of the engine version (7). If you want the engine version take out: if (this.versionSearchString == "Trident") { this.versionSearchString = "rv"; } and then change the identity in dataBrowser so it doesn't give you Explorer 7 when it really is Trident 7. – ObjectType Dec 17 '13 at 21:07
  • 1
    Is there a way to add this function as a test as part of Modernizr, maybe returning something like `Modernizr.addTest(result,function)`? it would be really useful to have browser name added as a css class – wf4 Jan 29 '14 at 17:05
  • There are some examples of how you might do this at https://gist.github.com/danott/855078 – Code Uniquely Jan 30 '14 at 06:06
  • the code is working great, however it does not detect whether it's a desktop / mobile browser. – Raptor Mar 07 '14 at 03:57
  • 1
    Added IE11 Support which now uses the trident engine and a rv: number – Code Uniquely Oct 25 '14 at 06:14
  • BrowserDetect.browser in opera writes chrome – kaxi1993 Aug 03 '15 at 12:05
  • There is one problem, Opera, Microsoft edge have also Chrome in their navigator, so I see Chrome as browser when i'm in Opera – user1245809 Nov 14 '15 at 07:40
  • 1
    @user1245809, I added a extra line for 'OPR' to find the latest Opera versions. It works with MS Edge too. But as I said at the start of my answer, you really should use feature detection for the things you are using rather than relying on the browser and version numbers being correct. :) It sure does not tell you its a Mobile version for instance, or whether you can use FileAPI – Code Uniquely Nov 20 '15 at 08:23
  • Not working on latest browsers. I tried on Ipad chrome. Still its saying Safari. – Zia Qamar Mar 18 '20 at 07:55
20

You can use Modernizr to detect simply IE or not IE, by checking for SVG SMIL animation support.

If you've included SMIL feature detection in your Modernizr setup, you can use a simple CSS approach, and target the .no-smil class that Modernizr applies to the html element:

html.no-smil {
  /* IE/Edge specific styles go here - hide HTML5 content and show Flash content */
}

Alternatively, you could use Modernizr with a simple JavaScript approach, like so:

if ( Modernizr.smil ) {
  /* set HTML5 content */
} else {
  /* set IE/Edge/Flash content */
}

Bear in mind, IE/Edge might someday support SMIL, but there are currently no plans to do so.

For reference, here's a link to the SMIL compatibility chart at caniuse.com.

jacefarm
  • 6,747
  • 6
  • 36
  • 46
  • Best answer imo. So simple – Batman Oct 29 '15 at 21:49
  • 2
    While this does work, it works for now. The whole point of feature detection and Modernizr is that you don't have to worry about what happens tomorrow. If Edge updates tomorrow with smil support, your code no longer works and you may not even know it. – Jason Dec 16 '15 at 17:32
  • It remains that there are [no plans for SMIL support in Edge](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/svgsmilanimation/). – jacefarm Jan 23 '19 at 19:16
  • 1
    This looked so simple, but apparently [Edge now supports SMIL](http://caniuse.com/#feat=svg-smil). – Jeremy Carlson Oct 16 '20 at 15:42
12

Detecting CSS 3D transforms

Modernizr can detect CSS 3D transforms, yeah. The truthiness of Modernizr.csstransforms3d will tell you if the browser supports them.

The above link lets you select which tests to include in a Modernizr build, and the option you're looking for is available there.


Detecting IE specifically

Alternatively, as user356990 answered, you can use conditional comments if you're searching for IE and IE alone. Rather than creating a global variable, you can use HTML5 Boilerplate's <html> conditional comments trick to assign a class:

<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->

If you already have jQuery initialised, you can just check with $('html').hasClass('lt-ie9'). If you need to check which IE version you're in so you can conditionally load either jQuery 1.x or 2.x, you can do something like this:

myChecks.ltIE9 = (function(){
    var htmlElemClasses = document.querySelector('html').className.split(' ');
    if (!htmlElemClasses){return false;}
    for (var i = 0; i < htmlElemClasses.length; i += 1 ){
      var klass = htmlElemClasses[i];
      if (klass === 'lt-ie9'){
        return true;
      }
    }
    return false;
}());

N.B. IE conditional comments are only supported up to IE9 inclusive. From IE10 onwards, Microsoft encourages using feature detection rather than browser detection.


Whichever method you choose, you'd then test with

if ( myChecks.ltIE9 || Modernizr.csstransforms3d ){
    // iframe or flash fallback
} 

Don't take that || literally, of course.

iono
  • 2,575
  • 1
  • 28
  • 36
  • Personally I always try do feature based detection rather than browser detection – Chris Nov 24 '14 at 13:37
  • @Chris Good for you, same here? I... don't think you actually read my answer. – iono Nov 25 '14 at 04:59
  • Yours was the first answer that actually suggests using feature detect so figured it might help another person if they read the comment – Chris Nov 25 '14 at 08:44
  • @Chris oh, sorry. I thought you were condemning me for including the IE test. – iono Nov 26 '14 at 09:40
9

If you're looking for a JS version (using a combination of feature detection and UA sniffing) of what html5 boilerplate used to do:

var IE = (!! window.ActiveXObject && +(/msie\s(\d+)/i.exec(navigator.userAgent)[1])) || NaN;
if (IE < 9) {
    document.documentElement.className += ' lt-ie9' + ' ie' + IE;
}
Pete B
  • 1,709
  • 18
  • 11
3

Well, after doing more research on this topic I ended up using following solution for targeting IE 10+. As IE10&11 are the only browsers which support the -ms-high-contrast media query, that is a good option without any JS:

@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {  
   /* IE10+ specific styles go here */  
}

Works perfectly.

Oliver White
  • 615
  • 6
  • 16
2

CSS tricks have a good solution to target IE 11:

http://css-tricks.com/ie-10-specific-styles/

The .NET and Trident/7.0 are unique to IE so can be used to detect IE version 11.

The code then adds the User Agent string to the html tag with the attribute 'data-useragent', so IE 11 can be targeted specifically...

Julian Veling
  • 357
  • 2
  • 13
1

I needed to detect IE vs most everything else and I didn't want to depend on the UA string. I found that using es6number with Modernizr did exactly what I wanted. I don't have much concern with this changing as I don't expect IE to ever support ES6 Number. So now I know the difference between any version of IE vs Edge/Chrome/Firefox/Opera/Safari.

More details here: http://caniuse.com/#feat=es6-number

Note that I'm not really concerned about Opera Mini false negatives. You might be.

Splaktar
  • 5,506
  • 5
  • 43
  • 74
1

I agree we should test for capabilities, but it's hard to find a simple answer to "what capabilities are supported by 'modern browsers' but not 'old browsers'?"

So I fired up a bunch of browsers and inspected Modernizer directly. I added a few capabilities I definitely require, and then I added "inputtypes.color" because that seems to cover all the major browsers I care about: Chrome, Firefox, Opera, Edge...and NOT IE11. Now I can gently suggest the user would be better off with Chrome/Opera/Firefox/Edge.

This is what I use - you can edit the list of things to test for your particular case. Returns false if any of the capabilities are missing.

/**
 * Check browser capabilities.
 */
public CheckBrowser(): boolean
{
    let tests = ["csstransforms3d", "canvas", "flexbox", "webgl", "inputtypes.color"];

    // Lets see what each browser can do and compare...
    //console.log("Modernizr", Modernizr);

    for (let i = 0; i < tests.length; i++)
    {
        // if you don't test for nested properties then you can just use
        // "if (!Modernizr[tests[i]])" instead
        if (!ObjectUtils.GetProperty(Modernizr, tests[i]))
        {
            console.error("Browser Capability missing: " + tests[i]);
            return false;
        }
    }

    return true;
}

And here is that GetProperty method which is needed for "inputtypes.color".

/**
 * Get a property value from the target object specified by name.
 * 
 * The property name may be a nested property, e.g. "Contact.Address.Code".
 * 
 * Returns undefined if a property is undefined (an existing property could be null).
 * If the property exists and has the value undefined then good luck with that.
 */
public static GetProperty(target: any, propertyName: string): any
{
    if (!(target && propertyName))
    {
        return undefined;
    }

    var o = target;

    propertyName = propertyName.replace(/\[(\w+)\]/g, ".$1");
    propertyName = propertyName.replace(/^\./, "");

    var a = propertyName.split(".");

    while (a.length)
    {
        var n = a.shift();

        if (n in o)
        {
            o = o[n];

            if (o == null)
            {
                return undefined;
            }
        }
        else
        {
            return undefined;
        }
    }

    return o;
}
Etherman
  • 1,777
  • 1
  • 21
  • 34
0

You can use the < !-- [if IE] > hack to set a global js variable that then gets tested in your normal js code. A bit ugly but has worked well for me.

crazy4groovy
  • 125
  • 1
  • 5