From 02cad52c6c2f7f6c799ad26a4a109bb55e8dbcce Mon Sep 17 00:00:00 2001 From: Andrea Verlicchi Date: Tue, 22 Jul 2014 09:25:08 +0200 Subject: [PATCH] Managing both `src` and `srcset` in the `img` tag picturePolyfill now manages the case in which the `img` tag contains both src and srcset, and it extracts both `srcset` and `src` from the `source` tags, applying it to the internal `img` tag --- Gruntfile.js | 2 +- bower.json | 2 +- dist/picturePolyfill.min.js | 4 +- index.html | 12 +++--- src/picturePolyfill.js | 76 +++++++++++++++++++---------------- test/picturePolyfill.qunit.js | 52 ++++++++++++------------ 6 files changed, 79 insertions(+), 69 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1fbf813..4fee459 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -5,7 +5,7 @@ module.exports = function(grunt) { grunt.initConfig({ // Metadata. meta: { - version: '4.0.0' + version: '4.1.0' }, banner: '/*! picturePolyfill - v<%= meta.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + diff --git a/bower.json b/bower.json index afb0701..14c0cc8 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "picturePolyfill", - "version": "4.0.0", + "version": "4.1.0", "main": "picturePolyfill.js", "ignore": [ ".idea", diff --git a/dist/picturePolyfill.min.js b/dist/picturePolyfill.min.js index a0c7631..cd64c3d 100644 --- a/dist/picturePolyfill.min.js +++ b/dist/picturePolyfill.min.js @@ -1,4 +1,4 @@ -/*! picturePolyfill - v4.0.0 - 2014-07-22 +/*! picturePolyfill - v4.1.0 - 2014-07-22 * https://github.com/verlok/picturePolyfill/ * Copyright (c) 2014 Andrea "verlok" Verlicchi; Licensed MIT */ -var picturePolyfill=function(a){"use strict";var b,c,d,e=100,f=!1;return{_getAttrs:function(a,b){for(var c,d,e={},f=0,g=b.length;g>f;f+=1)c=b[f],d=a.getAttribute(c),d&&(e[c]=d);return e},_getAttrsList:function(a){for(var b=[],c=0,d=a.attributes,e=d.length;e>c;c++)b.push(d.item(c).nodeName);return b},_getSrcsetArray:function(a){for(var b,c,d,e=[],f=a.split(","),g=0,h=f.length;h>g;g+=1)b=f[g].trim().split(" "),d=1===b.length?1:parseFloat(b[b.length-1],10),c=b[0],e.push({pxr:d,src:c});return e.sort(function(a,b){var c=a.pxr,d=b.pxr;return d>c?-1:c>d?1:0})},_getSrcFromArray:function(a,b){var c=0,d=a.length,e=-1;if(!d)return null;do(a[c].pxr>=b||c===d-1)&&(e=c),c+=1;while(!(e>-1||c>=d));return a[e].src},_getSrcFromData:function(b){for(var c,d,e,f=0,g=b.length;g>f;f+=1)if(c=b[f],d=c.media,e=c.srcset,!d||a.matchMedia(d).matches)return e?this._getSrcFromArray(e,this._pxRatio):c.src;return null},_setImgSrc:function(a,b){var c,d,e,f,g,h,i=a.getElementsByTagName("img");return 0===i.length?!1:(c=i[0],d=c.getAttribute("data-original-src"),e=c.getAttribute("data-original-srcset"),f=b.src,d||(c.setAttribute("data-original-src",c.getAttribute("src")),c.setAttribute("data-original-srcset",c.getAttribute("srcset"))),f?(g=f,h=b.srcset):(g=d,h=e),c.getAttribute("src")!==g&&c.setAttribute("src",g),void(h&&c.getAttribute("srcset")!==h&&c.setAttribute("srcset",h)))},_getSourcesData:function(a){for(var b,c,d=[],e=a.getElementsByTagName("source"),f=0,g=e.length;g>f;f+=1)b=e[f],c=this._getAttrs(b,this._getAttrsList(b)),c.srcset&&(c.srcset=this._getSrcsetArray(c.srcset)),d.push(c);return d},_addListeners:function(){function b(){picturePolyfill.parse(document)}function c(){clearTimeout(d),d=setTimeout(b,e)}return!this.isUseful||f?!1:(a.addEventListener?(a.addEventListener("resize",c),a.addEventListener("DOMContentLoaded",function(){b(),a.removeEventListener("load",b)}),a.addEventListener("load",b)):a.attachEvent&&(a.attachEvent("onload",b),a.attachEvent("onresize",c)),void(f=!0))},initialize:function(){this._pxRatio=a.devicePixelRatio||1,this._mqSupport=!!a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,this.isUseful=!a.HTMLPictureElement,b=[],c=0,this._addListeners()},parse:function(a,d){var e,f,g,h,i,j;if(!this.isUseful)return 0;null===d&&(d=!0),g=(a||document).getElementsByTagName("picture"),i=this._mqSupport;for(var k=0,l=g.length;l>k;k+=1)e=null,f=g[k],d&&(j=f.getAttribute("data-cache-index"),null!==j&&(e=b[j])),e||(e=this._getSourcesData(f),b[c]=e,f.setAttribute("data-cache-index",c),c+=1),h=0!==e.length&&i?this._getSrcFromData(e):f.getAttribute("data-default-src"),this._setImgSrc(f,{src:h,alt:f.getAttribute("data-alt")});return k}}}(window);picturePolyfill.initialize(),picturePolyfill.parse(); \ No newline at end of file +var picturePolyfill=function(a){"use strict";var b,c,d,e=100,f=!1;return{_getAttrs:function(a,b){for(var c,d,e={},f=0,g=b.length;g>f;f+=1)c=b[f],d=a.getAttribute(c),d&&(e[c]=d);return e},_getAttrsList:function(a){for(var b=[],c=0,d=a.attributes,e=d.length;e>c;c++)b.push(d.item(c).nodeName);return b},_getSrcsetArray:function(a){for(var b,c,d,e=[],f=a.split(","),g=0,h=f.length;h>g;g+=1)b=f[g].trim().split(" "),d=1===b.length?1:parseFloat(b[b.length-1],10),c=b[0],e.push({pxr:d,src:c});return e.sort(function(a,b){var c=a.pxr,d=b.pxr;return d>c?-1:c>d?1:0})},_getSrcFromArray:function(a,b){var c=0,d=a.length,e=-1;if(!d)return null;do(a[c].pxr>=b||c===d-1)&&(e=c),c+=1;while(!(e>-1||c>=d));return a[e].src},_getSrcsetFromData:function(b){for(var c,d,e,f=0,g=b.length;g>f;f+=1)if(c=b[f],d=c.media,e=c.srcset,!d||a.matchMedia(d).matches)return e?e:[{pxr:1,src:c.src}];return null},_setImgAttributes:function(a,b){function c(a,b,c){a.getAttribute(b)!==c&&a.setAttribute(b,c)}var d,e,f,g,h,i,j,k=a.getElementsByTagName("img");return 0===k.length?!1:(d=k[0],e=d.getAttribute("data-original-src"),f=d.getAttribute("data-original-srcset"),g=b.src,h=b.src,e||(c(d,"data-original-src",d.getAttribute("src")),c(d,"data-original-srcset",d.getAttribute("srcset"))),g||h?(i=g,j=h):(i=e,j=f),c(d,"src",i),void c(d,"srcset",j))},_getSourcesData:function(a){for(var b,c,d=[],e=a.getElementsByTagName("source"),f=0,g=e.length;g>f;f+=1)b=e[f],c=this._getAttrs(b,this._getAttrsList(b)),c.srcset&&(c.srcset=this._getSrcsetArray(c.srcset)),d.push(c);return d},_addListeners:function(){function b(){picturePolyfill.parse(document)}function c(){clearTimeout(d),d=setTimeout(b,e)}return!this.isUseful||f?!1:(a.addEventListener?(a.addEventListener("resize",c),a.addEventListener("DOMContentLoaded",function(){b(),a.removeEventListener("load",b)}),a.addEventListener("load",b)):a.attachEvent&&(a.attachEvent("onload",b),a.attachEvent("onresize",c)),void(f=!0))},initialize:function(){this._pxRatio=a.devicePixelRatio||1,this._mqSupport=!!a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,this.isUseful=!a.HTMLPictureElement,b=[],c=0,this._addListeners()},parse:function(a,d){var e,f,g,h,i,j,k;if(!this.isUseful)return 0;"undefined"==typeof d&&(d=!0),g=(a||document).getElementsByTagName("picture"),j=this._mqSupport;for(var l=0,m=g.length;m>l;l+=1)e=null,f=g[l],d&&(k=f.getAttribute("data-cache-index"),null!==k&&(e=b[k])),e||(e=this._getSourcesData(f),b[c]=e,f.setAttribute("data-cache-index",c),c+=1),0!==e.length&&j?(i=this._getSrcsetFromData(e),h=this._getSrcFromArray(i,this._pxRatio)):(h=f.getAttribute("data-default-src"),i=f.getAttribute("data-default-srcset")),this._setImgAttributes(f,{src:h,srcset:i,alt:f.getAttribute("data-alt")});return l}}}(window);picturePolyfill.initialize(),picturePolyfill.parse(); \ No newline at end of file diff --git a/index.html b/index.html index 15aa0fe..372dcb0 100644 --- a/index.html +++ b/index.html @@ -86,7 +86,7 @@

With HD (Retina) display support

- A beautiful responsive image + A beautiful responsive image
@@ -104,11 +104,11 @@

Without HD (Retina) display support

Full width pre-scaled image, with HD (Retina) display support

- - - - - A beautiful responsive image + + + + + A beautiful responsive image
diff --git a/src/picturePolyfill.js b/src/picturePolyfill.js index 4b67164..59427a6 100644 --- a/src/picturePolyfill.js +++ b/src/picturePolyfill.js @@ -1,12 +1,10 @@ -/* PicturePolyfill - Responsive Images that work today. (and mimic the proposed Picture element with span elements). Author: Andrea Verlicchi | License: MIT/GPLv2 */ - /* - * TODO: - * ALSO MANAGE SRCSET, IT MIGHT BE ALREADY SUPPORTED BY THE BROWSER - * SO NO NEED TO SET SRC INDIVIDUALLY - * 1. TEST SRCSET SUPPORT SOMEHOW - * 2. IF SRCSET IS SUPPORTED, SET BOTH SRC AND SRCSET IN THE IMG TAG, ELSE SRC ONLY - * */ + PicturePolyfill + Responsive Images that work today (and mimic the proposed Picture element with span elements) + Author: Andrea Verlicchi + License: MIT/GPLv2 + Please "/dist/picturePolyfill.min.js" for production purposes +*/ var picturePolyfill = (function (w) { @@ -117,12 +115,12 @@ var picturePolyfill = (function (w) { /** * Loop through every element of the sourcesData array, check if the media query applies and, - * if so, get the src element from the srcSet property based depending on pixel ratio + * if so, get the matching srcset attribute, if any * @param sourcesData * @returns {string||undefined} * @private */ - _getSrcFromData: function (sourcesData) { + _getSrcsetFromData: function (sourcesData) { var sourceData, media, srcset; @@ -132,7 +130,7 @@ var picturePolyfill = (function (w) { media = sourceData.media; srcset = sourceData.srcset; if (!media || w.matchMedia(media).matches) { - return (srcset) ? this._getSrcFromArray(srcset, this._pxRatio) : sourceData.src; + return (srcset) ? srcset : [{pxr: 1, src: sourceData.src}]; } } return null; @@ -144,44 +142,47 @@ var picturePolyfill = (function (w) { * @param pictureElement {Node} * @param attributes */ - _setImgSrc: function (pictureElement, attributes) { + _setImgAttributes: function (pictureElement, attributes) { var imageElements = pictureElement.getElementsByTagName('img'), - imgEl, originalImgSrc, originalImgSrcset, givenSrcAttribute, + imgEl, originalImgSrc, originalImgSrcset, + givenSrcAttribute, givenSrcsetAttribute, srcToSet, srcsetToSet; + function _setAttributeIfDifferent(element, attribute, value) { + if (element.getAttribute(attribute) !== value) { + element.setAttribute(attribute, value); + } + } + if (imageElements.length === 0) { return false; } + // Setting repeated usage variables imgEl = imageElements[0]; originalImgSrc = imgEl.getAttribute('data-original-src'); originalImgSrcset = imgEl.getAttribute('data-original-srcset'); givenSrcAttribute = attributes.src; + givenSrcsetAttribute = attributes.src; // Set original img tag's src and srcset in a data attribute if (!originalImgSrc) { - imgEl.setAttribute('data-original-src', imgEl.getAttribute('src')); - imgEl.setAttribute('data-original-srcset', imgEl.getAttribute('srcset')); + _setAttributeIfDifferent(imgEl, 'data-original-src', imgEl.getAttribute('src')); + _setAttributeIfDifferent(imgEl, 'data-original-srcset', imgEl.getAttribute('srcset')); } - // Set srcToSet and srcsetToSet depending on the given src attribute - // If the given src is empty, use the original img src and srcset - if (!givenSrcAttribute) { + // Set srcToSet and srcsetToSet depending on the given src/srcset attributes + // If none are given, use original ones + // If both ore one are given, them (even if one is null) + if (!givenSrcAttribute && !givenSrcsetAttribute) { srcToSet = originalImgSrc; srcsetToSet = originalImgSrcset; - } - else { + } else { srcToSet = givenSrcAttribute; - srcsetToSet = attributes.srcset; - } - - if (imgEl.getAttribute('src') !== srcToSet) { - imgEl.setAttribute('src', srcToSet); - } - if (!!srcsetToSet && imgEl.getAttribute('srcset') !== srcsetToSet) { - imgEl.setAttribute('srcset', srcsetToSet); + srcsetToSet = givenSrcsetAttribute; } - + _setAttributeIfDifferent(imgEl, 'src', srcToSet); + _setAttributeIfDifferent(imgEl, 'srcset', srcsetToSet); }, @@ -297,6 +298,7 @@ var picturePolyfill = (function (w) { pictureElement, pictureElements, srcAttribute, + srcsetAttribute, mqSupport, cacheIndex; @@ -306,7 +308,7 @@ var picturePolyfill = (function (w) { } // Default readFromCache parameter value - if (readFromCache === null) { + if (typeof readFromCache === 'undefined') { readFromCache = true; } @@ -332,12 +334,18 @@ var picturePolyfill = (function (w) { cacheLatestIndex += 1; } // If no sourcesData retrieved or media queries are not supported, read from the default src - srcAttribute = (sourcesData.length === 0 || !mqSupport) ? - pictureElement.getAttribute('data-default-src') : - this._getSrcFromData(sourcesData); + if (sourcesData.length === 0 || !mqSupport) { + srcAttribute = pictureElement.getAttribute('data-default-src'); + srcsetAttribute = pictureElement.getAttribute('data-default-srcset'); + } + else { + srcsetAttribute = this._getSrcsetFromData(sourcesData); + srcAttribute = this._getSrcFromArray(srcsetAttribute, this._pxRatio); + } // Set the img source - this._setImgSrc(pictureElement, { + this._setImgAttributes(pictureElement, { src: srcAttribute, + srcset: srcsetAttribute, alt: pictureElement.getAttribute('data-alt') }); } diff --git a/test/picturePolyfill.qunit.js b/test/picturePolyfill.qunit.js index 38c1fba..e85eed5 100644 --- a/test/picturePolyfill.qunit.js +++ b/test/picturePolyfill.qunit.js @@ -1,3 +1,9 @@ +/* +* TODO: Add test on readFromCache parameter of parse() +* +* TODO: Test cases with and sources matching or not matching +* */ + module("picturePolyfill", { setup: function () { // Nothing here! @@ -198,7 +204,7 @@ test("_getSrcsetArray correct behaviour, messy srcset format", function () { } }); -test("_getSrcFromData behaves correctly", function () { +test("_getSrcsetFromData behaves correctly", function () { if (!picturePolyfill._mqSupport) { ok(true); } @@ -246,38 +252,34 @@ test("_getSrcFromData behaves correctly", function () { matchMediaStub.withArgs("(min-width: 200px)").returns(negative); matchMediaStub.withArgs("(min-width: 100px)").returns(negative); matchMediaStub.withArgs(null).returns(positive); - - picturePolyfill._pxRatio = 1; - strictEqual(picturePolyfill._getSrcFromData(sourcesData), "50.gif"); - picturePolyfill._pxRatio = 2; - strictEqual(picturePolyfill._getSrcFromData(sourcesData), "100.gif"); + deepEqual(picturePolyfill._getSrcsetFromData(sourcesData), [ + {pxr: 1, src: "50.gif"}, + {pxr: 2, src: "100.gif"} + ]); // Testing width @ 100px matchMediaStub.withArgs("(min-width: 100px)").returns(positive); - - picturePolyfill._pxRatio = 1; - strictEqual(picturePolyfill._getSrcFromData(sourcesData), "100.gif"); - picturePolyfill._pxRatio = 2; - strictEqual(picturePolyfill._getSrcFromData(sourcesData), "200.gif"); + deepEqual(picturePolyfill._getSrcsetFromData(sourcesData), [ + {pxr: 1, src: "100.gif"}, + {pxr: 2, src: "200.gif"} + ]); // Testing width @ 200px matchMediaStub.withArgs("(min-width: 200px)").returns(positive); - - picturePolyfill._pxRatio = 1; - strictEqual(picturePolyfill._getSrcFromData(sourcesData), "200.gif"); - picturePolyfill._pxRatio = 2; - strictEqual(picturePolyfill._getSrcFromData(sourcesData), "400.gif"); + deepEqual(picturePolyfill._getSrcsetFromData(sourcesData), [ + {pxr: 1, src: "200.gif"}, + {pxr: 2, src: "400.gif"} + ]); // Testing width @ 300px matchMediaStub.withArgs("(min-width: 300px)").returns(positive); - - picturePolyfill._pxRatio = 1; - strictEqual(picturePolyfill._getSrcFromData(sourcesData), "300.gif"); - picturePolyfill._pxRatio = 2; - strictEqual(picturePolyfill._getSrcFromData(sourcesData), "600.gif"); + deepEqual(picturePolyfill._getSrcsetFromData(sourcesData), [ + {pxr: 1, src: "300.gif"}, + {pxr: 2, src: "600.gif"} + ]); } @@ -433,17 +435,17 @@ test("parse() only parses passed element", function () { // Setting spies - this.spy(picturePolyfill, "_setImgSrc"); + this.spy(picturePolyfill, "_setImgAttributes"); // Testing number of calls when calling parse() on the whole document or on a single element picturePolyfill.parse(); - ok(picturePolyfill._setImgSrc.calledTwice); + ok(picturePolyfill._setImgAttributes.calledTwice); - picturePolyfill._setImgSrc.reset(); + picturePolyfill._setImgAttributes.reset(); picturePolyfill.parse(document.getElementById('innerA')); - ok(picturePolyfill._setImgSrc.calledOnce); + ok(picturePolyfill._setImgAttributes.calledOnce); }); test("parse() resulting image sources - with MQ support", function () {