picturefill-rails 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * jQuery Picture
3
+ * http://jquerypicture.com
4
+ * http://github.com/Abban/jQuery-Picture
5
+ *
6
+ * May 2012
7
+ *
8
+ * @version 0.9
9
+ * @author Abban Dunne http://abandon.ie
10
+ * @license MIT
11
+ *
12
+ * jQuery Picture is a plugin to add support for responsive images to your layouts.
13
+ * It supports both figure elements with some custom data attributes and the new
14
+ * proposed picture format. This plugin will be made redundant when the format is
15
+ * approved and implemented by browsers. Lets hope that happens soon. In the meantime
16
+ * this plugin will be kept up to date with latest developments.
17
+ *
18
+ */(function(e){e.fn.picture=function(t){var n={container:null},r=e.extend({},n,t);this.each(function(){function a(o){if(o)if(s.get(0).tagName.toLowerCase()=="figure"){var a=s.data();e.each(a,function(e){var n;n=e.replace(/[^\d.]/g,"");n&&t.push(n)})}else s.find("source").each(function(){var n,r;n=e(this).attr("media");if(n){r=n.replace(/[^\d.]/g,"");t.push(r)}});var c=0;r.container==null?n=e(window).width()*u:n=e(r.container).width()*u;e.each(t,function(e,t){parseInt(n)>=parseInt(t)&&parseInt(c)<=parseInt(t)&&(c=t)});if(i!==c){i=c;s.get(0).tagName.toLowerCase()=="figure"?l():f()}}function f(){var t=new Object;s.find("source").each(function(){var n,r,i;n=e(this).attr("media");r=e(this).attr("src");n?i=n.replace(/[^\d.]/g,""):i=0;t[i]=r});if(s.find("img").length==0){var n='<img src="'+t[i]+'" style="'+s.attr("style")+'" alt="'+s.attr("alt")+'">';s.find("a").length==0?s.append(n):s.find("a").append(n)}else s.find("img").attr("src",t[i])}function l(){var t=new Object,n=s.data();e.each(n,function(e,n){var r;r=e.replace(/[^\d.]/g,"");r||(r=0);t[r]=n});if(s.find("img").length==0){var r='<img src="'+t[i]+'" alt="'+s.attr("title")+'">';s.find("a").length==0?s.prepend(r):s.find("a").prepend(r)}else s.find("img").attr("src",t[i])}var t=new Array,n,i,s,o,u=1;window.devicePixelRatio!==undefined&&(u=window.devicePixelRatio);s=e(this);a(!0);o=!1;e(window).resize(function(){o!==!1&&clearTimeout(o);o=setTimeout(a,200)})})}})(jQuery);
@@ -0,0 +1,60 @@
1
+ /*! Picturefill - Responsive Images that work today. (and mimic the proposed Picture element with divs). Author: Scott Jehl, Filament Group, 2012 | License: MIT/GPLv2 */
2
+
3
+ (function( w ){
4
+
5
+ // Enable strict mode
6
+ "use strict";
7
+
8
+ w.picturefill = function() {
9
+ var ps = w.document.getElementsByTagName( "div" );
10
+
11
+ // Loop the pictures
12
+ for( var i = 0, il = ps.length; i < il; i++ ){
13
+ if( ps[ i ].getAttribute( "data-picture" ) !== null ){
14
+
15
+ var sources = ps[ i ].getElementsByTagName( "div" ),
16
+ matches = [];
17
+
18
+ // See if which sources match
19
+ for( var j = 0, jl = sources.length; j < jl; j++ ){
20
+ var media = sources[ j ].getAttribute( "data-media" );
21
+ // if there's no media specified, OR w.matchMedia is supported
22
+ if( !media || ( w.matchMedia && w.matchMedia( media ).matches ) ){
23
+ matches.push( sources[ j ] );
24
+ }
25
+ }
26
+
27
+ // Find any existing img element in the picture element
28
+ var picImg = ps[ i ].getElementsByTagName( "img" )[ 0 ];
29
+
30
+ if( matches.length ){
31
+ if( !picImg ){
32
+ picImg = w.document.createElement( "img" );
33
+ picImg.alt = ps[ i ].getAttribute( "data-alt" );
34
+ ps[ i ].appendChild( picImg );
35
+ }
36
+
37
+ picImg.src = matches.pop().getAttribute( "data-src" );
38
+ }
39
+ else if( picImg ){
40
+ ps[ i ].removeChild( picImg );
41
+ }
42
+ }
43
+ }
44
+ };
45
+
46
+ // Run on resize and domready (w.load as a fallback)
47
+ if( w.addEventListener ){
48
+ w.addEventListener( "resize", w.picturefill, false );
49
+ w.addEventListener( "DOMContentLoaded", function(){
50
+ w.picturefill();
51
+ // Run once only
52
+ w.removeEventListener( "load", w.picturefill, false );
53
+ }, false );
54
+ w.addEventListener( "load", w.picturefill, false );
55
+ }
56
+ else if( w.attachEvent ){
57
+ w.attachEvent( "onload", w.picturefill );
58
+ }
59
+
60
+ }( this ));
@@ -0,0 +1,2 @@
1
+ /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
2
+ window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.appendChild(g);return function(h){g.innerHTML='&shy;<style media="'+h+'"> #mq-test-1 { width: 42px; }</style>';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document);
@@ -0,0 +1,755 @@
1
+ /*!
2
+ * jsUri v1.1.1
3
+ * https://github.com/derek-watson/jsUri
4
+ *
5
+ * Copyright 2011, Derek Watson
6
+ * Released under the MIT license.
7
+ * http://jquery.org/license
8
+ *
9
+ * Includes parseUri regular expressions
10
+ * http://blog.stevenlevithan.com/archives/parseuri
11
+ * Copyright 2007, Steven Levithan
12
+ * Released under the MIT license.
13
+ *
14
+ * Date: Mon Nov 14 20:06:34 2011 -0800
15
+ */
16
+
17
+
18
+ var Query = function (queryString) {
19
+
20
+ // query string parsing, parameter manipulation and stringification
21
+
22
+ 'use strict';
23
+
24
+ var // parseQuery(q) parses the uri query string and returns a multi-dimensional array of the components
25
+ parseQuery = function (q) {
26
+ var arr = [], i, ps, p, keyval;
27
+
28
+ if (typeof (q) === 'undefined' || q === null || q === '') {
29
+ return arr;
30
+ }
31
+
32
+ if (q.indexOf('?') === 0) {
33
+ q = q.substring(1);
34
+ }
35
+
36
+ ps = q.toString().split(/[&;]/);
37
+
38
+ for (i = 0; i < ps.length; i++) {
39
+ p = ps[i];
40
+ keyval = p.split('=');
41
+ arr.push([keyval[0], keyval[1]]);
42
+ }
43
+
44
+ return arr;
45
+ },
46
+
47
+ params = parseQuery(queryString),
48
+
49
+ // toString() returns a string representation of the internal state of the object
50
+ toString = function () {
51
+ var s = '', i, param;
52
+ for (i = 0; i < params.length; i++) {
53
+ param = params[i];
54
+ if (s.length > 0) {
55
+ s += '&';
56
+ }
57
+ s += param.join('=');
58
+ }
59
+ return s.length > 0 ? '?' + s : s;
60
+ },
61
+
62
+ decode = function (s) {
63
+ s = decodeURIComponent(s);
64
+ s = s.replace('+', ' ');
65
+ return s;
66
+ },
67
+
68
+ // getParamValues(key) returns the first query param value found for the key 'key'
69
+ getParamValue = function (key) {
70
+ var param, i;
71
+ for (i = 0; i < params.length; i++) {
72
+ param = params[i];
73
+ if (decode(key) === decode(param[0])) {
74
+ return param[1];
75
+ }
76
+ }
77
+ },
78
+
79
+ // getParamValues(key) returns an array of query param values for the key 'key'
80
+ getParamValues = function (key) {
81
+ var arr = [], i, param;
82
+ for (i = 0; i < params.length; i++) {
83
+ param = params[i];
84
+ if (decode(key) === decode(param[0])) {
85
+ arr.push(param[1]);
86
+ }
87
+ }
88
+ return arr;
89
+ },
90
+
91
+ // deleteParam(key) removes all instances of parameters named (key)
92
+ // deleteParam(key, val) removes all instances where the value matches (val)
93
+ deleteParam = function (key, val) {
94
+
95
+ var arr = [], i, param, keyMatchesFilter, valMatchesFilter;
96
+
97
+ for (i = 0; i < params.length; i++) {
98
+
99
+ param = params[i];
100
+ keyMatchesFilter = decode(param[0]) === decode(key);
101
+ valMatchesFilter = decode(param[1]) === decode(val);
102
+
103
+ if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && !keyMatchesFilter && !valMatchesFilter)) {
104
+ arr.push(param);
105
+ }
106
+ }
107
+
108
+ params = arr;
109
+
110
+ return this;
111
+ },
112
+
113
+ // addParam(key, val) Adds an element to the end of the list of query parameters
114
+ // addParam(key, val, index) adds the param at the specified position (index)
115
+ addParam = function (key, val, index) {
116
+
117
+ if (arguments.length === 3 && index !== -1) {
118
+ index = Math.min(index, params.length);
119
+ params.splice(index, 0, [key, val]);
120
+ } else if (arguments.length > 0) {
121
+ params.push([key, val]);
122
+ }
123
+ return this;
124
+ },
125
+
126
+ // replaceParam(key, newVal) deletes all instances of params named (key) and replaces them with the new single value
127
+ // replaceParam(key, newVal, oldVal) deletes only instances of params named (key) with the value (val) and replaces them with the new single value
128
+ // this function attempts to preserve query param ordering
129
+ replaceParam = function (key, newVal, oldVal) {
130
+
131
+ var index = -1, i, param;
132
+
133
+ if (arguments.length === 3) {
134
+ for (i = 0; i < params.length; i++) {
135
+ param = params[i];
136
+ if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) {
137
+ index = i;
138
+ break;
139
+ }
140
+ }
141
+ deleteParam(key, oldVal).addParam(key, newVal, index);
142
+ } else {
143
+ for (i = 0; i < params.length; i++) {
144
+ param = params[i];
145
+ if (decode(param[0]) === decode(key)) {
146
+ index = i;
147
+ break;
148
+ }
149
+ }
150
+ deleteParam(key);
151
+ addParam(key, newVal, index);
152
+ }
153
+ return this;
154
+ };
155
+
156
+ // public api
157
+ return {
158
+ getParamValue: getParamValue,
159
+ getParamValues: getParamValues,
160
+ deleteParam: deleteParam,
161
+ addParam: addParam,
162
+ replaceParam: replaceParam,
163
+
164
+ toString: toString
165
+ };
166
+ };
167
+
168
+ var Uri = function (uriString) {
169
+
170
+ // uri string parsing, attribute manipulation and stringification
171
+
172
+ 'use strict';
173
+
174
+ /*global Query: true */
175
+ /*jslint regexp: false, plusplus: false */
176
+
177
+ var strictMode = false,
178
+
179
+ // parseUri(str) parses the supplied uri and returns an object containing its components
180
+ parseUri = function (str) {
181
+
182
+ /*jslint unparam: true */
183
+ var parsers = {
184
+ strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
185
+ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
186
+ },
187
+ keys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"],
188
+ q = {
189
+ name: "queryKey",
190
+ parser: /(?:^|&)([^&=]*)=?([^&]*)/g
191
+ },
192
+ m = parsers[strictMode ? "strict" : "loose"].exec(str),
193
+ uri = {},
194
+ i = 14;
195
+
196
+ while (i--) {
197
+ uri[keys[i]] = m[i] || "";
198
+ }
199
+
200
+ uri[q.name] = {};
201
+ uri[keys[12]].replace(q.parser, function ($0, $1, $2) {
202
+ if ($1) {
203
+ uri[q.name][$1] = $2;
204
+ }
205
+ });
206
+
207
+ return uri;
208
+ },
209
+
210
+ uriParts = parseUri(uriString || ''),
211
+
212
+ queryObj = new Query(uriParts.query),
213
+
214
+
215
+ /*
216
+ Basic get/set functions for all properties
217
+ */
218
+
219
+ protocol = function (val) {
220
+ if (typeof val !== 'undefined') {
221
+ uriParts.protocol = val;
222
+ }
223
+ return uriParts.protocol;
224
+ },
225
+
226
+ hasAuthorityPrefixUserPref = null,
227
+
228
+ // hasAuthorityPrefix: if there is no protocol, the leading // can be enabled or disabled
229
+ hasAuthorityPrefix = function (val) {
230
+
231
+ if (typeof val !== 'undefined') {
232
+ hasAuthorityPrefixUserPref = val;
233
+ }
234
+
235
+ if (hasAuthorityPrefixUserPref === null) {
236
+ return (uriParts.source.indexOf('//') !== -1);
237
+ } else {
238
+ return hasAuthorityPrefixUserPref;
239
+ }
240
+ },
241
+
242
+ userInfo = function (val) {
243
+ if (typeof val !== 'undefined') {
244
+ uriParts.userInfo = val;
245
+ }
246
+ return uriParts.userInfo;
247
+ },
248
+
249
+ host = function (val) {
250
+ if (typeof val !== 'undefined') {
251
+ uriParts.host = val;
252
+ }
253
+ return uriParts.host;
254
+ },
255
+
256
+ port = function (val) {
257
+ if (typeof val !== 'undefined') {
258
+ uriParts.port = val;
259
+ }
260
+ return uriParts.port;
261
+ },
262
+
263
+ path = function (val) {
264
+ if (typeof val !== 'undefined') {
265
+ uriParts.path = val;
266
+ }
267
+ return uriParts.path;
268
+ },
269
+
270
+ query = function (val) {
271
+ if (typeof val !== 'undefined') {
272
+ queryObj = new Query(val);
273
+ }
274
+ return queryObj;
275
+ },
276
+
277
+ anchor = function (val) {
278
+ if (typeof val !== 'undefined') {
279
+ uriParts.anchor = val;
280
+ }
281
+ return uriParts.anchor;
282
+ },
283
+
284
+
285
+ /*
286
+ Fluent setters for Uri uri properties
287
+ */
288
+
289
+ setProtocol = function (val) {
290
+ protocol(val);
291
+ return this;
292
+ },
293
+
294
+ setHasAuthorityPrefix = function (val) {
295
+ hasAuthorityPrefix(val);
296
+ return this;
297
+ },
298
+
299
+ setUserInfo = function (val) {
300
+ userInfo(val);
301
+ return this;
302
+ },
303
+
304
+ setHost = function (val) {
305
+ host(val);
306
+ return this;
307
+ },
308
+
309
+ setPort = function (val) {
310
+ port(val);
311
+ return this;
312
+ },
313
+
314
+ setPath = function (val) {
315
+ path(val);
316
+ return this;
317
+ },
318
+
319
+ setQuery = function (val) {
320
+ query(val);
321
+ return this;
322
+ },
323
+
324
+ setAnchor = function (val) {
325
+ anchor(val);
326
+ return this;
327
+ },
328
+
329
+ /*
330
+ Query method wrappers
331
+ */
332
+ getQueryParamValue = function (key) {
333
+ return query().getParamValue(key);
334
+ },
335
+
336
+ getQueryParamValues = function (key) {
337
+ return query().getParamValues(key);
338
+ },
339
+
340
+ deleteQueryParam = function (key, val) {
341
+ if (arguments.length === 2) {
342
+ query().deleteParam(key, val);
343
+ } else {
344
+ query().deleteParam(key);
345
+ }
346
+
347
+ return this;
348
+ },
349
+
350
+ addQueryParam = function (key, val, index) {
351
+ if (arguments.length === 3) {
352
+ query().addParam(key, val, index);
353
+ } else {
354
+ query().addParam(key, val);
355
+ }
356
+ return this;
357
+ },
358
+
359
+ replaceQueryParam = function (key, newVal, oldVal) {
360
+ if (arguments.length === 3) {
361
+ query().replaceParam(key, newVal, oldVal);
362
+ } else {
363
+ query().replaceParam(key, newVal);
364
+ }
365
+
366
+ return this;
367
+ },
368
+
369
+ /*
370
+ Serialization
371
+ */
372
+
373
+ // toString() stringifies the current state of the uri
374
+ toString = function () {
375
+
376
+ var s = '',
377
+ is = function (s) {
378
+ return (s !== null && s !== '');
379
+ };
380
+
381
+ if (is(protocol())) {
382
+ s += protocol();
383
+ if (protocol().indexOf(':') !== protocol().length - 1) {
384
+ s += ':';
385
+ }
386
+ s += '//';
387
+ } else {
388
+ if (hasAuthorityPrefix() && is(host())) {
389
+ s += '//';
390
+ }
391
+ }
392
+
393
+ if (is(userInfo()) && is(host())) {
394
+ s += userInfo();
395
+ if (userInfo().indexOf('@') !== userInfo().length - 1) {
396
+ s += '@';
397
+ }
398
+ }
399
+
400
+ if (is(host())) {
401
+ s += host();
402
+ if (is(port())) {
403
+ s += ':' + port();
404
+ }
405
+ }
406
+
407
+ if (is(path())) {
408
+ s += path();
409
+ } else {
410
+ if (is(host()) && (is(query().toString()) || is(anchor()))) {
411
+ s += '/';
412
+ }
413
+ }
414
+ if (is(query().toString())) {
415
+ if (query().toString().indexOf('?') !== 0) {
416
+ s += '?';
417
+ }
418
+ s += query().toString();
419
+ }
420
+
421
+ if (is(anchor())) {
422
+ if (anchor().indexOf('#') !== 0) {
423
+ s += '#';
424
+ }
425
+ s += anchor();
426
+ }
427
+
428
+ return s;
429
+ },
430
+
431
+ /*
432
+ Cloning
433
+ */
434
+
435
+ // clone() returns a new, identical Uri instance
436
+ clone = function () {
437
+ return new Uri(toString());
438
+ };
439
+
440
+ // public api
441
+ return {
442
+
443
+ protocol: protocol,
444
+ hasAuthorityPrefix: hasAuthorityPrefix,
445
+ userInfo: userInfo,
446
+ host: host,
447
+ port: port,
448
+ path: path,
449
+ query: query,
450
+ anchor: anchor,
451
+
452
+ setProtocol: setProtocol,
453
+ setHasAuthorityPrefix: setHasAuthorityPrefix,
454
+ setUserInfo: setUserInfo,
455
+ setHost: setHost,
456
+ setPort: setPort,
457
+ setPath: setPath,
458
+ setQuery: setQuery,
459
+ setAnchor: setAnchor,
460
+
461
+ getQueryParamValue: getQueryParamValue,
462
+ getQueryParamValues: getQueryParamValues,
463
+ deleteQueryParam: deleteQueryParam,
464
+ addQueryParam: addQueryParam,
465
+ replaceQueryParam: replaceQueryParam,
466
+
467
+ toString: toString,
468
+ clone: clone
469
+ };
470
+ };
471
+
472
+ /* add compatibility for users of jsUri <= 1.1.1 */
473
+ var jsUri = Uri;
474
+
475
+ (function(exports) {
476
+
477
+ // Directly from http://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url:
478
+ var urlRegex = '[-a-zA-Z0-9@:%_+.~#?&//=]*';
479
+ var imageFragmentRegex = '\\s*(' + urlRegex + ')\\s*([0-9xwh.\\s]*)';
480
+ var srcsetRegex = '(' + imageFragmentRegex + ',?)+';
481
+
482
+ var IMAGE_FRAGMENT_REGEXP = new RegExp(imageFragmentRegex);
483
+ var SRCSET_REGEXP = new RegExp(srcsetRegex);
484
+ var INT_REGEXP = /^[0-9]+$/;
485
+
486
+ function SrcsetInfo(options) {
487
+ this.imageCandidates = [];
488
+ this.srcValue = options.src;
489
+ this.srcsetValue = options.srcset;
490
+ this.isValid = true;
491
+ this.error = '';
492
+
493
+ this._parse(this.srcsetValue);
494
+ if (!this.isValid) {
495
+ console.error('Error: ' + this.error);
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Parses the string that goes srcset="here".
501
+ *
502
+ * @returns [{url: _, x: _, w: _, h:_}, ...]
503
+ */
504
+ SrcsetInfo.prototype._parse = function() {
505
+ // Get image candidate fragments from srcset string.
506
+ var candidateStrings = this.srcsetValue.split(',');
507
+ // Iterate through the candidates.
508
+ for (var i = 0; i < candidateStrings.length; i++) {
509
+ var candidate = candidateStrings[i];
510
+ // Get all details for the candidate.
511
+ var match = candidate.match(IMAGE_FRAGMENT_REGEXP);
512
+ var src = match[1];
513
+ var desc = this._parseDescriptors(match[2]);
514
+ var imageInfo = new ImageInfo({
515
+ src: match[1],
516
+ x: desc.x,
517
+ w: desc.w,
518
+ h: desc.h
519
+ });
520
+ this._addCandidate(imageInfo);
521
+ }
522
+ // If there's a srcValue, add it to the candidates too.
523
+ if (this.srcValue) {
524
+ this._addCandidate(new ImageInfo({src: this.srcValue}));
525
+ }
526
+ };
527
+
528
+ /**
529
+ * Add an image candidate, unless it's a dupe of something that exists already.
530
+ */
531
+ SrcsetInfo.prototype._addCandidate = function(imageInfo) {
532
+ for (var j = 0; j < this.imageCandidates.length; j++) {
533
+ var existingCandidate = this.imageCandidates[j];
534
+ if (existingCandidate.x == imageInfo.x &&
535
+ existingCandidate.w == imageInfo.w &&
536
+ existingCandidate.h == imageInfo.h) {
537
+ // It's a dupe, so return early without adding the image candidate.
538
+ return;
539
+ }
540
+ }
541
+ this.imageCandidates.push(imageInfo);
542
+ };
543
+
544
+ SrcsetInfo.prototype._parseDescriptors = function(descString) {
545
+ var descriptors = descString.split(/\s/);
546
+ var out = {};
547
+ for (var i = 0; i < descriptors.length; i++) {
548
+ var desc = descriptors[i];
549
+ if (desc.length > 0) {
550
+ var lastChar = desc[desc.length-1];
551
+ var value = desc.substring(0, desc.length-1);
552
+ var intVal = parseInt(value, 10);
553
+ var floatVal = parseFloat(value);
554
+ if (value.match(INT_REGEXP) && lastChar === 'w') {
555
+ out[lastChar] = intVal;
556
+ } else if (value.match(INT_REGEXP) && lastChar =='h') {
557
+ out[lastChar] = intVal;
558
+ } else if (!isNaN(floatVal) && lastChar == 'x') {
559
+ out[lastChar] = floatVal;
560
+ } else {
561
+ this.error = 'Invalid srcset descriptor found in "' + desc + '".';
562
+ this.isValid = false;
563
+ }
564
+ }
565
+ }
566
+ return out;
567
+ };
568
+
569
+ function ImageInfo(options) {
570
+ this.src = options.src;
571
+ this.w = options.w || Infinity;
572
+ this.h = options.h || Infinity;
573
+ this.x = options.x || 1;
574
+ }
575
+
576
+ exports.SrcsetInfo = SrcsetInfo;
577
+
578
+ })(window);
579
+
580
+ (function(exports) {
581
+
582
+ function ViewportInfo() {
583
+ this.w = null;
584
+ this.h = null;
585
+ this.x = null;
586
+ }
587
+
588
+ /**
589
+ * Calculate viewport information: viewport width, height and
590
+ * devicePixelRatio.
591
+ */
592
+ ViewportInfo.prototype.compute = function() {
593
+ this.w = window.innerWidth;
594
+ this.h = window.innerHeight;
595
+ this.x = window.devicePixelRatio;
596
+ };
597
+
598
+ /**
599
+ * Set a fake viewport for testing purposes.
600
+ */
601
+ ViewportInfo.prototype.setForTesting = function(options) {
602
+ this.w = options.w;
603
+ this.h = options.h;
604
+ this.x = options.x;
605
+ };
606
+
607
+ /**
608
+ * Direct implementation of "processing the image candidates":
609
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#processing-the-image-candidates
610
+ *
611
+ * @returns {ImageInfo} The best image of the possible candidates.
612
+ */
613
+ ViewportInfo.prototype.getBestImage = function(srcsetInfo) {
614
+ var images = srcsetInfo.imageCandidates.slice(0);
615
+ // Get the largest width.
616
+ var largestWidth = this._getBestCandidateIf(images, function(a, b) { return a.w > b.w; });
617
+ // Remove all candidates with widths less than client width.
618
+ this._removeCandidatesIf(images, function(a) { return a.w < this.w; }.bind(this));
619
+ // If none are left, keep the one with largest width.
620
+ if (images.length === 0) { images = [largestWidth]; }
621
+
622
+ // Get the largest height.
623
+ var largestHeight = this._getBestCandidateIf(images, function(a, b) { return a.h > b.h; });
624
+ // Remove all candidates with heights less than client height.
625
+ this._removeCandidatesIf(images, function(a) { return a.h < this.h; }.bind(this));
626
+ // If none are left, keep one with largest height.
627
+ if (images.length === 0) { images = [largestHeight]; }
628
+
629
+ // Get the largest pixel density.
630
+ var largestPxDensity = this._getBestCandidateIf(images, function(a, b) { return a.x > b.x; });
631
+ // Remove all candidates with pxdensity less than client pxdensity.
632
+ this._removeCandidatesIf(images, function(a) { return a.x < this.x; }.bind(this));
633
+ // If none are left, keep one with largest pixel density.
634
+ if (images.length === 0) { images = [largestPxDensity]; }
635
+
636
+
637
+ // Get the smallest width.
638
+ var smallestWidth = this._getBestCandidateIf(images, function(a, b) { return a.w < b.w; });
639
+ // Remove all candidates with width greater than it.
640
+ this._removeCandidatesIf(images, function(a, b) { return a.w > smallestWidth.w; });
641
+
642
+ // Get the smallest height.
643
+ var smallestHeight = this._getBestCandidateIf(images, function(a, b) { return a.h < b.h; });
644
+ // Remove all candidates with height greater than it.
645
+ this._removeCandidatesIf(images, function(a, b) { return a.h > smallestWidth.h; });
646
+
647
+ // Get the smallest pixel density.
648
+ var smallestPxDensity = this._getBestCandidateIf(images, function(a, b) { return a.x < b.x; });
649
+ // Remove all candidates with pixel density less than smallest px density.
650
+ this._removeCandidatesIf(images, function(a, b) { return a.x > smallestPxDensity.x; });
651
+
652
+ return images[0];
653
+ };
654
+
655
+ ViewportInfo.prototype._getBestCandidateIf = function(images, criteriaFn) {
656
+ var bestCandidate = images[0];
657
+ for (var i = 0; i < images.length; i++) {
658
+ var candidate = images[i];
659
+ if (criteriaFn(candidate, bestCandidate)) {
660
+ bestCandidate = candidate;
661
+ }
662
+ }
663
+ return bestCandidate;
664
+ };
665
+
666
+ ViewportInfo.prototype._removeCandidatesIf = function(images, criteriaFn) {
667
+ for (var i = images.length - 1; i >= 0; i--) {
668
+ var candidate = images[i];
669
+ if (criteriaFn(candidate)) {
670
+ // Remove it.
671
+ images.splice(i, 1);
672
+ }
673
+ }
674
+ return images;
675
+ };
676
+
677
+ /**
678
+ * Get the best image from the set of image candidates, based on the viewport
679
+ * information.
680
+ *
681
+ * The best image should fit within the devicePixelRatio (x), and be as close
682
+ * to fitting the viewport width and height as possible.
683
+ *
684
+ * @returns {ImageInfo} The best image of the possible candidates.
685
+ */
686
+ ViewportInfo.prototype.getBestImage2 = function(srcsetInfo) {
687
+ var bestMatch = null;
688
+ var images = srcsetInfo.imageCandidates;
689
+ for (var i = 0; i < images.length; i++) {
690
+ var imageCandidate = images[i];
691
+ var bestMatchX = bestMatch ? bestMatch.x : 0;
692
+ // If candidate DPR is at least as large as the best, and less than or
693
+ // equal to client DPR, evaluate it further.
694
+ if (bestMatchX <= imageCandidate.x && imageCandidate.x <= this.x) {
695
+ // If there's no image to compare against, set it to the first one.
696
+ if (bestMatch === null) {
697
+ bestMatch = imageCandidate;
698
+ continue;
699
+ }
700
+ // If the width or height bounds are tighter with this candidate, it's
701
+ // a better match.
702
+ if (this.w <= imageCandidate.w && imageCandidate.w <= bestMatch.w) {
703
+ bestMatch = imageCandidate;
704
+ }
705
+ // Ignore height for now.
706
+ }
707
+ }
708
+ return bestMatch;
709
+ };
710
+
711
+ exports.ViewportInfo = ViewportInfo;
712
+
713
+ })(window);
714
+
715
+ (function(exports) {
716
+
717
+ function isSrcsetImplemented() {
718
+ var img = new Image();
719
+ return 'srcset' in img;
720
+ }
721
+
722
+ function main() {
723
+ // If the browser supports @srcset natively, don't do any polyfill.
724
+ if (isSrcsetImplemented()) {
725
+ return;
726
+ }
727
+
728
+ // Get the user agent's capabilities (viewport width, viewport height, dPR).
729
+ var viewportInfo = new ViewportInfo();
730
+ viewportInfo.compute();
731
+ // Go through all images on the page.
732
+ var images = document.querySelectorAll('img');
733
+ // If they have srcset attributes, apply JS to handle that correctly.
734
+ for (var i = 0; i < images.length; i++) {
735
+ var img = images[i];
736
+ // Parse the srcset from the image element.
737
+ var srcset = img.attributes.srcset;
738
+ if (srcset) {
739
+ var srcsetInfo = new SrcsetInfo({src: img.src,
740
+ srcset: srcset.textContent});
741
+ // Go through all the candidates, pick the best one that matches.
742
+ var imageInfo = viewportInfo.getBestImage(srcsetInfo);
743
+ // TODO: consider using -webkit-image-set instead (if available).
744
+ // Replace the <img src> with this image.
745
+ img.src = imageInfo.src;
746
+ // Scale the image if necessary (ie. x != 1).
747
+ img.style.webkitTransform = 'scale(' + (1/imageInfo.x) + ')';
748
+ img.style.webkitTransformOrigin = '0 0';
749
+ }
750
+ }
751
+ }
752
+
753
+ window.addEventListener('DOMContentLoaded', main);
754
+
755
+ })(window);