1 |
57592
|
stefania.m
|
/**
|
2 |
|
|
* Dense - Device pixel ratio aware images
|
3 |
|
|
*
|
4 |
|
|
* @link http://dense.rah.pw
|
5 |
|
|
* @license MIT
|
6 |
|
|
*/
|
7 |
|
|
|
8 |
|
|
/*
|
9 |
|
|
* Copyright (C) 2013 Jukka Svahn
|
10 |
|
|
*
|
11 |
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
12 |
|
|
* copy of this software and associated documentation files (the "Software"),
|
13 |
|
|
* to deal in the Software without restriction, including without limitation
|
14 |
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
15 |
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
16 |
|
|
* Software is furnished to do so, subject to the following conditions:
|
17 |
|
|
*
|
18 |
|
|
* The above copyright notice and this permission notice shall be included in
|
19 |
|
|
* all copies or substantial portions of the Software.
|
20 |
|
|
*
|
21 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
22 |
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
23 |
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
24 |
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
25 |
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
26 |
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27 |
|
|
*/
|
28 |
|
|
|
29 |
|
|
/**
|
30 |
|
|
* @name jQuery
|
31 |
|
|
* @class
|
32 |
|
|
*/
|
33 |
|
|
|
34 |
|
|
/**
|
35 |
|
|
* @name fn
|
36 |
|
|
* @class
|
37 |
|
|
* @memberOf jQuery
|
38 |
|
|
*/
|
39 |
|
|
|
40 |
|
|
(function (factory)
|
41 |
|
|
{
|
42 |
|
|
'use strict';
|
43 |
|
|
|
44 |
|
|
if (typeof define === 'function' && define.amd)
|
45 |
|
|
{
|
46 |
|
|
define(['jquery'], factory);
|
47 |
|
|
}
|
48 |
|
|
else
|
49 |
|
|
{
|
50 |
|
|
factory(window.jQuery || window.Zepto);
|
51 |
|
|
}
|
52 |
|
|
}(function ($)
|
53 |
|
|
{
|
54 |
|
|
'use strict';
|
55 |
|
|
|
56 |
|
|
/**
|
57 |
|
|
* An array of checked image URLs.
|
58 |
|
|
*/
|
59 |
|
|
|
60 |
|
|
var pathStack = [],
|
61 |
|
|
|
62 |
|
|
/**
|
63 |
|
|
* Methods.
|
64 |
|
|
*/
|
65 |
|
|
|
66 |
|
|
methods = {},
|
67 |
|
|
|
68 |
|
|
/**
|
69 |
|
|
* Regular expression to check whether the URL has a protocol.
|
70 |
|
|
*
|
71 |
|
|
* Is used to check whether the image URL is external.
|
72 |
|
|
*/
|
73 |
|
|
|
74 |
|
|
regexHasProtocol = /^([a-z]:)?\/\//i,
|
75 |
|
|
|
76 |
|
|
/**
|
77 |
|
|
* Regular expression that split extensions from the file.
|
78 |
|
|
*
|
79 |
|
|
* Is used to inject the DPR suffix to the name.
|
80 |
|
|
*/
|
81 |
|
|
|
82 |
|
|
regexSuffix = /\.\w+$/,
|
83 |
|
|
|
84 |
|
|
/**
|
85 |
|
|
* Device pixel ratio.
|
86 |
|
|
*/
|
87 |
|
|
|
88 |
|
|
devicePixelRatio;
|
89 |
|
|
|
90 |
|
|
/**
|
91 |
|
|
* Init is the default method responsible for rendering
|
92 |
|
|
* a pixel-ratio-aware images.
|
93 |
|
|
*
|
94 |
|
|
* This method is used to select the images that
|
95 |
|
|
* should display retina-size images on high pixel ratio
|
96 |
|
|
* devices. Dense defaults to the init method if no
|
97 |
|
|
* method is specified.
|
98 |
|
|
*
|
99 |
|
|
* When attached to an image, the correct image variation is
|
100 |
|
|
* selected based on the device's pixel ratio. If the image element
|
101 |
|
|
* defines <code>data-{ratio}x</code> attributes (e.g. data-1x, data-2x, data-3x),
|
102 |
|
|
* the most appropriate of those is selected.
|
103 |
|
|
*
|
104 |
|
|
* If no data-ratio attributes are defined, the retina image is
|
105 |
|
|
* constructed from the <code>src</code> attribute.
|
106 |
|
|
* The searched high pixel ratio images follows
|
107 |
|
|
* a <code>{imageName}_{ratio}x.{ext}</code> naming convention.
|
108 |
|
|
* For an image found in /path/to/images/image.jpg, the 2x retina
|
109 |
|
|
* image would be looked from /path/to/images/image_2x.jpg.
|
110 |
|
|
*
|
111 |
|
|
* When image is constructed from the src, the image existance is
|
112 |
|
|
* verified using HTTP HEAD request, if <code>ping</code> option is
|
113 |
|
|
* <code>true</code>. The check makes sure no HTTP error code is returned,
|
114 |
|
|
* and that the received content-type is of an image. Vector image formats,
|
115 |
|
|
* like svg, are skipped based on the file extension.
|
116 |
|
|
*
|
117 |
|
|
* This method can also be used to load image in semi-lazy fashion,
|
118 |
|
|
* and avoid larger extra HTTP requests due to retina replacements.
|
119 |
|
|
* The data-1x attribute can be used to supstitute the src, making
|
120 |
|
|
* sure the browser doesn't try to download the normal image variation
|
121 |
|
|
* before the JavaScript driven behaviour kicks in.
|
122 |
|
|
*
|
123 |
|
|
* Some classes are added to the selected elements while Dense is processing
|
124 |
|
|
* the document. These classes include <code>dense-image</code>, <code>dense-loading</code>
|
125 |
|
|
* and <code>dense-ready</code>. These classes can be used to style the images,
|
126 |
|
|
* or hide them while they are being loaded.
|
127 |
|
|
*
|
128 |
|
|
* @param {Object} [options={}] Options
|
129 |
|
|
* @param {Boolean} [options.ping=null] Check image existence. If the default <code>NULL</code> checks local images, <code>FALSE</code> disables checking and <code>TRUE</code> checks even external images cross-domain
|
130 |
|
|
* @param {String} [options.dimensions=preserve] What to do with the image's <code>width</code> and <code>height</code> attributes. Either <code>update</code>, <code>remove</code> or <code>preserve</code>
|
131 |
|
|
* @param {String} [options.glue=_] String that glues the retina "nx" suffix to the image. This option can be used to change the naming convention between the two commonly used practices, <code>image@2x.jpg</code> and <code>image_2x.jpg</code>
|
132 |
|
|
* @param {Array} [options.skipExtensions=['svg']] Skipped image file extensions. There might be situations where you might want to exclude vector image formats
|
133 |
|
|
* @return {Object} this
|
134 |
|
|
* @method init
|
135 |
|
|
* @memberof jQuery.fn.dense
|
136 |
|
|
* @fires jQuery.fn.dense#denseRetinaReady.dense
|
137 |
|
|
* @example
|
138 |
|
|
* $('img').dense({
|
139 |
|
|
* ping: false,
|
140 |
|
|
* dimension: 'update'
|
141 |
|
|
* });
|
142 |
|
|
*/
|
143 |
|
|
|
144 |
|
|
methods.init = function (options)
|
145 |
|
|
{
|
146 |
|
|
options = $.extend({
|
147 |
|
|
ping: null,
|
148 |
|
|
dimensions: 'preserve',
|
149 |
|
|
glue: '_',
|
150 |
|
|
skipExtensions: ['svg']
|
151 |
|
|
}, options);
|
152 |
|
|
|
153 |
|
|
this.each(function ()
|
154 |
|
|
{
|
155 |
|
|
var $this = $(this);
|
156 |
|
|
|
157 |
|
|
if (!$this.is('img') || $this.hasClass('dense-image'))
|
158 |
|
|
{
|
159 |
|
|
return;
|
160 |
|
|
}
|
161 |
|
|
|
162 |
|
|
$this.addClass('dense-image dense-loading');
|
163 |
|
|
|
164 |
|
|
var image = methods.getImageAttribute.call(this),
|
165 |
|
|
originalImage = $this.attr('src'),
|
166 |
|
|
ping = false,
|
167 |
|
|
updateImage;
|
168 |
|
|
|
169 |
|
|
if (!image)
|
170 |
|
|
{
|
171 |
|
|
if (!originalImage || devicePixelRatio === 1 || $.inArray(originalImage.split('.').pop().split(/[\?\#]/).shift(), options.skipExtensions) !== -1)
|
172 |
|
|
{
|
173 |
|
|
$this.removeClass('dense-image dense-loading');
|
174 |
|
|
return;
|
175 |
|
|
}
|
176 |
|
|
|
177 |
|
|
image = originalImage.replace(regexSuffix, function (extension)
|
178 |
|
|
{
|
179 |
|
|
var pixelRatio = $this.attr('data-dense-cap') ? $this.attr('data-dense-cap') : devicePixelRatio;
|
180 |
|
|
return options.glue + pixelRatio + 'x' + extension;
|
181 |
|
|
});
|
182 |
|
|
|
183 |
|
|
ping = options.ping !== false && $.inArray(image, pathStack) === -1 && (options.ping === true || !regexHasProtocol.test(image) || image.indexOf('//'+document.domain) === 0 || image.indexOf(document.location.protocol+'//'+document.domain) === 0);
|
184 |
|
|
}
|
185 |
|
|
|
186 |
|
|
updateImage = function ()
|
187 |
|
|
{
|
188 |
|
|
var readyImage = function ()
|
189 |
|
|
{
|
190 |
|
|
$this.removeClass('dense-loading').addClass('dense-ready').trigger('denseRetinaReady.dense');
|
191 |
|
|
};
|
192 |
|
|
|
193 |
|
|
$this.attr('src', image);
|
194 |
|
|
|
195 |
|
|
if (options.dimensions === 'update')
|
196 |
|
|
{
|
197 |
|
|
$this.dense('updateDimensions').one('denseDimensionChanged', readyImage);
|
198 |
|
|
}
|
199 |
|
|
else
|
200 |
|
|
{
|
201 |
|
|
if (options.dimensions === 'remove')
|
202 |
|
|
{
|
203 |
|
|
$this.removeAttr('width height');
|
204 |
|
|
}
|
205 |
|
|
|
206 |
|
|
readyImage();
|
207 |
|
|
}
|
208 |
|
|
};
|
209 |
|
|
|
210 |
|
|
if (ping)
|
211 |
|
|
{
|
212 |
|
|
$.ajax({
|
213 |
|
|
url : image,
|
214 |
|
|
type : 'HEAD'
|
215 |
|
|
})
|
216 |
|
|
.done(function (data, textStatus, jqXHR)
|
217 |
|
|
{
|
218 |
|
|
var type = jqXHR.getResponseHeader('Content-type');
|
219 |
|
|
|
220 |
|
|
if (!type || type.indexOf('image/') === 0)
|
221 |
|
|
{
|
222 |
|
|
pathStack.push(image);
|
223 |
|
|
updateImage();
|
224 |
|
|
}
|
225 |
|
|
});
|
226 |
|
|
}
|
227 |
|
|
else
|
228 |
|
|
{
|
229 |
|
|
updateImage();
|
230 |
|
|
}
|
231 |
|
|
});
|
232 |
|
|
|
233 |
|
|
return this;
|
234 |
|
|
};
|
235 |
|
|
|
236 |
|
|
/**
|
237 |
|
|
* Sets an image's width and height attributes to its native values.
|
238 |
|
|
*
|
239 |
|
|
* Updates an img element's dimensions to the source image's
|
240 |
|
|
* real values. This method is asynchronous, so you can not directly
|
241 |
|
|
* return its values. Instead, use the 'dense-dimensions-updated'
|
242 |
|
|
* event to detect when the action is done.
|
243 |
|
|
*
|
244 |
|
|
* @return {Object} this
|
245 |
|
|
* @method updateDimensions
|
246 |
|
|
* @memberof jQuery.fn.dense
|
247 |
|
|
* @fires jQuery.fn.dense#denseDimensionChanged.dense
|
248 |
|
|
* @example
|
249 |
|
|
* var image = $('img').dense('updateDimensions');
|
250 |
|
|
*/
|
251 |
|
|
|
252 |
|
|
methods.updateDimensions = function ()
|
253 |
|
|
{
|
254 |
|
|
return this.each(function ()
|
255 |
|
|
{
|
256 |
|
|
var img, $this = $(this), src = $this.attr('src');
|
257 |
|
|
|
258 |
|
|
if (src)
|
259 |
|
|
{
|
260 |
|
|
img = new Image();
|
261 |
|
|
img.src = src;
|
262 |
|
|
|
263 |
|
|
$(img).on('load.dense', function ()
|
264 |
|
|
{
|
265 |
|
|
$this.attr({
|
266 |
|
|
width: img.width,
|
267 |
|
|
height: img.height
|
268 |
|
|
}).trigger('denseDimensionChanged.dense');
|
269 |
|
|
});
|
270 |
|
|
}
|
271 |
|
|
});
|
272 |
|
|
};
|
273 |
|
|
|
274 |
|
|
/**
|
275 |
|
|
* Gets device pixel ratio rounded up to the closest integer.
|
276 |
|
|
*
|
277 |
|
|
* @return {Integer} The pixel ratio
|
278 |
|
|
* @method devicePixelRatio
|
279 |
|
|
* @memberof jQuery.fn.dense
|
280 |
|
|
* @example
|
281 |
|
|
* var ratio = $(window).dense('devicePixelRatio');
|
282 |
|
|
* alert(ratio);
|
283 |
|
|
*/
|
284 |
|
|
|
285 |
|
|
methods.devicePixelRatio = function ()
|
286 |
|
|
{
|
287 |
|
|
var pixelRatio = 1;
|
288 |
|
|
|
289 |
|
|
if ($.type(window.devicePixelRatio) !== 'undefined')
|
290 |
|
|
{
|
291 |
|
|
pixelRatio = window.devicePixelRatio;
|
292 |
|
|
}
|
293 |
|
|
else if ($.type(window.matchMedia) !== 'undefined')
|
294 |
|
|
{
|
295 |
|
|
$.each([1.3, 2, 3, 4, 5, 6], function (key, ratio)
|
296 |
|
|
{
|
297 |
|
|
var mediaQuery = [
|
298 |
|
|
'(-webkit-min-device-pixel-ratio: '+ratio+')',
|
299 |
|
|
'(min-resolution: '+Math.floor(ratio*96)+'dpi)',
|
300 |
|
|
'(min-resolution: '+ratio+'dppx)'
|
301 |
|
|
].join(',');
|
302 |
|
|
|
303 |
|
|
if (!window.matchMedia(mediaQuery).matches)
|
304 |
|
|
{
|
305 |
|
|
return false;
|
306 |
|
|
}
|
307 |
|
|
|
308 |
|
|
pixelRatio = ratio;
|
309 |
|
|
});
|
310 |
|
|
}
|
311 |
|
|
|
312 |
|
|
return Math.ceil(pixelRatio);
|
313 |
|
|
};
|
314 |
|
|
|
315 |
|
|
/**
|
316 |
|
|
* Gets an appropriate URL for the pixel ratio from the data attribute list.
|
317 |
|
|
*
|
318 |
|
|
* Selects the most appropriate <code>data-{ratio}x</code> attribute from
|
319 |
|
|
* the given element's attributes. If the devices pixel ratio is greater
|
320 |
|
|
* than the largest specified image, the largest one of the available is used.
|
321 |
|
|
*
|
322 |
|
|
* @return {String|Boolean} The attribute value
|
323 |
|
|
* @method getImageAttribute
|
324 |
|
|
* @memberof jQuery.fn.dense
|
325 |
|
|
* @example
|
326 |
|
|
* var image = $('<div data-1x="image.jpg" data-2x="image_2x.jpg" />').dense('getImageAttribute');
|
327 |
|
|
* $('body').css('background-image', 'url(' + image + ')');
|
328 |
|
|
*/
|
329 |
|
|
|
330 |
|
|
methods.getImageAttribute = function ()
|
331 |
|
|
{
|
332 |
|
|
var $this = $(this).eq(0), image = false, url;
|
333 |
|
|
|
334 |
|
|
for (var i = 1; i <= devicePixelRatio; i++)
|
335 |
|
|
{
|
336 |
|
|
url = $this.attr('data-' + i + 'x');
|
337 |
|
|
|
338 |
|
|
if (url)
|
339 |
|
|
{
|
340 |
|
|
image = url;
|
341 |
|
|
}
|
342 |
|
|
}
|
343 |
|
|
|
344 |
|
|
return image;
|
345 |
|
|
};
|
346 |
|
|
|
347 |
|
|
devicePixelRatio = methods.devicePixelRatio();
|
348 |
|
|
|
349 |
|
|
/**
|
350 |
|
|
* Dense offers few methods and options that can be used to both customize the
|
351 |
|
|
* plugin's functionality and return resulting values. All interaction is done through
|
352 |
|
|
* the <code>$.fn.dense()</code> method, that accepts a called method and its options
|
353 |
|
|
* object as its arguments. Both arguments are optional, and either one can be omitted.
|
354 |
|
|
*
|
355 |
|
|
* @param {String} [method=init] The called method
|
356 |
|
|
* @param {Object} [options={}] Options passed to the method
|
357 |
|
|
* @class dense
|
358 |
|
|
* @memberof jQuery.fn
|
359 |
|
|
*/
|
360 |
|
|
|
361 |
|
|
$.fn.dense = function (method, options)
|
362 |
|
|
{
|
363 |
|
|
if ($.type(method) !== 'string' || $.type(methods[method]) !== 'function')
|
364 |
|
|
{
|
365 |
|
|
options = method;
|
366 |
|
|
method = 'init';
|
367 |
|
|
}
|
368 |
|
|
|
369 |
|
|
return methods[method].call(this, options);
|
370 |
|
|
};
|
371 |
|
|
|
372 |
|
|
/**
|
373 |
|
|
* Initialize automatically when document is ready.
|
374 |
|
|
*
|
375 |
|
|
* Dense is initialized automatically if the body element
|
376 |
|
|
* has a <code>dense-retina</code> class.
|
377 |
|
|
*/
|
378 |
|
|
|
379 |
|
|
$(function ()
|
380 |
|
|
{
|
381 |
|
|
$('body.dense-retina img').dense();
|
382 |
|
|
});
|
383 |
|
|
|
384 |
|
|
/**
|
385 |
|
|
* This event is invoked when a retina image has finished loading.
|
386 |
|
|
*
|
387 |
|
|
* @event jQuery.fn.dense#denseRetinaReady.dense
|
388 |
|
|
* @type {Object}
|
389 |
|
|
*/
|
390 |
|
|
|
391 |
|
|
/**
|
392 |
|
|
* This event is invoked when an image's dimension values
|
393 |
|
|
* have been updated by the <code>updateDimensions</code>
|
394 |
|
|
* method.
|
395 |
|
|
*
|
396 |
|
|
* @event jQuery.fn.dense#denseDimensionChanged.dense
|
397 |
|
|
* @type {Object}
|
398 |
|
|
*/
|
399 |
|
|
}));
|