How to create a polyfill for

CSS Filters

Bottom up

What is a Filter?

Works with videos, too?

Yes, it does!

Nice! How can I use it?

CSS Filter Effects

BLURRY

HTML
<h1>BLURRY</h1>
CSS
h1 {
    filter: blur(8px);
}
CSS
E {
    filter: blur(8px);
}
CSS
E {
    filter: grayscale(1);
}
CSS
E {
    filter: sepia(1);
}
CSS
E {
    filter: brightness(100%);
}
CSS
E {
    filter: invert(1);
}

Great. So we're good?

Nope!

-webkit-

-moz-

-ms-

-o-

CSS Filter Effects

So those were the bad guys

Who are the good guys?

To the rescue he comes

Polyfill

Allows to write once

Use many

Allows to use latest technologies

Cross-Platform

Allows the developer to focus

On what matters

Polyfill

Ingredients

  • A technology
  • In-depth knowledge of current implementations
  • Various browser support
  • Silent interface to client code

The Technology

CSS
property: parameter(value) parameter(value) ...;
filter: blur(4px) grayscale(1);

Alt. Implementations

CSS Filter Effects SVG MS Visual Filters
* Excludes IE10

Silent Interfacing

  • Less resources, less bandwidth
  • Do not include third-party libraries
  • Minimize implementation requirements

We're at the river

Let's build the bridge now

1. Collect page stylesheets

JavaScript
_collectStyles: function() {
    var stylesheets;
    if (document.querySelectorAll) {
        stylesheets = document.querySelectorAll('style,link[rel="stylesheet"]')
    } else {
        stylesheets = document.getElementsByTagName('*');
    }

    for(var i = 0; i < stylesheets.length; i++){
        switch(stylesheets[i].nodeName.toLowerCase()) {
            default:
            break;

            case 'style':
                this._allStyles.push({
                    media:      stylesheets[i].media || 'all',
                    content:    stylesheets[i].innerHTML
                });
            break;

            ...

JavaScript
            ...

            case 'link':
                if (stylesheets[i].rel.toLowerCase() !== 'stylesheet')
                    break;

                var lastPos = this._allStyles.length;

                this._allStyles.push({
                    media:      stylesheets[i].media || 'all'
                });

                var href = stylesheets[i].href;
                this._fetchStylesheet(href, lastPos);
            break;
        }
    }

    if (this._fetchQueue == 0) {
        this.parseStylesheets();
    }
},

2. Manage a fetch queue

JavaScript
_fetchStylesheet: function(url, index) {
    this._fetchQueue++;
    this._xmlHttp.open('GET', url, true);

    this._xmlHttp.onreadystatechange = function() {
        if (filllter._xmlHttp.readyState == 4) {
            if (!filllter._allStyles[index].content) {
                filllter._allStyles[index].content = filllter._xmlHttp.responseText;
                this._advanceQueue();
            }
        }
    }

    try {
        this._xmlHttp.send(null);
    } catch(e) {
        if (!filllter._allStyles[index].content) {
            this._advanceQueue();
        }
    }
},
JavaScript
_advanceQueue: function() {
    filllter._fetchQueue--;
    if (filllter._fetchQueue == 0) {

        filllter.parseStylesheets();

    }
},

3. Parse the fetched CSS

JavaScript
parseStylesheets: function(){
    var parser = new CSSParser();

    for(var i = 0; i < this._allStyles.length; i++) {
        var styles = "";
        var source = parser.parse(this._allStyles[i].content, false, true);

        if (source !== null) {
            for (var ruleIndex in source.cssRules) {
                var style = source.cssRules[ruleIndex];
                styles += this._doMagic(style) + '}';
            }
        }

        ...

JavaScript
        ...

        var styleElement = document.createElement('style');
        var mediaType = this._allStyles[i].media;
        styleElement.setAttribute('media', mediaType);
        document.getElementsByTagName('head')[0].appendChild(styleElement);

        try {
            styleElement.innerHTML = styles;
        } catch {
            styleElement.styleSheet.cssText = styles; // for IE
        }
    }
},

4. The magic (adaptation)

JavaScript
style['standards'] = ['blur(' + filterValue + ')'];
style['svg'] = [filllter._createSVGFilter('grayscale', filterValue)];
style['ms'] = ['progid:DXImageTransform.Microsoft.Blur(pixelradius=' + filterValue + ')'];
JavaScript
_createSVGFilter: function(filterType, filterValue) {
    var svgFilterElement = document.createElementNS('http://www.w3.org/2000/svg', 'feColorMatrix');

    for (attrIndex in this._svgFilterAttributes[filterType]) {
        svgFilterElement.setAttributeNS(null, attrIndex, _svgFilterAttributes[attrIndex]);
    }

    return svgFilterElement;
},
JavaScript
_svgFilterAttributes = {
    grayscale: (0.2126 + 0.7874 * (1 - amount)) + ' ' 
                + (0.7152 - 0.7152 * (1 - amount)) + ' ' 
                + (0.0722 - 0.0722 * (1 - amount)) + ' 0 0 ' 
                + (0.2126 - 0.2126 * (1 - amount)) + ' ' 
                + (0.7152 + 0.2848 * (1 - amount)) + ' ' 
                + (0.0722 - 0.0722 * (1 - amount)) + ' 0 0 ' 
                + (0.2126 - 0.2126 * (1 - amount)) + ' ' 
                + (0.7152 - 0.7152 * (1 - amount)) + ' ' 
                + (0.0722 + 0.9278 * (1 - amount)) + ' 0 0 0 0 0 1 0',

    sepia:     (0.393 + 0.607 * (1 - amount)) + ' ' 
                + (0.769 - 0.769 * (1 - amount)) + ' ' 
                + (0.189 - 0.189 * (1 - amount)) + ' 0 0 ' 
                + (0.349 - 0.349 * (1 - amount)) + ' ' 
                + (0.686 + 0.314 * (1 - amount)) + ' ' 
                + (0.168 - 0.168 * (1 - amount)) + ' 0 0 '
                + (0.272 - 0.272 * (1 - amount)) + ' ' 
                + (0.534 - 0.534 * (1 - amount)) + ' ' 
                + (0.131 + 0.869 * (1 - amount)) + ' 0 0 0 0 0 1 0'
},

5. Ignite the machine

JavaScript
if (window.addEventListener) {
    document.addEventListener('DOMContentLoaded', function() {
        filllter._collectStyles();
    }, false);
} else if (window.attachEvent) {
    window.attachEvent('onload', function() {
        filllter._collectStyles();
    });
}

Thank You

Yogev Ahuvia