├── .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 |
--------------------------------------------------------------------------------