matchMedia

Benchmark created on


Setup

// Media
                (function(win) {
                    'use strict';
    
                    var _doc             = win.document,
                        _mediaInfo       = _doc.getElementsByTagName('head')[0],
                        _mediaInfoStyle  = '',
                        _viewport        = _doc.documentElement,
                        _typeList        = 'all, screen, print, speech, projection, handheld, tv, braille, embossed, tty',
                        _mqlID           = 0,
                        _timer           = 0;
    
                    // Helper methods
    
                    /*
                        MediaType
                        A single instance of a media type, ex. not screen or screen
                    */
                    function MediaType(name, negate) {
                        return {
                            name        : name || 'all',    // screen, print, ...
                            toString    : function toString() { 
                                return this.name === Media.type;
                            }
                        };
                    };
    
                    /*
                        MediaExpr
                        A single instance of a media expression, ex. (max-width: 400px)
                    */
                    function MediaExpr(name, value, type) {
                        var feature = Media.features[name];
    
                        return {
                            name        : name,
                            value       : value,
                            type        : type,
                            toString    : function toString() {
                                var absValue = Media.getAbsValue(this.value);
    
                                if (this.type === 'min') {
                                    return feature > absValue;
                                }
    
                                if (this.type === 'max') {
                                    return feature < absValue;
                                }
    
                                if (this.value !== 'undefined') {
                                    return feature === absValue;
                                }
    
                                return Number(feature);
                            }
                        };
                    };
    
                    /*
                        MediaQueryList
                        A list of parsed media queries, ex. screen and (max-width: 400px), screen and (max-width: 800px)
                    */
                    function MediaQueryList(media) {
                        var _id = _mqlID++;
    
                        Media.queries[_id] = {listeners: []};
    
                        return {
                            matches         : Media.parseMatch(media, false),
                            media           : media,
                            addListener     : function addListener(listener) {
                                Media.queries[_id].mql || (Media.queries[_id].mql = this);
                                Media.queries[_id].listeners.push(listener);
                            },
                            removeListener  : function removeListener(listener) {
                                var query = Media.queries[_id];
    
                                if (!query) {
                                    return;
                                }
    
                                for (var i = 0, il = query.listeners.length; i < il; i++) {
                                    if (query.listeners[i] === listener) {
                                        query.listeners.splice(i, 1);
                                    }
                                }
                            }
                        };
                    };
    
                    /*
                        getStyle
                        Gets computed/current style of the target element.
                    */
                    function getStyle(target, property, psuedo) {
                        return win.getComputedStyle ? 
                            win.getComputedStyle(target, psuedo || null).getPropertyValue(property) : 
                            target.currentStyle[property.replace(/-([a-z])/g, function (p, p1) {
                                return p1.toUpperCase();
                            })];
                    };
    
                    win.Media = {
                        // Properties
                        supported   : parseFloat(getStyle(_mediaInfo, 'height')) === 1,
                        type        : _typeList.split(', ')[parseFloat(getStyle(_mediaInfo, 'z-index'))],
                        queries     : [],
                        features    : {
                            "width"                 : 0, /* Update on resize */
                            "height"                : 0, /* Update on resize */
                            "aspect-ratio"          : 0, /* Update on resize */
                            "color"                 : screen.colorDepth,
                            "color-index"           : Math.pow(2, screen.colorDepth),
                            "device-aspect-ratio"   : win.devicePixelRatio || (screen.width / screen.height).toFixed(2),
                            "device-width"          : screen.width,
                            "device-height"         : screen.height,
                            "monochrome"            : Number(screen.colorDepth == 2),
                            "orientation"           : "landscape", /* Update on orientation change */
                            "resolution"            : screen.deviceXDPI || parseFloat(getStyle(_mediaInfo, 'width'))
                        },
    
                        // Methods
    
                        /*
                            getAbsValue
                         */
                        getAbsValue: function(data) {
                            var match;
    
                            // Convert length unit to pixels
                            if ((match = data.match(/([\d\.]+)(px|em|rem|%|in|cm|mm|ex|pt|pc)/))) {
                                if (match[2] == 'em') {
                                    // Assumed base font size is 16px
                                    return 16 * match[1];
                                } else if (match[2] == 'pt') {
                                    return (Media.features.resolution / 72) * match[1];
                                }
                                return parseFloat(match[1]);
                            }
    
                            // Convert aspect ratio to decimals
                            else if ((match = data.match(/(\d+)[\/:](\d+)/))) {
                                return match[1] / match[2];
                            }
    
                            // Convert resolution unit to pixels
                            else if ((match = data.match(/([\d]+)(dpi|dpcm)/))) {
                                if (match[2] == 'dpcm') {
                                    return match[1] * 0.3937;
                                }
                                return match[1];
                            }
    
                            return data;
                        },
    
                        /*
                            parseMatch
                         */
                        parseMatch: function(media, matched) {
                            var list    = typeof media === 'string' ? media.split(', ') : media,
                                negate  = (/\s*not\s*/).test(list[list.length - 1]),
                                mq      = list.pop(),
                                mt      = 'all',
                                expr    = mq.split(' and ') || [],
                                match   = !negate,
                                exprl   = expr.length - 1;
    
                            do {
                                var item = null;
    
                                // Test for 'not screen' and (max-width: 400px).
                                // Evaluate each item, then call parseMatch() if there are more media queries or return value of negate.
                                if (expr[exprl].indexOf('(') === -1) {
                                    mt = new MediaType(expr[exprl].match(/(not)?\s*(\w*)/)[2]);
    
                                    if (!mt.toString()) {
                                        return (list.length ? this.parseMatch(list, matched) : negate);
                                    } else {
                                        continue;
                                    }
                                }
    
                                if ((item = expr[exprl].match(/\(\s*(min|max)?-?([^:\s]+)\s*:\s*([^\s]+)\s*\)/)) && (!MediaExpr(item[2], item[3], item[1]).toString())) {
                                    return (list.length ? this.parseMatch(list, matched) : negate);
                                }
                            } while(exprl--);
    
                            return (matched && match && {matches: match, type: (negate ? _typeList.split(mt).join(', ').replace(/(,\s){2}/, '') : mt), media: mq}) || match;
                        },
    
                        /*
                            match
                         */
                        match: function(media) {
                            return new MediaQueryList(media);
                        },
    
                        /*
                            watch
                         */
                        watch: function(evt) {
                            clearTimeout(_timer);
    
                            _timer = setTimeout(function() {
                                Media.setMutableFeatures();
                                var id = _mqlID;
    
                                do {
                                    var query = Media.queries[id],
                                        match = false;
    
                                    if (typeof query === 'undefined') { continue; }
    
                                    match = Media.parseMatch(query.mql.media);
    
                                    if ((match && !query.mql.matches) || (!match && query.mql.matches)) {
                                        query.mql.matches = match;
    
                                        for (var i = 0, il = query.listeners.length; i < il; i++) {
                                            if (query.listeners[i]) {
                                                query.listeners[i].call(win, query.mql, evt);
                                            }
                                        }
                                    }
    
                                } while(id--);
                            }, 10);
                        },
    
                        /*
                            Sets properties of Media that change on resize and/or orientation.
                        */
                        setMutableFeatures: function() {
                            Media.features.width            = win.innerWidth || _viewport.clientWidth;
                            Media.features.height           = win.innerHeight || _viewport.clientHeight;
                            Media.features['aspect-ratio']  = (Media.features.width / Media.features.height).toFixed(2);
                        },
    
                        listen: function(listener) {
                            if (win.addEventListener) {
                                win.addEventListener('resize', listener);
                                win.addEventListener('orientationchange', listener);
                            } else if (win.attachEvent) {
                                win.attachEvent('onresize', listener);
                                win.attachEvent('onorientationchange', listener);
                            }
                        }
                    };
    
                    Media.setMutableFeatures();
                    Media.listen(Media.watch);
                })(window);
    
                window.matchMediaPolyfill = (function( doc, undefined ) {
    
                  "use strict";
    
                  var bool,
                      docElem = doc.documentElement,
                      refNode = docElem.firstElementChild || docElem.firstChild,
                      // fakeBody required for <FF4 when executed in <head>
                      fakeBody = doc.createElement( "body" ),
                      div = doc.createElement( "div" );
    
                  div.id = "mq-test-1";
                  div.style.cssText = "position:absolute;top:-100em";
                  fakeBody.style.background = "none";
                  fakeBody.appendChild(div);
    
                  return function(q){
    
                    div.innerHTML = "&shy;<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>";
    
                    docElem.insertBefore( fakeBody, refNode );
                    bool = div.offsetWidth === 42;
                    docElem.removeChild( fakeBody );
    
                    return {
                      matches: bool,
                      media: q
                    };
    
                  };
    
                }( document ));

Test runner

Ready to run.

Testing in
TestOps/sec
native matchMedia
window.matchMedia('screen and (min-width: 600px) and (min-height: 400px), screen and (min-height: 400px)');
ready
Media.match
Media.match('screen and (min-width: 600px) and (min-height: 400px), screen and (min-height: 400px)');
ready
polyfill paulirish/scottjehl/matchMedia.js
window.matchMediaPolyfill('screen and (min-width: 600px) and (min-height: 400px), screen and (min-height: 400px)');
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.