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.
- data/README.md +112 -1
- data/VERSION +1 -1
- data/lib/picturefill/view_helper.rb +21 -1
- data/picturefill-rails.gemspec +17 -3
- data/spec/srcset/banner-HD.jpeg +0 -0
- data/spec/srcset/banner-phone-HD.jpeg +0 -0
- data/spec/srcset/banner-phone.jpeg +0 -0
- data/spec/srcset/banner.jpeg +0 -0
- data/spec/srcset/index.html +16 -0
- data/spec/srcset/tests/index.html +29 -0
- data/spec/srcset/tests/srcset-tests.js +100 -0
- data/spec/srcset/view_helper_spec.rb +35 -0
- data/vendor/assets/javascripts/jquery-picture.js +241 -0
- data/vendor/assets/javascripts/jquery-picture.min.js +18 -0
- data/vendor/assets/javascripts/picturefill.js +60 -0
- data/vendor/assets/javascripts/picturefill/matchmedia.js +2 -0
- data/vendor/assets/javascripts/srcset.js +755 -0
- data/vendor/assets/javascripts/srcset.min.js +1 -0
- metadata +17 -3
@@ -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='­<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);
|