1/** 2 * @preserve Slimmage 0.4.2, use with ImageResizer. MIT/Apache2 dual licensed by Imazen 3 */ 4 5/* We often use string instead of dot notation to keep 6 Closure Compiler's advanced mode from breaking APIs */ 7/*jshint sub:true*/ 8 9(function (w) { //w==window 10 // Enable strict mode 11 "use strict"; 12 13 var s = window['slimmage'] || {}; 14 window['slimmage'] = s; 15 if (s['verbose'] === undefined) { s['verbose'] = false;} 16 if (s['tryWebP'] === undefined) { s['tryWebP'] = false;} 17 if (s['maxWidth'] === undefined) { s['maxWidth'] = 2048;} 18 if (s['widthStep'] === undefined) { s['widthStep'] = 160;} 19 if (s['jpegQuality'] === undefined) { s['jpegQuality'] = 90;} 20 if (s['jpegRetinaQuality'] === undefined) { s['jpegRetinaQuality'] = 80;} 21 if (s['webpTimeout'] === undefined){ s['webpTimeout'] = 0;} 22 s['changed'] = []; 23 24 var log = function(){ if (s['verbose'] && w.console && w.console.log) try {w.console.log.apply(w.console,arguments);}catch(e){}}; 25 s.beginWebPTest = function(){ 26 if (!s['tryWebP'] || s._testingWebP) return; 27 s._testingWebP = true; 28 29 var WebP=new Image(); 30 WebP.onload=WebP.onerror=function(){ 31 s['webp'] = (WebP.height==2); 32 s._testingWebP = false; 33 if (s.cr) { s.cr(); } 34 }; 35 WebP.src='data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA'; 36 }; 37 //test for webp support ASAP 38 s.beginWebPTest(); 39 40 s.setAttr = function(elem, name, value){ 41 name = name.toLowerCase(); 42 if (name == "class"){ elem.className = value;} 43 else if (name == "tabindex") {elem.tabIndex = value;} 44 else if (name == "usemap") {elem.useMap = value;} 45 else{ 46 elem.setAttribute(name,value); 47 } 48 49 }; 50 s['setAttribute'] = s.setAttr; 51 52 s.is_blank = function(v){ return v === "none" || v === null || v === undefined || v === "" || v === false;}; 53 s['getCssValue'] = function(target, hyphenProp){ 54 //See http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser 55 56 var val = typeof(window.getComputedStyle) != "undefined" && window.getComputedStyle(target, null).getPropertyValue(hyphenProp); 57 58 // Some browsers (IE8, Firefox 28) read "none" when not set. Others (IE6) respond with undefined. A value of 59 // "none" is invalid and would cause an exception or be interpreted as 60 if (!s.is_blank(val)){ 61 return val; 62 } 63 64 if (target.currentStyle){ 65 val = target.currentStyle[hyphenProp.replace(/([a-z])\-([a-z])/, function(a,b,c){ return b + c.toUpperCase();})] || target.currentStyle[hyphenProp]; 66 } 67 68 return s.is_blank(val) ? null : val; 69 }; 70 71 s['getCssPixels'] = function(target, hyphenProp){ 72 var val = s['getCssValue'](target,hyphenProp); 73 74 if (val === null || val === "0" || val === 0) return val; 75 76 //We can return pixels directly, but not other units 77 if (val.slice(-2) == "px") return parseFloat(val.slice(0,-2)); 78 79 //Create a temporary sibling div to resolve units into pixels. 80 var temp = document.createElement("div"); 81 temp.style.overflow = temp.style.visibility = "hidden"; 82 temp.style.cssFloat = "none"; 83 target.parentNode.appendChild(temp); 84 temp.style.width = val; 85 var pixels = temp.offsetWidth; 86 target.parentNode.removeChild(temp); 87 return pixels; 88 }; 89 90 s.nodesToArray = function (nodeList) { 91 var array = []; 92 // iterate backwards ensuring that length is an UInt32 93 for (var i = nodeList.length >>> 0; i--;) { 94 array[i] = nodeList[i]; 95 } 96 return array; 97 }; 98 99 /* 100 No URI decoding/encoding is perfomed on keys or values. Caller's responsibility. 101 102 vistor and mutator are applied to string.replace -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter 103 params: 104 1. entire pair, incl. (optional) & and = 105 2. '&' (if present) 106 3. key 107 4. value 108 109 mutator's result is honored. Must return param 1, or provide valid replacement. 110 111 injector(query) is responsible for adding additional pairs if required. 112 It must not produce a malformatted query. 113 */ 114 s['mutateUrl'] = function(url, visitor,mutator,injector){ 115 var m = /^([^?#]*)?(\?([^#]*))?(#.*)?/.exec(url); 116 var q = m[3] || ''; 117 118 var qr = /(^&*|&+)([^&=]*)=?([^&]*)/g; 119 q.replace(qr, visitor); //read-only, to gather data 120 121 var newq = '?' + q.replace(qr, mutator).replace(/(?:^\?*&*)|(?:[?&]+$)/g,"").replace(/&&+/g,"&"); 122 123 return (m[1] || '') + injector(newq) + (m[4] || ''); 124 }; 125 126 //Expects virtual, not device pixel width 127 s['getImageInfo'] = function (elementWidth, previousSrc, previousPixelWidth, previousElement) { 128 var data = { 129 'webp': s['webp'], 130 'width': elementWidth, 131 'dpr': window.devicePixelRatio || 1, 132 'src': previousSrc, 133 'element': previousElement 134 }; 135 //Determine quality percentage 136 var high_density = s['webp'] ? 65 : s['jpegRetinaQuality']; 137 var low_density = s['webp'] ? 78 : s['jpegQuality']; 138 data['quality'] = data['dpr'] > 1.49 ? high_density : low_density; 139 140 //Calculate raw pixels using devicePixelRatio. Limit size to maxWidth. 141 var idealWidth = elementWidth * data['dpr']; 142 //Minimize variants for caching improvements; round up to nearest multiple of widthStep 143 data['requestedWidth'] = Math.min(s['maxWidth'], //Limit size to maxWidth 144 Math.round( //Round in case widthStep isn't an integer 145 Math.ceil(idealWidth / s['widthStep']) * s['widthStep'] //Divide, ceiling, then multiply 146 ) 147 ); 148 149 150 var a = s['adjustImageParameters']; 151 if (a && typeof(a) === "function") { 152 a(data); 153 } 154 var finalWidth = data['requestedWidth']; 155 156 if (finalWidth > previousPixelWidth) { 157 //Never request a smaller image once the larger one has already started loading 158 var u = {}; //For storing raw pairs 159 var c = {}; //For storing relevant parsed info 160 var newSrc = s['mutateUrl']( 161 previousSrc, 162 //Visitor 163 function(_,d, k,v){ 164 u[k.toLowerCase()] = v; 165 }, 166 //Mutator 167 function(p,d,k,v){ 168 169 //Parse existing values so we can make educated calculations for width/height 170 if (c.zoom === undefined){ 171 c.zoom = parseFloat(u.zoom || 1); 172 if (isNaN(c.zoom)){ c.zoom = 1; } 173 c.w = (finalWidth / c.zoom).toFixed(); 174 } 175 if (c.ratio === undefined){ 176 var w = parseFloat(u.width || u.w || u.maxwidth); 177 var h = parseFloat(u.height || u.h || u.maxheight); 178 if (!isNaN(w) && !isNaN(h) && w > 0 && h > 0){ 179 c.ratio = w/h; 180 c.h = (((finalWidth / c.zoom) / w) * h).toFixed(); 181 }else{ 182 c.ratio = 'noclue'; 183 } 184 } 185 186 if (k.match(/^format$/i) && data['webp']) { return d + "format=webp"; } 187 if (k.match(/^quality/i)) { return d + "quality=" + data['quality']; } 188 189 if (k.match(/^(w|width|maxwidth)$/i)) { return d + k + "=" + c.w;} 190 if (k.match(/^(h|height|maxheight)$/i)) { return d + k + "=" + c.h;} 191 192 return p; 193 }, 194 //Injector 195 function(q){ return q;}); 196
197 return {'src': newSrc, 'data-pixel-width': finalWidth}; 198 } 199 return null; 200 }; 201 s['adjustImageSrc'] = function (img, previousSrc) { 202 203 var cssMaxWidth = s['getCssPixels'](img, 'max-width'); 204 var result = s['getImageInfo'](cssMaxWidth, 205 previousSrc, 206 img.getAttribute('data-pixel-width') | 0, 207 img); 208 209 if (result){ 210 img.src = result['src']; 211 s.setAttr(img,'data-pixel-width',result['data-pixel-width']); 212 if (s['enforceCss']){ 213 if (cssMaxWidth < result['data-pixel-width']){ 214 img.style.width = s['getCssValue'](img,'max-width'); 215 s.setAttr(img,'data-width-enforced',true); 216 }else{ 217 img.style.width = 'auto'; 218 } 219 } 220 s['changed'].push(img); 221 log("Slimming: updating " + result['src']); 222 }else if (s['enforceCss'] && img.getAttribute('data-width-enforced')){ 223 var imageWidth = parseFloat(img.getAttribute('data-pixel-width')); 224 if (!isNaN(imageWidth) && cssMaxWidth >= imageWidth){ 225 img.style.width = 'auto'; 226 img.removeAttribute('data-width-enforced'); 227 } 228 } 229 230 }; 231 s.cr = function (delay) { 232 var i, il, j, jl, k, kl; 233 234 if (s.timeoutid > 0) w.clearTimeout(s.timeoutid); 235 s.timeoutid = 0; 236 237 if (s._testingWebP && s['webpTimeout'] > 0 && !s.webp_waiting){ 238 s.webp_waiting = true; 239 delay = s['webpTimeout']; 240 } 241 if (delay && delay > 0) { 242 s.timeoutid = w.setTimeout(s.cr, delay); 243 return; 244 } 245 var stopwatch = new Date().getTime(); 246 247 var newImages = 0; 248 //1. Copy images out of noscript tags, but hide 'src' attribute as data-src 249 var n = s.nodesToArray(w.document.getElementsByTagName("noscript")); 250 for (i = 0, il = n.length; i < il; i++) { 251 var ns = n[i]; 252 if (ns.getAttribute("data-slimmage") !== null) { 253 254 var div = w.document.createElement('div'); 255 var contents = (ns.textContent || ns.innerHTML); 256 if (!contents || contents.replace(/[\s\t\r\n]+/,"").length === 0){ 257 //IE doesn't let us touch noscript, so we have to use attributes. 258 var img = new Image(); 259 for (var ai = 0; ai < ns.attributes.length; ai++) { 260 var a = ns.attributes[ai]; 261 if (a && a.specified && a.name.indexOf("data-img-") === 0){ 262 s.setAttr(img,a.name.slice(9 - a.name.length),a.value); 263 } 264 } 265 div.appendChild(img); 266 }else{ 267 //noscript isn't part of DOM, so we have to recreate it, unescaping html, src->data-src 268 div.innerHTML = contents.replace(/\s+src\s*=\s*(['"])/i, " data-src=$1"). 269 replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); 270 } 271 //Clear source values before we add it back to the dom, ensure data-slimmage is set. 272 var childImages = div.getElementsByTagName("img"); 273 for (j = 0, jl = childImages.length; j < jl; j++) { 274 var ci = childImages[j]; 275 if (ci.src !== null && ci.src.length > 0) { 276 s.setAttr(ci,"data-src", ci.src); 277 ci.src = ""; 278 } 279 s.setAttr(ci,"data-slimmage", true); 280 ns.parentNode.insertBefore(ci, ns); 281 newImages++; 282 } 283 //2. Remove old noscript tags 284 ns.parentNode.removeChild(ns); 285 } 286 } 287 288 //3. Let plugins inject custom behavior 289 if('function' === typeof s['beforeAdjustSrc']) { 290 s['beforeAdjustSrc'](); 291 } 292 293 //4. Find images with data-slimmage and run adjustImageSrc. 294 var totalImages = 0; 295 var images = s.nodesToArray(w.document.getElementsByTagName("img")); 296 for (k = 0, kl = images.length; k < kl; k++) { 297 if (images[k].getAttribute("data-slimmage") !== null) { 298 var previousSrc = images[k].getAttribute("data-src") || images[k].src; 299 s['adjustImageSrc'](images[k], previousSrc); 300 totalImages++; 301 } 302 } 303 304 var changed = s['changed'].slice(); 305 s['changed'].length = 0; 306 307 //5. Callback when ready 308 if((changed.length > 0 || !s.readyCalled) && 'function' === typeof s['readyCallback']) { 309 s['readyCallback'](changed); 310 s.readyCalled = true; 311 } 312 313 log("Slimmage: restored " + newImages + " images from noscript tags, checked " + totalImages + " images, changed " + changed.length + ". " + (new Date().getTime() - stopwatch) + "ms"); 314 }; 315 316 s['checkResponsiveImages'] = s.cr; 317 // Run on resize and domready (w.load as a fallback) 318 if (w.addEventListener) { 319 w.addEventListener("resize", function () { s.cr(500); }, false); 320 w.addEventListener("DOMContentLoaded", function () { 321 s.cr(); 322 // Run once only 323 w.removeEventListener("load", s.cr, false); 324 }, false); 325 w.addEventListener("load", s.cr, false); 326 } else if (w.attachEvent) { 327 w.attachEvent("onload", s.cr); 328 w.attachEvent("onresize", function () { s.cr(500); }); 329 } 330 331}(this));
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.