1/** 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>[email protected]</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 return options.glue + devicePixelRatio + 'x' + extension; 180 }); 181 182 ping = options.ping !== false && $.inArray(image, pathStack) === -1 && (options.ping === true || !regexH
182asProtocol.test(image) || image.indexOf('//'+document.domain) === 0 || image.indexOf(document.location.protocol+'//'+document.domain) === 0); 183 } 184 185 updateImage = function () 186 { 187 var readyImage = function () 188 { 189 $this.removeClass('dense-loading').addClass('dense-ready').trigger('denseRetinaReady.dense'); 190 }; 191 192 $this.attr('src', image); 193 194 if (options.dimensions === 'update') 195 { 196 $this.dense('updateDimensions').one('denseDimensionChanged', readyImage); 197 } 198 else 199 { 200 if (options.dimensions === 'remove') 201 { 202 $this.removeAttr('width height'); 203 } 204 205 readyImage(); 206 } 207 }; 208 209 if (ping) 210 { 211 $.ajax({ 212 url : image, 213 type : 'HEAD' 214 }) 215 .done(function (data, textStatus, jqXHR) 216 { 217 var type = jqXHR.getResponseHeader('Content-type'); 218 219 if (!type || type.indexOf('image/') === 0) 220 { 221 pathStack.push(image); 222 updateImage(); 223 } 224 }); 225 } 226 else 227 { 228 updateImage(); 229 } 230 }); 231 232 return this; 233 }; 234 235 /** 236 * Sets an image's width and height attributes to its native values. 237 * 238 * Updates an img element's dimensions to the source image's 239 * real values. This method is asynchronous, so you can not directly 240 * return its values. Instead, use the 'dense-dimensions-updated' 241 * event to detect when the action is done. 242 * 243 * @return {Object} this 244 * @method updateDimensions 245 * @memberof jQuery.fn.dense 246 * @fires jQuery.fn.dense#denseDimensionChanged.dense 247 * @example 248 * var image = $('img').dense('updateDimensions'); 249 */ 250 251 methods.updateDimensions = function () 252 { 253 return this.each(function () 254 { 255 var img, $this = $(this), src = $this.attr('src'); 256 257 if (src) 258 { 259 img = new Image(); 260 img.src = src; 261 262 $(img).on('load.dense', function () 263 { 264 $this.attr({ 265 width: img.width, 266 height: img.height 267 }).trigger('denseDimensionChanged.dense'); 268 }); 269 } 270 }); 271 }; 272 273 /** 274 * Gets device pixel ratio rounded up to the closest integer. 275 * 276 * @return {Integer} The pixel ratio 277 * @method devicePixelRatio 278 * @memberof jQuery.fn.dense 279 * @example 280 * var ratio = $(window).dense('devicePixelRatio'); 281 * alert(ratio); 282 */ 283 284 methods.devicePixelRatio = function () 285 { 286 var pixelRatio = 1; 287 288 if ($.type(window.devicePixelRatio) !== 'undefined') 289 { 290 pixelRatio = window.devicePixelRatio; 291 } 292 else if ($.type(window.matchMedia) !== 'undefined') 293 { 294 $.each([1.3, 2, 3, 4, 5, 6], function (key, ratio) 295 { 296 var mediaQuery = [ 297 '(-webkit-min-device-pixel-ratio: '+ratio+')', 298 '(min-resolution: '+Math.floor(ratio*96)+'dpi)', 299 '(min-resolution: '+ratio+'dppx)' 300 ].join(','); 301 302 if (!window.matchMedia(mediaQuery).matches) 303 { 304 return false; 305 } 306 307 pixelRatio = ratio; 308 }); 309 } 310 311 return Math.ceil(pixelRatio); 312 }; 313 314 /** 315 * Gets an appropriate URL for the pixel ratio from the data attribute list. 316 * 317 * Selects the most appropriate <code>data-{ratio}x</code> attribute from 318 * the given element's attributes. If the devices pixel ratio is greater 319 * than the largest specified image, the largest one of the available is used. 320 * 321 * @return {String|Boolean} The attribute value 322 * @method getImageAttribute 323 * @memberof jQuery.fn.dense 324 * @example 325 * var image = $('<div data-1x="image.jpg" data-2x="image_2x.jpg" />').dense('getImageAttribute');
326 * $('body').css('background-image', 'url(' + image + ')'); 327 */ 328 329 methods.getImageAttribute = function () 330 { 331 var $this = $(this).eq(0), image = false, url; 332 333 for (var i = 1; i <= devicePixelRatio; i++) 334 { 335 url = $this.attr('data-' + i + 'x'); 336 337 if (url) 338 { 339 image = url; 340 } 341 } 342 343 return image; 344 }; 345 346 devicePixelRatio = methods.devicePixelRatio(); 347 348 /** 349 * Dense offers few methods and options that can be used to both customize the 350 * plugin's functionality and return resulting values. All interaction is done through 351 * the <code>$.fn.dense()</code> method, that accepts a called method and its options 352 * object as its arguments. Both arguments are optional, and either one can be omitted. 353 * 354 * @param {String} [method=init] The called method 355 * @param {Object} [options={}] Options passed to the method 356 * @class dense 357 * @memberof jQuery.fn 358 */ 359 360 $.fn.dense = function (method, options) 361 { 362 if ($.type(method) !== 'string' || $.type(methods[method]) !== 'function') 363 { 364 options = method; 365 method = 'init'; 366 } 367 368 return methods[method].call(this, options); 369 }; 370 371 /** 372 * Initialize automatically when document is ready. 373 * 374 * Dense is initialized automatically if the body element 375 * has a <code>dense-retina</code> class. 376 */ 377 378 $(function () 379 { 380 $('body.dense-retina img').dense(); 381 }); 382 383 /** 384 * This event is invoked when a retina image has finished loading. 385 * 386 * @event jQuery.fn.dense#denseRetinaReady.dense 387 * @type {Object} 388 */ 389 390 /** 391 * This event is invoked when an image's dimension values 392 * have been updated by the <code>updateDimensions</code> 393 * method. 394 * 395 * @event jQuery.fn.dense#denseDimensionChanged.dense 396 * @type {Object} 397 */ 398}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.