Commit 152f93d9 authored by Sven Franck's avatar Sven Franck

ext/app: added responsive images via picturefill

parent 85f372b3
......@@ -657,13 +657,15 @@ span.offer_tag {
padding: .5em;
line-height: .5em;
}
.ui-header .ui-first-wrap ~ .ui-title-logo {
margin-left: 6em;
.ui-header .ui-first-wrap ~ .ui-title-logo,
.ui-header .ui-first-wrap ~ picture {
margin-left: 5em;
}
/* logo in header */
@media (max-width: 24em) {
.ui-header .ui-first-wrap ~ .ui-title-logo {
margin-left: 2.8em;
.ui-header .ui-first-wrap ~ .ui-title-logo,
.ui-header .ui-first-wrap ~ picture {
margin-left: 3em;
}
}
/* img in subheader */
......
......@@ -48,8 +48,11 @@
"theme": "slapos-white",
"class_list": "override_header ",
"image": {
"src": "img/logo.jpg",
"alt": "Bip&Go"
"alt": "Bip&Go",
"src_set": [
{"src": "img/logo-large.jpg", "media": "(min-width: 40em)"},
{"src": "img/logo.jpg"}
]
}
},
"children": [{
......
/*! Picturefill - Responsive Images that work today.
* Author: Scott Jehl, Filament Group, 2012 ( new proposal implemented by Shawn Jansepar )
* License: MIT/GPLv2
* Spec: http://picture.responsiveimages.org/
*/
(function( w, doc ) {
// Enable strict mode
"use strict";
// If picture is supported, well, that's awesome. Let's get outta here...
if( w.HTMLPictureElement ){
return;
}
// HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround)
doc.createElement( "picture" );
// local object for method references and testing exposure
var pf = {};
// namespace
pf.ns = "picturefill";
// srcset support test
pf.srcsetSupported = new w.Image().srcset !== undefined;
// just a string trim workaround
pf.trim = function( str ){
return str.trim ? str.trim() : str.replace( /^\s+|\s+$/g, "" );
};
// just a string endsWith workaround
pf.endsWith = function( str, suffix ){
return str.endsWith ? str.endsWith( suffix ) : str.indexOf( suffix, str.length - suffix.length ) !== -1;
};
/**
* Shortcut method for matchMedia ( for easy overriding in tests )
*/
pf.matchesMedia = function( media ) {
return w.matchMedia && w.matchMedia( media ).matches;
};
/**
* Shortcut method for `devicePixelRatio` ( for easy overriding in tests )
*/
pf.getDpr = function() {
return ( w.devicePixelRatio || 1 );
};
/**
* Get width in css pixel value from a "length" value
* http://dev.w3.org/csswg/css-values-3/#length-value
*/
pf.getWidthFromLength = function( length ) {
// If no length was specified, or it is 0, default to `100vw` (per the spec).
length = length && parseFloat( length ) > 0 ? length : "100vw";
/**
* If length is specified in `vw` units, use `%` instead since the div we’re measuring
* is injected at the top of the document.
*
* TODO: maybe we should put this behind a feature test for `vw`?
*/
length = length.replace( "vw", "%" );
// Create a cached element for getting length value widths
if( !pf.lengthEl ){
pf.lengthEl = doc.createElement( "div" );
doc.documentElement.insertBefore( pf.lengthEl, doc.documentElement.firstChild );
}
// Positioning styles help prevent padding/margin/width on `html` from throwing calculations off.
pf.lengthEl.style.cssText = "position: absolute; left: 0; width: " + length + ";";
// Using offsetWidth to get width from CSS
return pf.lengthEl.offsetWidth;
};
// container of supported mime types that one might need to qualify before using
pf.types = {};
// test svg support
pf.types[ "image/svg+xml" ] = doc.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Image', '1.1');
// test webp support, only when the markup calls for it
pf.types[ "image/webp" ] = function(){
// based on Modernizr's lossless img-webp test
// note: asynchronous
var img = new w.Image(),
type = "image/webp";
img.onerror = function(){
pf.types[ type ] = false;
picturefill();
};
img.onload = function(){
pf.types[ type ] = img.width === 1;
picturefill();
};
img.src = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=';
};
/**
* Takes a source element and checks if its type attribute is present and if so, supported
* Note: for type tests that require a async logic,
* you can define them as a function that'll run only if that type needs to be tested. Just make the test function call picturefill again when it is complete.
* see the async webp test above for example
*/
pf.verifyTypeSupport = function( source ){
var type = source.getAttribute( "type" );
// if type attribute exists, return test result, otherwise return true
if( type === null || type === "" ){
return true;
}
else {
// if the type test is a function, run it and return "pending" status. The function will rerun picturefill on pending elements once finished.
if( typeof( pf.types[ type ] ) === "function" ){
pf.types[ type ]();
return "pending";
}
else {
return pf.types[ type ];
}
}
};
/**
* Parses an individual `size` and returns the length, and optional media query
*/
pf.parseSize = function( sourceSizeStr ) {
var match = /(\([^)]+\))?\s*(.+)/g.exec( sourceSizeStr );
return {
media: match && match[1],
length: match && match[2]
};
};
/**
* Takes a string of sizes and returns the width in pixels as a number
*/
pf.findWidthFromSourceSize = function( sourceSizeListStr ) {
// Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33%
// or (min-width:30em) calc(30% - 15px)
var sourceSizeList = pf.trim( sourceSizeListStr ).split( /\s*,\s*/ ),
winningLength;
for ( var i=0, len=sourceSizeList.length; i < len; i++ ) {
// Match <media-condition>? length, ie ( min-width: 50em ) 100%
var sourceSize = sourceSizeList[ i ],
// Split "( min-width: 50em ) 100%" into separate strings
parsedSize = pf.parseSize( sourceSize ),
length = parsedSize.length,
media = parsedSize.media;
if ( !length ) {
continue;
}
if ( !media || pf.matchesMedia( media ) ) {
// if there is no media query or it matches, choose this as our winning length
// and end algorithm
winningLength = length;
break;
}
}
// pass the length to a method that can properly determine length
// in pixels based on these formats: http://dev.w3.org/csswg/css-values-3/#length-value
return pf.getWidthFromLength( winningLength );
};
/**
* Takes a srcset in the form of url/
* ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or
* "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or
* "images/pic-small.png"
* Get an array of image candidates in the form of
* {url: "/foo/bar.png", resolution: 1}
* where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value
* If sizes is specified, resolution is calculated
*/
pf.getCandidatesFromSourceSet = function( srcset, sizes ) {
var candidates = pf.trim( srcset ).split( /,\s+/ ),
widthInCssPixels = sizes ? pf.findWidthFromSourceSize( sizes ) : "100%",
formattedCandidates = [];
for ( var i = 0, len = candidates.length; i < len; i++ ) {
var candidate = candidates[ i ],
candidateArr = candidate.split( /\s+/ ),
sizeDescriptor = candidateArr[ 1 ],
resolution;
if ( sizeDescriptor && ( sizeDescriptor.slice( -1 ) === "w" || sizeDescriptor.slice( -1 ) === "x" ) ) {
sizeDescriptor = sizeDescriptor.slice( 0, -1 );
}
if ( sizes ) {
// get the dpr by taking the length / width in css pixels
resolution = parseFloat( ( parseInt( sizeDescriptor, 10 ) / widthInCssPixels ) );
} else {
// get the dpr by grabbing the value of Nx
resolution = sizeDescriptor ? parseFloat( sizeDescriptor, 10 ) : 1;
}
var formattedCandidate = {
url: candidateArr[ 0 ],
resolution: resolution
};
formattedCandidates.push( formattedCandidate );
}
return formattedCandidates;
};
/*
* if it's an img element and it has a srcset property,
* we need to remove the attribute so we can manipulate src
* (the property's existence infers native srcset support, and a srcset-supporting browser will prioritize srcset's value over our winning picture candidate)
* this moves srcset's value to memory for later use and removes the attr
*/
pf.dodgeSrcset = function( img ){
if( img.srcset ){
img[ pf.ns ].srcset = img.srcset;
img.removeAttribute( "srcset" );
}
};
/*
* Accept a source or img element and process its srcset and sizes attrs
*/
pf.processSourceSet = function( el ) {
var srcset = el.getAttribute( "srcset" ),
sizes = el.getAttribute( "sizes" ),
candidates = [];
// if it's an img element, use the cached srcset property (defined or not)
if( el.nodeName.toUpperCase() === "IMG" && el[ pf.ns ] && el[ pf.ns ].srcset ){
srcset = el[ pf.ns ].srcset;
}
if( srcset ) {
candidates = pf.getCandidatesFromSourceSet( srcset, sizes );
}
return candidates;
};
pf.applyBestCandidate = function( candidates, picImg ) {
var candidate,
length,
bestCandidate;
candidates.sort( pf.ascendingSort );
length = candidates.length;
bestCandidate = candidates[ length - 1 ];
for ( var l=0; l < length; l++ ) {
candidate = candidates[ l ];
if ( candidate.resolution >= pf.getDpr() ) {
bestCandidate = candidate;
break;
}
}
if ( !pf.endsWith( picImg.src, bestCandidate.url ) ) {
picImg.src = bestCandidate.url;
// currentSrc attribute and property to match
// http://picture.responsiveimages.org/#the-img-element
picImg.currentSrc = picImg.src;
}
};
pf.ascendingSort = function( a, b ) {
return a.resolution - b.resolution;
};
/*
* In IE9, <source> elements get removed if they aren"t children of
* video elements. Thus, we conditionally wrap source elements
* using <!--[if IE 9]><video style="display: none;"><![endif]-->
* and must account for that here by moving those source elements
* back into the picture element.
*/
pf.removeVideoShim = function( picture ){
var videos = picture.getElementsByTagName( "video" );
if ( videos.length ) {
var video = videos[ 0 ],
vsources = video.getElementsByTagName( "source" );
while ( vsources.length ) {
picture.insertBefore( vsources[ 0 ], video );
}
// Remove the video element once we're finished removing its children
video.parentNode.removeChild( video );
}
};
/*
* Find all picture elements and,
* in browsers that don't natively support srcset, find all img elements
* with srcset attrs that don't have picture parents
*/
pf.getAllElements = function() {
var pictures = doc.getElementsByTagName( "picture" ),
elems = [],
imgs = doc.getElementsByTagName( "img" );
for ( var h = 0, len = pictures.length + imgs.length; h < len; h++ ) {
if ( h < pictures.length ){
elems[ h ] = pictures[ h ];
}
else {
var currImg = imgs[ h - pictures.length ];
if ( currImg.parentNode.nodeName.toUpperCase() !== "PICTURE" &&
( ( pf.srcsetSupported && currImg.getAttribute( "sizes" ) ) ||
currImg.getAttribute( "srcset" ) !== null ) ) {
elems.push( currImg );
}
}
}
return elems;
};
pf.getMatch = function( picture ) {
var sources = picture.childNodes,
match;
// Go through each child, and if they have media queries, evaluate them
for ( var j=0, slen = sources.length; j < slen; j++ ) {
var source = sources[ j ];
// ignore non-element nodes
if( source.nodeType !== 1 ){
continue;
}
// Hitting an `img` element stops the search for `sources`.
// If no previous `source` matches, the `img` itself is evaluated later.
if( source.nodeName.toUpperCase() === "IMG" ) {
return match;
}
// ignore non-`source` nodes
if( source.nodeName.toUpperCase() !== "SOURCE" ){
continue;
}
var media = source.getAttribute( "media" );
// if source does not have a srcset attribute, skip
if ( !source.getAttribute( "srcset" ) ) {
continue;
}
// if there"s no media specified, OR w.matchMedia is supported
if( ( !media || pf.matchesMedia( media ) ) ){
var typeSupported = pf.verifyTypeSupport( source );
if( typeSupported === true ){
match = source;
break;
} else if( typeSupported === "pending" ){
return false;
}
}
}
return match;
};
function picturefill( options ) {
var elements,
element,
elemType,
firstMatch,
candidates,
picImg;
options = options || {};
elements = options.elements || pf.getAllElements();
// Loop through all elements
for ( var i=0, plen = elements.length; i < plen; i++ ) {
element = elements[ i ];
elemType = element.nodeName.toUpperCase();
firstMatch = undefined;
candidates = undefined;
picImg = undefined;
// expando for caching data on the img
if( !element[ pf.ns ] ){
element[ pf.ns ] = {};
}
// if the element has already been evaluated, skip it
// unless `options.force` is set to true ( this, for example,
// is set to true when running `picturefill` on `resize` ).
if ( !options.reevaluate && element[ pf.ns ].evaluated ) {
continue;
}
// if element is a picture element
if( elemType === "PICTURE" ){
// IE9 video workaround
pf.removeVideoShim( element );
// return the first match which might undefined
// returns false if there is a pending source
// TODO the return type here is brutal, cleanup
firstMatch = pf.getMatch( element );
// if any sources are pending in this picture due to async type test(s)
// remove the evaluated attr and skip for now ( the pending test will
// rerun picturefill on this element when complete)
if( firstMatch === false ) {
continue;
}
// Find any existing img element in the picture element
picImg = element.getElementsByTagName( "img" )[ 0 ];
} else {
// if it's an img element
firstMatch = undefined;
picImg = element;
}
if( picImg ) {
// expando for caching data on the img
if( !picImg[ pf.ns ] ){
picImg[ pf.ns ] = {};
}
// Cache and remove `srcset` if present and we’re going to be doing `sizes`/`picture` polyfilling to it.
if( picImg.srcset && ( elemType === "PICTURE" || picImg.getAttribute( "sizes" ) ) ){
pf.dodgeSrcset( picImg );
}
if ( firstMatch ) {
candidates = pf.processSourceSet( firstMatch );
pf.applyBestCandidate( candidates, picImg );
} else {
// No sources matched, so we’re down to processing the inner `img` as a source.
candidates = pf.processSourceSet( picImg );
if( picImg.srcset === undefined || picImg.getAttribute( "sizes" ) ) {
// Either `srcset` is completely unsupported, or we need to polyfill `sizes` functionality.
pf.applyBestCandidate( candidates, picImg );
} // Else, resolution-only `srcset` is supported natively.
}
// set evaluated to true to avoid unnecessary reparsing
element[ pf.ns ].evaluated = true;
}
}
}
/**
* Sets up picture polyfill by polling the document and running
* the polyfill every 250ms until the document is ready.
* Also attaches picturefill on resize
*/
function runPicturefill() {
picturefill();
var intervalId = setInterval( function(){
// When the document has finished loading, stop checking for new images
// https://github.com/ded/domready/blob/master/ready.js#L15
w.picturefill();
if ( /^loaded|^i|^c/.test( doc.readyState ) ) {
clearInterval( intervalId );
return;
}
}, 250 );
if( w.addEventListener ){
var resizeThrottle;
w.addEventListener( "resize", function() {
w.clearTimeout( resizeThrottle );
resizeThrottle = w.setTimeout( function(){
picturefill({ reevaluate: true });
}, 60 );
}, false );
}
}
runPicturefill();
/* expose methods for testing */
picturefill._ = pf;
/* expose picturefill */
if ( typeof module === "object" && typeof module.exports === "object" ){
// CommonJS, just export
module.exports = picturefill;
}
else if( typeof define === "object" && define.amd ){
// AMD support
define( function(){ return picturefill; } );
}
else if( typeof w === "object" ){
// If no AMD and we are in the browser, attach to window
w.picturefill = picturefill;
}
} )( this, this.document );
......@@ -28,6 +28,7 @@
<!-- 3rd party plugins -->
<script type="text/javascript" src="ext/plugins/validval/jquery.validVal.js"></script>
<script type="text/javascript" src="ext/plugins/i18next/i18next.js"></script>
<script type="text/javascript" src="ext/plugins/picturefill/picturefill.js"></script>
<!-- stuff happens here -->
<script type="text/javascript" data-storage="data/storages.json" data-config="data/global.json" src="js/erp5_loader.js"></script>
......
......@@ -71,6 +71,7 @@
case "rows":
case "cols":
case "name":
case "media":
case "value":
case "data-":
case "role":
......@@ -876,7 +877,7 @@
// TODO: too complicated and too much custom stuff passed back
// This cannot be so difficult...
factory.widget.header = function (spec) {
var container, id, target, set_img, img;
var container, id, target, set_img, img, pic;
container = document.createDocumentFragment();
id = spec.id || ((spec.data_url || "global") + "-header");
......@@ -885,7 +886,7 @@
// button group wrappers and title (inserted before last wrapper!)
target = function (new_spec) {
var position, drop_content, fragment, count, config;
var position, drop_content, fragment, count, config, i, len, source;
count = new_spec.j;
config = new_spec.config;
......@@ -904,6 +905,41 @@
("[alt]" + config.title_i18n) : null
}
}));
// TODO: separate method!
} else if (config.src_set) {
// NOTE: responsive <picture> element
pic = factory.element({"type": "picture"});
len = config.src_set.length;
// // all hail IE9
// pic.appendChild(
// document.createTextNode('<!--[if IE 9]><video style="display: none;"><![endif]-->')
// );
for (i = 0; i < len; i += 1) {
pic.appendChild(factory.element({
"type": "source",
"attributes": {"srcset": config.src_set[i].src},
"logic": {"media": config.src_set[i].media || null}
}));
}
// // again...
// pic.appendChild(
// document.createTextNode('<!--[if IE 9]></video><![endif]-->')
// );
pic.appendChild(factory.element({
"type": "img",
"direct": {"className": "ui-title-logo", "alt": config.alt},
"attributes": {"srcset": config.src_set[len - 1].srcset}
}));
source = factory.element({
"type": "img",
"direct": {"alt": config.alt},
"attributes": {"srcset": config.src_set[len - 1].srcset}
});
fragment.appendChild(pic);
} else {
fragment.appendChild(factory.element({
"type": "h1",
......@@ -956,8 +992,9 @@
"target_selector": "last",
// TODO: remove this...
"spec": {
"img": !!set_img,
"img": !!set_img && !img.src_set,
"src": img.src,
"src_set": img.src_set,
"title": img.alt || spec.title,
"title_i18n": img.alt_i18n || spec.title_i18n,
"section_list": spec.section_list,
......@@ -7380,6 +7417,9 @@
*/
app.init.contentLoaded(window, function () {
// shim <picture>
document.createElement("picture");
// TODO: Don't wipe. Wait for sync...!
// NOTE: Don't clear if page is opened in popup (like when using oauth)
if (window.opener === null) {
......@@ -7403,6 +7443,9 @@
.then(function () {
// need one call here to initialize whats on the first page
$(document).enhanceWithin();
// picturefill... so much extra §$%&/
window.picturefill();
})
.fail(app.util.error);
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment