Skip to content

Commit

Permalink
Merge branch 'master' into gh-pages
Browse files Browse the repository at this point in the history
  • Loading branch information
verlok committed Mar 15, 2014
2 parents b73f798 + 8e1c5f9 commit be72a68
Show file tree
Hide file tree
Showing 15 changed files with 506 additions and 282 deletions.
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "picturePolyfill",
"version": "1.0.0",
"version": "2.0.0",
"main": "picturePolyfill.js",
"ignore": [
".idea",
Expand Down
39 changes: 18 additions & 21 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
body { margin: 0; padding: 0; text-align: center; }
.intro { padding: 0 16px; }
.outro { padding: 1px 16px; clear: both; }
.element a, .element span, .element img { display: block; }
.element a, .element span, .element img { display: block; }
img { width: 100%; height: auto; border: 0; }
.element h2 { padding: 0 16px; }
.element:hover h2 { text-decoration: underline; }
Expand All @@ -21,45 +21,42 @@
<div id="container">
<div class="intro">
<h1>Responsive images using <a href="https://github.com/verlok/picturePolyfill">PicturePolyfill</a> - Demo</h1>
<p>The following link contains two <strong>responsive images</strong> created using a <code>&lt;span&gt;</code> element, inline JSON (instead of <code>&lt;source&gt;</code> markup), and the <a href="https://github.com/verlok/picturePolyfill">picturePolyfill</a> script.</p>
<p>The following link contains two <strong>responsive images</strong> created using a real <code>&lt;picture&gt;</code> element, and the <a href="https://github.com/verlok/picturePolyfill">picturePolyfill</a> script.</p>
</div>
<div class="element">
<h2>With HD (Retina) display support</h2>
<a href="#someLink1">
<span data-alt="A beautiful responsive image" data-picture='[
{"srcset": ["http://demo.api.pixtulate.com/demo/large-2.jpg?w=480", "http://demo.api.pixtulate.com/demo/large-2.jpg?w=960"]},
{"media": "(min-width: 481px)", "srcset": ["http://demo.api.pixtulate.com/demo/large-2.jpg?w=512", "http://demo.api.pixtulate.com/demo/large-2.jpg?w=1024"]},
{"media": "(min-width: 1025px)", "srcset": ["http://demo.api.pixtulate.com/demo/large-2.jpg?w=640", "http://demo.api.pixtulate.com/demo/large-2.jpg?w=1280"]},
{"media": "(min-width: 1281px)", "srcset": ["http://demo.api.pixtulate.com/demo/large-2.jpg?w=960", "http://demo.api.pixtulate.com/demo/large-2.jpg?w=1920"]},
{"media": "(min-width: 1921px)", "srcset": ["http://demo.api.pixtulate.com/demo/large-2.jpg?w=1400", "http://demo.api.pixtulate.com/demo/large-2.jpg?w=2800"]}
]'>
<picture data-alt="A beautiful responsive image" data-default-src="img/1440x1440.gif">
<source srcset="img/480x480.gif, img/480x480x2.gif 2x"/>
<source srcset="img/768x768.gif, img/768x768x2.gif 2x" media="(min-width: 481px)"/>
<source srcset="img/1440x1440.gif, img/1440x1440x2.gif 2x" media="(min-width: 1025px)"/>
<source srcset="img/1920x1920.gif, img/1920x1920x2.gif 2x" media="(min-width: 1441px)"/>
<noscript>
<img src="img/1280x1280.gif" alt="A beautiful responsive image"/>
<img src="img/768x768.gif" alt="A beautiful responsive image"/>
</noscript>
</span>
</picture>
</a>
</div>
<div class="element">
<h2>Without HD (Retina) display support</h2>
<a href="#someLink2">
<span data-alt="A beautiful responsive image" data-picture='[
{ "srcset": "http://demo.api.pixtulate.com/demo/large-2.jpg?w=480"},
{"media": "(min-width: 481px)", "srcset": "http://demo.api.pixtulate.com/demo/large-2.jpg?w=512"},
{"media": "(min-width: 1025px)", "srcset": "http://demo.api.pixtulate.com/demo/large-2.jpg?w=640"},
{"media": "(min-width: 1281px)", "srcset": "http://demo.api.pixtulate.com/demo/large-2.jpg?w=960"},
{"media": "(min-width: 1921px)", "srcset": "http://demo.api.pixtulate.com/demo/large-2.jpg?w=1400"}
]'>
<picture data-alt="A beautiful responsive image" data-default-src="img/1440x1440.gif">
<source srcset="img/480x480.gif"/>
<source srcset="img/768x768.gif" media="(min-width: 481px)"/>
<source srcset="img/1440x1440.gif" media="(min-width: 1025px)"/>
<source srcset="img/1920x1920.gif" media="(min-width: 1441px)"/>
<noscript>
<img src="img/1280x1280.gif" alt="A beautiful responsive image"/>
<img src="img/768x768.gif" alt="A beautiful responsive image"/>
</noscript>
</span>
</picture>
</a>
</div>
<!--
<div class="outro">
<p>Real time server side scaling is provided by <a href="http://www.pixtulate.com">Pixtulate</a>.</p>
</div>
-->
</div>
<script src="external/matchMedia.js"></script>
<script src="picturePolyfill.js"></script>
</body>
</html>
249 changes: 151 additions & 98 deletions picturePolyfill.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,155 +5,208 @@
"use strict";

var timerId,
pixelRatio = Math.ceil(w.devicePixelRatio || 1), // The pixel density (2 is for HD aka Retina displays)
mediaQueriesSupported = w.matchMedia && w.matchMedia("only all") !== null && w.matchMedia("only all").matches;
pixelRatio,
mediaQueriesSupported,
browserCanAppendImagesToPictures;

/**
* Detects if browser can append images to pictures
* @returns {boolean}
*/
function detectIfBrowserCanAppendImagesToPictures() {
var newImgElement = document.createElement('img'),
theFirstPictureElement = document.getElementsByTagName("picture")[0];

try {
if (theFirstPictureElement) {
theFirstPictureElement.appendChild(newImgElement);
theFirstPictureElement.removeChild(newImgElement);
}
return true;
}
catch(e) {
return false;
}
}

/**
* Returns the proper src from the srcSet property
* If arrayOrString is a string, returns it
* else get the first valid element from passed position to the left
* @param arrayOrString
* @param position
* @returns {*}
* Replaces the existing picture element with another picture element containing an image with the imgSrc source
* @param picture
* @param imgSrc
* @param imgAlt
*/
function replacePictureWithPictureAndImg(picture, imgSrc, imgAlt) {
var newImage = document.createElement("img"),
newPicture = document.createElement("picture");
newImage.setAttribute('src', imgSrc);
newImage.setAttribute('alt', imgAlt);
newPicture.appendChild(newImage);
picture.parentNode.replaceChild(newPicture, picture);
}

