├── .jshintrc ├── README.md ├── bower.json ├── examples ├── index.html ├── withlink.css └── withlink.html ├── image-set-polyfill.js ├── image-set-polyfill.min.js └── package.json /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Settings 3 | "passfail" : true, // Stop on first error. 4 | "maxerr" : 100, // Maximum error before stopping. 5 | 6 | 7 | // Predefined globals whom JSHint will ignore. 8 | "browser" : true, // Standard browser globals e.g. `window`, `document`. 9 | 10 | // Development. 11 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 12 | "devel" : false, // Allow developments statements e.g. `console.log();`. 13 | 14 | 15 | // ECMAScript 5. 16 | "es5" : false, // Allow ECMAScript 5 syntax. 17 | "strict" : false, // Require `use strict` pragma in every file. 18 | "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 19 | 20 | 21 | // The Good Parts. 22 | "unused" : true, 23 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 24 | "laxbreak" : true, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 25 | "bitwise" : false, // Prohibit bitwise operators (&, |, ^, etc.). 26 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 27 | "curly" : true, // Require {} for every new block or scope. 28 | "eqeqeq" : true, // Require triple equals i.e. `===`. 29 | "eqnull" : true, // Tolerate use of `== null`. 30 | "evil" : true, // Tolerate use of `eval`. 31 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 32 | "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`. 33 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 34 | "latedef" : false, // Prohipit variable use before definition. 35 | "loopfunc" : false, // Allow functions to be defined within loops. 36 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 37 | "regexp" : false, // Prohibit `.` and `[^...]` in regular expressions. 38 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 39 | "scripturl" : true, // Tolerate script-targeted URLs. 40 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 41 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 42 | "undef" : true, // Require all non-global variables be declared before they are used. 43 | 44 | 45 | // Personal styling preferences. 46 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 47 | "noempty" : true, // Prohibit use of empty blocks. 48 | "nonew" : true, // Prohibit use of constructors for side-effects. 49 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 50 | "onevar" : true, // Allow only one `var` statement per function. 51 | "plusplus" : false, // Prohibit use of `++` & `--`. 52 | "sub" : true, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 53 | "trailing" : true, // Prohibit trailing whitespaces. 54 | "white" : true, // Check against strict whitespace and indentation rules. 55 | "indent" : 4 // Specify indentation spacing 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## image-set polyfill 2 | 3 | Polyfill for css4 function `image-set()` (http://dev.w3.org/csswg/css-images/#image-set-notation) 4 | 5 | ## Install 6 | You can install `image-set-polyfill` with `bower` or with `npm`: 7 | 8 | bower: 9 | ```sh 10 | bower install --save image-set-polyfill 11 | ``` 12 | npm: 13 | ```sh 14 | npm install --save image-set-polyfill 15 | ``` 16 | 17 | ## Usage 18 | You should only include `image-set-polyfill.js` on your page 19 | 20 | ```html 21 | 22 | ``` 23 | 24 | ## Supports 25 | 26 | CSS styles 27 | ```css 28 | .smart-image 29 | { 30 | background-image: -webkit-image-set( 31 | url('http://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Cat_poster_2.jpg/297px-Cat_poster_2.jpg') 1.0x, 32 | url('http://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Cat_poster_2.jpg/594px-Cat_poster_2.jpg') 2.0x 33 | ); 34 | } 35 | ``` 36 | 37 | Inline styles 38 | ```html 39 |
40 | ``` 41 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-set-polyfill", 3 | "main": "image-set-polyfill.js", 4 | "version": "0.1.5", 5 | "homepage": "https://github.com/wtfil/image-set-polyfill", 6 | "authors": [ 7 | "Evgeniy Filatov" 8 | ], 9 | "description": "polyfill for css4 image-set() function", 10 | "keywords": [ 11 | "css", 12 | "javascript", 13 | "polyfill", 14 | "image-set" 15 | ], 16 | "license": "MIT", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 29 | 30 | 31 |
32 |

Static

33 |
34 |
35 |
36 |
37 |

Dynamic child

38 |
39 |
40 |
41 |

Dynamic attributes

42 |
43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/withlink.css: -------------------------------------------------------------------------------- 1 | .test { 2 | width: 200px; 3 | height: 200px; 4 | background-size: 100%; 5 | background-image: -webkit-image-set(url("http://upload.wikimedia.org/wikipedia/commons/2/22/Turkish_Van_Cat.jpg") 1.0x, url(http://img.wallbeam.com/53processed/little%20cat%20hd%20wallpapers.jpg) 2.0x); 6 | } 7 | -------------------------------------------------------------------------------- /examples/withlink.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /image-set-polyfill.js: -------------------------------------------------------------------------------- 1 | (function (window, document) { 2 | 3 | var EMPTY = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 4 | IMAGE_SET_RE_TEPMLATE = 'url\\([\'"]?([^\'"\\)]+)[\'"]?\\)\\s*([\\d\\.]+)x?', 5 | GET_IMAGE_SET_RE = new RegExp(IMAGE_SET_RE_TEPMLATE, 'g'), 6 | GET_IMAGE_SET_IMAGE_URL_RE = new RegExp(IMAGE_SET_RE_TEPMLATE, ''), 7 | forEach = Array.prototype.forEach, 8 | devicePixelRatio = window.devicePixelRatio || 1; 9 | 10 | /** 11 | * image-set() feature detect 12 | * 13 | * @returns {Boolean} 14 | */ 15 | function testImageSet() { 16 | var elem = document.createElement('div'), 17 | style = [ 18 | 'background-image: -webkit-image-set(url(' + EMPTY + ') 1.0x)', 19 | 'background-image: image-set(url(' + EMPTY + ') 1.0x)' 20 | ].join(';'), 21 | support; 22 | 23 | elem.setAttribute('style', style); 24 | document.body.appendChild(elem); 25 | support = GET_IMAGE_SET_RE.test(window.getComputedStyle(elem).background); 26 | elem.parentNode.removeChild(elem); 27 | 28 | return support; 29 | } 30 | 31 | /** 32 | * Gerring background from inline styles 33 | * 34 | * @returns {String} 35 | */ 36 | function getBackground(elem) { 37 | var style = elem.getAttribute('style'), 38 | bgs; 39 | if (!style) { 40 | return ''; 41 | } 42 | bgs = style.split(/\s*;\s*/).filter(function (s) { 43 | return s.indexOf('background') !== -1; 44 | }); 45 | if (bgs.length) { 46 | return bgs.pop(); 47 | } 48 | return ''; 49 | } 50 | 51 | /** 52 | * Getting best image for current DPI 53 | * 54 | * @param {Array} array of devicePixelRatio - image url pairs 55 | * [[1, 'url1'], [2, 'url2']] 56 | * 57 | * @returns {String} 'url(url1)' 58 | */ 59 | function getBestSize(sizes) { 60 | var key, set; 61 | 62 | for (key in sizes) { 63 | if (sizes.hasOwnProperty(key)) { 64 | set = sizes[key]; 65 | if (set[0] >= devicePixelRatio) { 66 | break; 67 | } 68 | } 69 | } 70 | 71 | return 'url(' + set[1] + ')'; 72 | } 73 | 74 | /** 75 | * Trying polyfill DOM node 76 | * 77 | * @param {HTMLElement} 78 | */ 79 | function polyfillNode(elem) { 80 | var bg = getBackground(elem), 81 | match = bg.match(GET_IMAGE_SET_RE), 82 | sizes = [], 83 | best; 84 | 85 | GET_IMAGE_SET_RE.lastIndex = 0; 86 | while ((match = GET_IMAGE_SET_RE.exec(bg))) { 87 | sizes.push([ 88 | Number(match[2]), 89 | match[1] 90 | ]); 91 | } 92 | 93 | if (!sizes.length) { 94 | return; 95 | } 96 | 97 | best = getBestSize(sizes); 98 | elem.style.backgroundImage = best; 99 | } 100 | 101 | /** 102 | * Making polyfilled styles based on non-polyfilled 103 | * 104 | * @param {String} styles 105 | * 106 | * @return {String} new styles 107 | */ 108 | function getPolyfilledStyles(styles) { 109 | var findImageSetRE = /(?:\-(?:webkit|moz)\-)?image\-set\(([^;}]*)\)/mg, 110 | newStyles = '', 111 | lastIndex = 0, 112 | match, urls, best; 113 | 114 | function urlMap(item) { 115 | var m = item.match(GET_IMAGE_SET_IMAGE_URL_RE); 116 | return [ 117 | Number(m[2]), 118 | m[1] 119 | ]; 120 | } 121 | 122 | while ((match = findImageSetRE.exec(styles))) { 123 | urls = match[1].split(/\s*,\s*/).map(urlMap); 124 | best = getBestSize(urls); 125 | 126 | newStyles += styles.slice(lastIndex, match.index) + best; 127 | lastIndex = match.index + match[0].length; 128 | } 129 | newStyles += styles.slice(lastIndex); 130 | return newStyles; 131 | } 132 | 133 | /** 134 | * Trying modify current styles 135 | * 136 | * @param {HTMLStyleElement} 137 | */ 138 | function polyfillStyle(styleTag) { 139 | styleTag.innerHTML = getPolyfilledStyles(styleTag.innerHTML); 140 | } 141 | 142 | /** 143 | * Trying polyfill link[rel="stylesheet"] tag 144 | * 145 | * @param {HTMLLinkElement} 146 | */ 147 | function polifillLink(link) { 148 | var xhr = new XMLHttpRequest(); 149 | xhr.onreadystatechange = function () { 150 | var styleText, styleTag; 151 | if (xhr.readyState === 4) { 152 | styleText = getPolyfilledStyles(xhr.responseText); 153 | styleTag = document.createElement('style'); 154 | 155 | styleTag.innerHTML = styleText; 156 | link.parentNode.replaceChild(styleTag, link); 157 | } 158 | }; 159 | xhr.open('get', link.href); 160 | xhr.send(); 161 | } 162 | 163 | /** 164 | * Running on document load 165 | */ 166 | function main() { 167 | // replacing inline styles 168 | forEach.call(document.querySelectorAll('*'), polyfillNode); 169 | 170 | // replacing css styles 171 | forEach.call(document.querySelectorAll('style'), polyfillStyle); 172 | 173 | forEach.call(document.querySelectorAll('link[rel="stylesheet"]'), polifillLink); 174 | 175 | } 176 | 177 | /** 178 | * Make polyfill on new nodes and after modify styles 179 | */ 180 | function eventsAttach() { 181 | var appendChild = HTMLElement.prototype.appendChild, 182 | setAttribute = HTMLElement.prototype.setAttribute, 183 | observer; 184 | 185 | if ('MutationObserver' in window) { 186 | observer = new MutationObserver(function (mutations) { 187 | mutations.forEach(function (mutation) { 188 | if (mutation.type === 'childList') { 189 | [].forEach.call(mutation.addedNodes, polyfillNode); 190 | } else if (mutation.type === 'attributes') { 191 | polyfillNode(mutation.target); 192 | } 193 | }); 194 | }); 195 | observer.observe(document, { 196 | subtree: true, 197 | attributes: true, 198 | childList: true, 199 | characterData: true 200 | }); 201 | } else { 202 | HTMLElement.prototype.appendChild = function (elem) { 203 | polyfillNode(elem); 204 | return appendChild.apply(this, arguments); 205 | }; 206 | HTMLElement.prototype.setAttribute = function (name, val) { 207 | var r = setAttribute.apply(this, arguments); 208 | if (name === 'style' && val.indexOf('background') !== -1) { 209 | polyfillNode(this); 210 | } 211 | return r; 212 | }; 213 | } 214 | } 215 | 216 | document.addEventListener('DOMContentLoaded', function () { 217 | if (testImageSet()) { 218 | return; 219 | } 220 | 221 | main(); 222 | eventsAttach(); 223 | }); 224 | 225 | }(window, document)); 226 | -------------------------------------------------------------------------------- /image-set-polyfill.min.js: -------------------------------------------------------------------------------- 1 | (function(window,document){var EMPTY="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",IMAGE_SET_RE_TEPMLATE="url\\(['\"]?([^'\"\\)]+)['\"]?\\)\\s*([\\d\\.]+)x?",GET_IMAGE_SET_RE=new RegExp(IMAGE_SET_RE_TEPMLATE,"g"),GET_IMAGE_SET_IMAGE_URL_RE=new RegExp(IMAGE_SET_RE_TEPMLATE,""),forEach=Array.prototype.forEach,devicePixelRatio=window.devicePixelRatio||1;function testImageSet(){var elem=document.createElement("div"),style=["background-image: -webkit-image-set(url("+EMPTY+") 1.0x)","background-image: image-set(url("+EMPTY+") 1.0x)"].join(";"),support;elem.setAttribute("style",style);document.body.appendChild(elem);support=GET_IMAGE_SET_RE.test(window.getComputedStyle(elem).background);elem.parentNode.removeChild(elem);return support}function getBackground(elem){var style=elem.getAttribute("style"),bgs;if(!style){return""}bgs=style.split(/\s*;\s*/).filter(function(s){return s.indexOf("background")!==-1});if(bgs.length){return bgs.pop()}return""}function getBestSize(sizes){var key,set;for(key in sizes){if(sizes.hasOwnProperty(key)){set=sizes[key];if(set[0]>=devicePixelRatio){break}}}return"url("+set[1]+")"}function polyfillNode(elem){var bg=getBackground(elem),match=bg.match(GET_IMAGE_SET_RE),sizes=[],best;GET_IMAGE_SET_RE.lastIndex=0;while(match=GET_IMAGE_SET_RE.exec(bg)){sizes.push([Number(match[2]),match[1]])}if(!sizes.length){return}best=getBestSize(sizes);elem.style.backgroundImage=best}function getPolyfilledStyles(styles){var findImageSetRE=/(?:\-(?:webkit|moz)\-)?image\-set\(((?:a|[^a])*)\)/gm,newStyles="",lastIndex=0,match,urls,best;function urlMap(item){var m=item.match(GET_IMAGE_SET_IMAGE_URL_RE);return[Number(m[2]),m[1]]}while(match=findImageSetRE.exec(styles)){urls=match[1].split(/\s*,\s*/).map(urlMap);best=getBestSize(urls);newStyles+=styles.slice(lastIndex,match.index)+best;lastIndex=match.index+match[0].length}newStyles+=styles.slice(lastIndex);return newStyles}function polyfillStyle(styleTag){styleTag.innerHTML=getPolyfilledStyles(styleTag.innerHTML)}function polifillLink(link){var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){var styleText,styleTag;if(xhr.readyState===4){styleText=getPolyfilledStyles(xhr.responseText);styleTag=document.createElement("style");styleTag.innerHTML=styleText;link.parentNode.replaceChild(styleTag,link)}};xhr.open("get",link.href);xhr.send()}function main(){forEach.call(document.querySelectorAll("*"),polyfillNode);forEach.call(document.querySelectorAll("style"),polyfillStyle);forEach.call(document.querySelectorAll('link[rel="stylesheet"]'),polifillLink)}function eventsAttach(){var div=document.createElement("div"),supported=false,appendChild=HTMLElement.prototype.appendChild,setAttribute=HTMLElement.prototype.setAttribute;div.addEventListener("DOMNodeInserted",function(){supported=true});div.appendChild(div.cloneNode(true));if(supported){window.addEventListener("DOMNodeInserted",function(e){polyfillNode(e.originalTarget)},false);window.addEventListener("DOMAttrModified",function(e){polyfillNode(e.originalTarget)},false)}else{HTMLElement.prototype.appendChild=function(elem){polyfillNode(elem);return appendChild.apply(this,arguments)};HTMLElement.prototype.setAttribute=function(name,val){var r=setAttribute.apply(this,arguments);if(name==="style"&&val.indexOf("background")!==-1){polyfillNode(this)}return r}}}document.addEventListener("DOMContentLoaded",function(){if(testImageSet()){return}main();eventsAttach()})})(window,document); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-set-polyfill", 3 | "version": "0.1.5", 4 | "description": "polyfill for css4 image-set() function", 5 | "keywords": [ 6 | "css", 7 | "javascript", 8 | "polyfill", 9 | "image-set" 10 | ], 11 | "author": { 12 | "name": "Evgeniy Filatov" 13 | }, 14 | "scripts": { 15 | "min": "npm install --save-dev && ./node_modules/.bin/uglifyjs image-set-polyfill.js > image-set-polyfill.min.js" 16 | }, 17 | "devDependencies": { 18 | "uglify-js": "*" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/wtfil/image-set-polyfill.git" 23 | }, 24 | "main": "image-set-polyfill.js" 25 | } 26 | --------------------------------------------------------------------------------