1/* 2 PicturePolyfill 3 Responsive Images that work today (and mimic the proposed Picture element with span elements) 4 Author: Andrea Verlicchi 5 License: MIT/GPLv2 6 Please "/dist/picturePolyfill.min.js" for production purposes 7 */ 8 9var picturePolyfill = (function (w) { 10 11 "use strict"; 12 13 var cacheArray, 14 cacheLatestIndex, 15 resizeTimer, 16 timeAfterResize = 100, 17 areListenersActive = false; 18 19 return { 20 21 /** 22 * Get all the "attributes" from an "element" and returns them as a hash 23 * @param element 24 * @param attributes 25 * @returns {{}} 26 * @private 27 */ 28 _getAttrs: function (element, attributes) { 29 var ret = {}, attributeName, attributeValue; 30 for (var i = 0, len = attributes.length; i < len; i += 1) { 31 attributeName = attributes[i]; 32 attributeValue = element.getAttribute(attributeName); 33 if (attributeValue) { 34 ret[attributeName] = attributeValue; 35 } 36 } 37 return ret; 38 }, 39 40 /** 41 * Gets the attributes list from an "element" 42 * @param element 43 * @returns {Array} 44 * @private 45 */ 46 _getAttrsList: function (element) { 47 var arr = []; 48 for (var i = 0, attributes = element.attributes, l = attributes.length; i < l; i++) { 49 arr.push(attributes.item(i).nodeName); 50 } 51 return arr; 52 }, 53 54 /** 55 * Returns a sorted array of object representing the elements of a srcset attribute, 56 * where pxr is the pixel ratio and src is the source for that ratio 57 * @param srcset 58 * @returns {Array} 59 * @private 60 */ 61 _getSrcsetArray: function (srcset) { 62 var srcSetElement, 63 source, 64 density, 65 ret = [], 66 srcSetElements; 67 68 if (srcset === null || srcset === '' || typeof srcset === 'undefined') { 69 return ret; 70 } 71 72 srcSetElements = srcset.split(','); 73 74 for (var i = 0, len = srcSetElements.length; i < len; i += 1) { 75 srcSetElement = srcSetElements[i].trim().split(' '); 76 if (srcSetElement.length === 1) { 77 density = 1; 78 } 79 else { 80 density = parseFloat(srcSetElement[srcSetElement.length - 1], 10); 81 } 82 source = srcSetElement[0]; 83 ret.push({ pxr: density, src: source }); 84 } 85 86 return ret.sort(function (hash1, hash2) { 87 var pxr1 = hash1.pxr, pxr2 = hash2.pxr; 88 if (pxr1 < pxr2) { 89 return -1; 90 } 91 if (pxr1 > pxr2) { 92 return 1; 93 } 94 return 0; 95 }); 96 }, 97 98 /** 99 * Returns the proper src from the given srcset 100 * Get the first valid element from passed position to the left 101 * @param srcset 102 * @param pixelRatio 103 * @returns string || null 104 * @private 105 */ 106 _getSrcFromSrcset: function (srcset, pixelRatio) { 107 var i = 0, 108 array, 109 len, 110 breakPoint = -1; 111 112 if (srcset === null || srcset === '' || typeof srcset === 'undefined') { 113 return ""; 114 } 115 116 array = this._getSrcsetArray(srcset); 117 len = array.length; 118 119 do { 120 if (array[i].pxr >= pixelRatio || i === len - 1) { 121 breakPoint = i; 122 } 123 i += 1; 124 } while (!(breakPoint > -1 || i >= len)); 125 126 return array[breakPoint].src; 127 }, 128 129 /** 130 * Loop through every element of the sourcesData array, check if the media query applies and, 131 * if so, get the matching srcset attribute, if any 132 * @param sourcesData 133 * @returns string 134 * @private 135 */ 136 _getSrcsetFromData: function (sourcesData) { 137 var sourceData, 138 media, 139 src, 140 srcset; 141 142 for (var i = 0, len = sourcesData.length; i < len; i += 1) { 143 sourceData = sourcesData[i]; 144 media = sourceData.media; 145 srcset = sourceData.srcset; 146 src = sourceData.src; 147 if (!media || w.matchMedia(media).matches) { 148 if (!!srcset) { 149 return srcset; 150 } 151 if (!!src) { 152 return src; 153 } 154 return ""; 155 } 156 } 157 return ""; 158 }, 159 160 /** 161 * Get the img tag inside picture, even in IE9 and IE8 162 * @param pictureElement 163 * @returns Array 164 * @private 165 */ 166 _getImgTagsInPicture: function (pictureElement) { 167 var currentElement = pictureElement, 168 imgTags; 169 imgTags = pictureElement.getElementsByTagName('img'); 170 if (imgTags.length > 0) { 171 return imgTags; 172 } 173 // Didn't find anything? 174 // IE8 reads the img tag AFTER the picture tag... 175 // So keep searching in next siblings, and when found it push it in imgTags 176 do { 177 currentElement = currentElement.nextSibling; 178 if (currentElement === null) { 179 return []; 180 } 181 } while (currentElement.tagName !== 'IMG'); 182 // Found, return currentElement in a 1 element array 183 return [currentElement]; 184 }, 185 186 /** 187 * Set the src attribute of the first image element inside passed pictureElement 188 * please not that the img is required in the markup, as stated in the specs 189 * @param pictureElement {Node} 190 * @param attributes 191 */ 192 _setImgAttributes: function (pictureElement, attributes) { 193 var imageElements = this._getImgTagsInPicture(pictureElement), 194 imgEl, givenSrcAttribute, givenSrcsetAttribute, 195 srcToSet, srcsetToSet; 196 197 function _setAttributeIfDifferent(element, attribute, value) { 198 if (element.getAttribute(attribute) !== value) { 199 element.setAttribute(attribute, value); 200 } 201 } 202 203 if (imageElements.length === 0) { 204 return false; 205 } 206 207 // Set original img tag's src and srcset in a data attribute 208 imgEl = imageElements[0]; 209 if (!imgEl.getAttribute('data-original-src')) { 210 _setAttributeIfDifferent(imgEl, 'data-original-src', imgEl.getAttribute('src')); 211 _setAttributeIfDifferent(imgEl, 'data-original-srcset', imgEl.getAttribute('srcset')); 212 } 213 214 // Set srcToSet and srcsetToSet depending on the given src/srcset attributes 215 // If none are given, use original ones 216 // If both ore one are given, use them (even if one is null) 217 givenSrcAttribute = attributes.src; 218 givenSrcsetAttribute = attributes.srcset; 219 if (!givenSrcAttribute && !givenSrcsetAttribute) { 220 srcToSet = imgEl.getAttribute('data-original-src'); 221 srcsetToSet = imgEl.getAttribute('data-original-srcset'); 222 } else { 223 srcToSet = givenSrcAttribute; 224 srcsetToSet = givenSrcsetAttribute; 225 } 226 _setAttributeIfDifferent(imgEl, 'src', srcToSet); 227 _setAttributeIfDifferent(imgEl, 'srcset', srcsetToSet); 228 229 }, 230 231 /** 232 * Parses the picture element looking for sources elements, then 233 * generate the array or string for the SrcSetArray 234 * @param {Array} pictureElement the starting element to parse DOM into. If not passed, it parses the whole document. 235 */ 236 _getSourcesData: function (pictureElement) { 237 var sourcesData = [], 238 sourceElement, 239 sourceData, 240 foundSources = pictureElement.getElementsByTagName('source'); 241 242 for (var i = 0, len = foundSources.length; i < len; i += 1) { 243 sourceElement = foundSources[i];
244 sourceData = this._getAttrs(sourceElement, this._getAttrsList(sourceElement)); 245 sourcesData.push(sourceData); 246 } 247 return sourcesData; 248 }, 249 250 /** 251 * Adds listeners to load and resize event 252 * @private 253 */ 254 _addListeners: function () { 255 256 if (!this.isUseful || areListenersActive) { 257 return false; 258 } 259 260 function parseDocument() { 261 picturePolyfill.parse(document); 262 } 263 264 // Manage resize event only if they've passed 100 milliseconds between a resize event and another 265 // to avoid the script to slow down browsers that animate resize or when browser edge is being manually dragged 266 function parseDocumentAfterTimeout() { 267 clearTimeout(resizeTimer); 268 resizeTimer = setTimeout(parseDocument, timeAfterResize); 269 } 270 271 if (w.addEventListener) { 272 w.addEventListener('resize', parseDocumentAfterTimeout); 273 w.addEventListener('DOMContentLoaded', function () { 274 parseDocument(); 275 w.removeEventListener('load', parseDocument); 276 }); 277 w.addEventListener('load', parseDocument); 278 } 279 else if (w.attachEvent) { 280 w.attachEvent('onload', parseDocument); 281 w.attachEvent('onresize', parseDocumentAfterTimeout); 282 } 283 284 areListenersActive = true; 285 }, 286 287 /** 288 * Initialize and resize event handlers 289 */ 290 initialize: function () { 291 292 /** 293 * The device pixel ratio. 1 for standard displays, 2+ for HD displays 294 * @type {number} 295 * @private 296 */ 297 this._pxRatio = w.devicePixelRatio || 1; 298 299 /** 300 * Detect if browser has media queries support 301 * @type {boolean} 302 * @private 303 */ 304 this._mqSupport = !!w.matchMedia && w.matchMedia("only all") !== null && w.matchMedia("only all").matches; 305 306 /** 307 * Detect if polyfill is necessary 308 * @type {boolean} 309 */ 310 this.isUseful = !w.HTMLPictureElement; 311 312 /** 313 * Cache array, where all sources data is stored 314 * @type {Array} 315 * @private 316 */ 317 cacheArray = []; 318 319 /** 320 * Cache index, incremental 321 * @type {number} 322 * @private 323 */ 324 cacheLatestIndex = 0; 325 326 // Add listeners (listeners are added once) 327 this._addListeners(); 328 }, 329 330 /** 331 * Parses the DOM looking for elements containing the "data-picture" attribute, then 332 * generate the images or updates their src attribute. 333 * @param {Node} element (the starting element to parse DOM into. REQUIRED) 334 */ 335 parse: function (element, readFromCache) { 336 var sourcesData, 337 pictureElement, 338 pictureElements, 339 srcAttribute, 340 srcsetAttribute, 341 mqSupport, 342 cacheIndex; 343 344 // Do nothing if picture is supported 345 if (!this.isUseful) { 346 return 0; 347 } 348 349 // Default readFromCache parameter value 350 if (typeof readFromCache === 'undefined') { 351 readFromCache = true; 352 } 353 354 pictureElements = (element || document).getElementsByTagName('picture'); 355 mqSupport = this._mqSupport; 356 357 for (var i = 0, len = pictureElements.length; i < len; i += 1) { 358 sourcesData = null; 359 pictureElement = pictureElements[i]; 360 // Try to read sources data from cache 361 if (readFromCache) { 362 cacheIndex = pictureElement.getAttribute('data-cache-index'); 363 if (cacheIndex !== null) { 364 sourcesData = cacheArray[cacheIndex]; 365 } 366 } 367 // If no sources are found in cache, try to read sources data from the picture element, then cache them 368 if (!sourcesData) { 369 sourcesData = this._getSourcesData(pictureElement); 370 // Write in cache 371 cacheArray[cacheLatestIndex] = sourcesData; 372 pictureElement.setAttribute('data-cache-index', cacheLatestIndex); 373 cacheLatestIndex += 1;
374 } 375 // If no sourcesData retrieved or media queries are not supported, read from the default src 376 if (!mqSupport || sourcesData.length === 0) { 377 srcAttribute = pictureElement.getAttribute('data-default-src'); 378 srcsetAttribute = pictureElement.getAttribute('data-default-srcset'); 379 } 380 else { 381 srcsetAttribute = this._getSrcsetFromData(sourcesData); 382 srcAttribute = this._getSrcFromSrcset(srcsetAttribute, this._pxRatio); 383 } 384 // Set the img source 385 this._setImgAttributes(pictureElement, { 386 src: srcAttribute, 387 srcset: srcsetAttribute, 388 alt: pictureElement.getAttribute('data-alt') 389 }); 390 } 391 392 return i; 393 } 394 }; 395 396}(window)); 397 398picturePolyfill.initialize(); 399picturePolyfill.parse();
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.