function getSrcFromSrcSet(arrayOrString, position) {
if (typeof arrayOrString === 'string') {
return arrayOrString;
/**
* Returns a hash density > sourceSet
* @param srcSetAttribute
* @returns {{}}
*/
function getSrcSetHash(srcSetAttribute) {
var srcSetElement,
source,
density,
hash = {},
srcSetElements = srcSetAttribute.split(',');

for (var i=0, len=srcSetElements.length; i<len; i+=1) {
srcSetElement = srcSetElements[i].trim().split(' ');
density = srcSetElement[1] ? srcSetElement[1].trim() : "1x";
source = srcSetElement[0].trim();
hash[density] = source;
}
while (arrayOrString[position]===undefined && position>0) {
return hash;
}

/**
* Returns the proper src from the srcSet property
* Get the first valid element from passed position to the left
* @param srcSetArray
* @param position
* @returns {string}
*/
function getSrcFromSrcSetArray(srcSetArray, position) {
var ret;
do {
ret = srcSetArray[position+'x'];
position-=1;
}
return arrayOrString[position];
while (ret===undefined && position>0);
return ret;
}


/**
* Loop through every element of the dataPicture array, check if the media query applies and,
* if so, get the src element from the srcSet property based depending on pixel ratio
* @param dataPicture
* @param dataPicture {element}
* @returns {string}
*/

function getSrcAttributeFromData(dataPicture) {
var media, matchedSrc;
var media,
matchedSrc;

for (var i=0, len=dataPicture.length; i<len; i+=1) {
media = dataPicture[i].media;
if (!media || w.matchMedia(media).matches) {
matchedSrc = getSrcFromSrcSet(dataPicture[i].srcset, pixelRatio-1);
matchedSrc = getSrcFromSrcSetArray(dataPicture[i].srcset, pixelRatio);
}
}
return matchedSrc;
}


/**
* Search for the "standard: true" image in the array
* @param dataPicture
* @returns {string}
* Set the src attribute of the first image element inside passed pictureElement
* if the image doesn't exist, creates it, sets its alt attribute, and appends it to pictureElement
* @param pictureElement
* @param sourcesData
*/
function createOrUpdateImage(pictureElement, sourcesData) {
var imageElement, srcAttribute, altAttribute,
imageElements = pictureElement.getElementsByTagName('img');

function getStandardImageFromData(dataPicture) {
var dataElement;
srcAttribute = (!mediaQueriesSupported || !sourcesData.length) ?
pictureElement.getAttribute("data-default-src") :
getSrcAttributeFromData(sourcesData);

for (var i=0, len=dataPicture.length; i<len; i+=1) {
dataElement = dataPicture[i];
if (dataElement.standard) {
break;
// If image already exists, use it
if (imageElements.length) {
imageElements[0].setAttribute('src', srcAttribute);
}
// Else create the image
else {
altAttribute = pictureElement.getAttribute('data-alt');
if (browserCanAppendImagesToPictures) {
imageElement = document.createElement('img');
imageElement.setAttribute('alt', altAttribute);
imageElement.setAttribute('src', srcAttribute);
pictureElement.appendChild(imageElement);
}
else {
replacePictureWithPictureAndImg(pictureElement, srcAttribute, altAttribute );
}
}
return getSrcFromSrcSet(dataElement.srcset, 0);
}

/**
* Set the src attribute of the first image element inside passed imageHolder
* if the image doesn't exist, creates it, sets its alt attribute, and appends it to imageHolder
* @param imageHolder
* @param srcAttribute
* Parses the picture element looking for sources elements, then
* generate the array or string for the SrcSetArray
* @param pictureElement the starting element to parse DOM into. If not passed, it parses the whole document.
*/

function createOrUpdateImage(imageHolder, srcAttribute) {
var imageElements, imageElement;
imageElements = imageHolder.getElementsByTagName('img');

// If image already exist, use it
if (imageElements.length) {
imageElements[0].setAttribute('src', srcAttribute);
}
// Else create the image
else {
imageElement = document.createElement('img');
imageElement.setAttribute('alt', imageHolder.getAttribute('data-alt'));
imageElement.setAttribute('src', srcAttribute);
imageHolder.appendChild(imageElement);
function parseSources(pictureElement) {
var sourcesData = [],
foundSources = pictureElement.getElementsByTagName('source');

for (var i=0, len = foundSources.length; i<len; i+=1) {
var sourceElement = foundSources[i];
var media = sourceElement.getAttribute('media');
var srcset = getSrcSetHash(sourceElement.getAttribute('srcset'));
sourcesData.push({
'media': media,
'srcset': srcset
});
}
return sourcesData;
}

/**
* Parses the DOM looking for elements containing the "data-picture" attribute, then
* generate the images or updates their src attribute.
* @param element the starting element to parse DOM into. If not passed, it parses the whole document.
*/

function parseDOMTree(element) {
var pictureData, imageHolder,
imageHolders = element.querySelectorAll('[data-picture]');

// Finding all the elements with data-image
for (var i=0, len=imageHolders.length; i<len; i+=1) {
imageHolder = imageHolders[i];
try {
pictureData = JSON.parse(imageHolder.getAttribute('data-picture'));
// Take the source from the matched media, or standard media
// Update the image, or create it
createOrUpdateImage(imageHolder, (mediaQueriesSupported) ?
getSrcAttributeFromData(pictureData) :
getStandardImageFromData(pictureData));
}
catch (e) {
w.console.log(e);
}
function parsePictures(element) {
var sourcesData,
pictureElement,
pictureElements = element.getElementsByTagName('picture');

for (var i=0, len=pictureElements.length; i<len; i+=1) {
pictureElement = pictureElements[i];
sourcesData = parseSources(pictureElement); //NEXT STEP: store sources data somewhere to avoid parsing it every time
createOrUpdateImage(pictureElement, sourcesData);
}
}

/**
* Private function to call w.picturePolyfill on the whole document
* Initialize and resize event handlers
*/
function picturePolyfillDocument() {
w.picturePolyfill(document);
}
function initialize() {

/**
* Expose the function to the global environment, if browser is supported, else empty function
* @type {Function}
*/

w.picturePolyfill = (!document.querySelectorAll) ? function(){} : function(element){
parseDOMTree(element || document);
};
function picturePolyfillDocument() {
parsePictures(document);
}

pixelRatio = (w.devicePixelRatio) ? Math.ceil(w.devicePixelRatio) : 1;
mediaQueriesSupported = w.matchMedia && w.matchMedia("only all") !== null && w.matchMedia("only all").matches;
browserCanAppendImagesToPictures = detectIfBrowserCanAppendImagesToPictures();

if (w.addEventListener) {
// Manage resize event only if they've passed 100 milliseconds between a resize event and another
// to avoid the script to slow down browsers that animate resize or when browser edge is being manually dragged
w.addEventListener('resize', function() {
clearTimeout(timerId);
timerId = setTimeout(picturePolyfillDocument, 100);
});
w.addEventListener('DOMContentLoaded', function(){
picturePolyfillDocument();
w.removeEventListener('load', picturePolyfillDocument);
});
w.addEventListener('load', picturePolyfillDocument);
}
else if (w.attachEvent) {
w.attachEvent('onload', picturePolyfillDocument);
}
}

/**
* Manage resize event calling the parseDOMTree function
* only if they've passed 100 milliseconds between a resize event and another
* to avoid the script to slower the browser on animated resize or browser edge dragging
*/
initialize();

if (w.addEventListener) {
w.addEventListener('resize', function() {
clearTimeout(timerId);
timerId = setTimeout(picturePolyfillDocument, 100);
});
w.addEventListener('DOMContentLoaded', function(){
picturePolyfillDocument();
w.removeEventListener('load', picturePolyfillDocument);
});
w.addEventListener('load', picturePolyfillDocument);
}
else if (w.attachEvent) {
w.attachEvent('onload', picturePolyfillDocument);
}
// Exposing picturePolyfill to the global environment,
// to gain the ability to call picturePolyfill on a slice of DOM (eg: after an AJAX call)
w.picturePolyfill = parsePictures;

}(this));
4 changes: 2 additions & 2 deletions picturePolyfill.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit be72a68

Please sign in to comment.