picturefill
Advanced tools
Comparing version
@@ -35,2 +35,1 @@ Authors ordered by first contribution. | ||
Krister Kari, @kristerkari | ||
Zoltan Hawryluk, @zoltan-dulac |
@@ -5,3 +5,2 @@ { | ||
"description": "A Polyfill for the HTML Picture Element (http://picture.responsiveimages.org/) that you can use today.", | ||
"version": "2.3.1", | ||
"main": "dist/picturefill.js", | ||
@@ -32,4 +31,5 @@ "scripts": [ | ||
"tests", | ||
"examples" | ||
"examples", | ||
"perf-test" | ||
] | ||
} |
@@ -1,730 +0,1460 @@ | ||
/*! Picturefill - v2.3.1 - 2015-04-09 | ||
/*! Picturefill - v3.0.0-alpha1 - 2015-06-24 | ||
* http://scottjehl.github.io/picturefill | ||
* Copyright (c) 2015 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT */ | ||
/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */ | ||
(function(window) { | ||
/*jshint eqnull:true */ | ||
var ua = navigator.userAgent; | ||
window.matchMedia || (window.matchMedia = function() { | ||
"use strict"; | ||
if ( window.HTMLPictureElement && ((/ecko/).test(ua) && ua.match(/rv\:(\d+)/) && RegExp.$1 < 41) ) { | ||
addEventListener("resize", (function() { | ||
var timer; | ||
// For browsers that support matchMedium api such as IE 9 and webkit | ||
var styleMedia = (window.styleMedia || window.media); | ||
var dummySrc = document.createElement("source"); | ||
// For those that don't support matchMedium | ||
if (!styleMedia) { | ||
var style = document.createElement('style'), | ||
script = document.getElementsByTagName('script')[0], | ||
info = null; | ||
var fixRespimg = function(img) { | ||
var source, sizes; | ||
var picture = img.parentNode; | ||
style.type = 'text/css'; | ||
style.id = 'matchmediajs-test'; | ||
if (picture.nodeName.toUpperCase() === "PICTURE") { | ||
source = dummySrc.cloneNode(); | ||
script.parentNode.insertBefore(style, script); | ||
picture.insertBefore(source, picture.firstElementChild); | ||
setTimeout(function() { | ||
picture.removeChild(source); | ||
}); | ||
} else if (!img._pfLastSize || img.offsetWidth > img._pfLastSize) { | ||
img._pfLastSize = img.offsetWidth; | ||
sizes = img.sizes; | ||
img.sizes += ",100vw"; | ||
setTimeout(function() { | ||
img.sizes = sizes; | ||
}); | ||
} | ||
}; | ||
// 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers | ||
info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle; | ||
var findPictureImgs = function() { | ||
var i; | ||
var imgs = document.querySelectorAll("picture > img, img[srcset][sizes]"); | ||
for (i = 0; i < imgs.length; i++) { | ||
fixRespimg(imgs[i]); | ||
} | ||
}; | ||
var onResize = function() { | ||
clearTimeout(timer); | ||
timer = setTimeout(findPictureImgs, 99); | ||
}; | ||
var mq = window.matchMedia && matchMedia("(orientation: landscape)"); | ||
var init = function() { | ||
onResize(); | ||
styleMedia = { | ||
matchMedium: function(media) { | ||
var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }'; | ||
// 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers | ||
if (style.styleSheet) { | ||
style.styleSheet.cssText = text; | ||
} else { | ||
style.textContent = text; | ||
if (mq && mq.addListener) { | ||
mq.addListener(onResize); | ||
} | ||
}; | ||
// Test if media query is true or false | ||
return info.width === '1px'; | ||
dummySrc.srcset = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; | ||
if (/^[c|i]|d$/.test(document.readyState || "")) { | ||
init(); | ||
} else { | ||
document.addEventListener("DOMContentLoaded", init); | ||
} | ||
}; | ||
return onResize; | ||
})()); | ||
} | ||
})(window); | ||
return function(media) { | ||
return { | ||
matches: styleMedia.matchMedium(media || 'all'), | ||
media: media || 'all' | ||
}; | ||
}; | ||
}()); | ||
/*! 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, image ) { | ||
* Author: Scott Jehl, Filament Group, 2012 ( new proposal implemented by Shawn Jansepar ) | ||
* License: MIT | ||
* Spec: http://picture.responsiveimages.org/ | ||
*/ | ||
(function( window, document, undefined ) { | ||
/* global parseSizes */ | ||
// Enable strict mode | ||
"use strict"; | ||
function expose(picturefill) { | ||
/* expose picturefill */ | ||
if ( typeof module === "object" && typeof module.exports === "object" ) { | ||
// CommonJS, just export | ||
module.exports = picturefill; | ||
} else if ( typeof define === "function" && define.amd ) { | ||
// AMD support | ||
define( "picturefill", function() { return picturefill; } ); | ||
} | ||
if ( typeof w === "object" ) { | ||
// If no AMD and we are in the browser, attach to window | ||
w.picturefill = picturefill; | ||
} | ||
} | ||
// If picture is supported, well, that's awesome. Let's get outta here... | ||
if ( w.HTMLPictureElement ) { | ||
expose(function() { }); | ||
return; | ||
} | ||
// HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround) | ||
doc.createElement( "picture" ); | ||
document.createElement( "picture" ); | ||
var warn, eminpx, alwaysCheckWDescriptor, evalId; | ||
// local object for method references and testing exposure | ||
var pf = w.picturefill || {}; | ||
var pf = {}; | ||
var noop = function() {}; | ||
var image = document.createElement( "img" ); | ||
var getImgAttr = image.getAttribute; | ||
var setImgAttr = image.setAttribute; | ||
var removeImgAttr = image.removeAttribute; | ||
var docElem = document.documentElement; | ||
var types = {}; | ||
var cfg = { | ||
//resource selection: | ||
algorithm: "" | ||
}; | ||
var srcAttr = "data-pfsrc"; | ||
var srcsetAttr = srcAttr + "set"; | ||
// ua sniffing is done for undetectable img loading features, | ||
// to do some non crucial perf optimizations | ||
var ua = navigator.userAgent; | ||
var supportAbort = (/rident/).test(ua) || ((/ecko/).test(ua) && ua.match(/rv\:(\d+)/) && RegExp.$1 > 35 ); | ||
var curSrcProp = "currentSrc"; | ||
var regWDesc = /\s+\+?\d+(e\d+)?w/; | ||
var regSize = /(\([^)]+\))?\s*(.+)/; | ||
var setOptions = window.picturefillCFG; | ||
/** | ||
* Shortcut property for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests ) | ||
*/ | ||
// baseStyle also used by getEmValue (i.e.: width: 1em is important) | ||
var baseStyle = "position:absolute;left:0;visibility:hidden;display:block;padding:0;border:none;font-size:1em;width:1em;overflow:hidden;clip:rect(0px, 0px, 0px, 0px)"; | ||
var fsCss = "font-size:100%!important;"; | ||
var isVwDirty = true; | ||
// namespace | ||
pf.ns = "picturefill"; | ||
var cssCache = {}; | ||
var sizeLengthCache = {}; | ||
var DPR = window.devicePixelRatio; | ||
var units = { | ||
px: 1, | ||
"in": 96 | ||
}; | ||
var anchor = document.createElement( "a" ); | ||
/** | ||
* alreadyRun flag used for setOptions. is it true setOptions will reevaluate | ||
* @type {boolean} | ||
*/ | ||
var alreadyRun = false; | ||
// srcset support test | ||
(function() { | ||
pf.srcsetSupported = "srcset" in image; | ||
pf.sizesSupported = "sizes" in image; | ||
pf.curSrcSupported = "currentSrc" in image; | ||
})(); | ||
// Reusable, non-"g" Regexes | ||
// just a string trim workaround | ||
pf.trim = function( str ) { | ||
return str.trim ? str.trim() : str.replace( /^\s+|\s+$/g, "" ); | ||
// (Don't use \s, to avoid matching non-breaking space.) | ||
var regexLeadingSpaces = /^[ \t\n\r\u000c]+/, | ||
regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/, | ||
regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/, | ||
regexTrailingCommas = /[,]+$/, | ||
regexNonNegativeInteger = /^\d+$/, | ||
// ( Positive or negative or unsigned integers or decimals, without or without exponents. | ||
// Must include at least one digit. | ||
// According to spec tests any decimal point must be followed by a digit. | ||
// No leading plus sign is allowed.) | ||
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number | ||
regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/; | ||
var on = function(obj, evt, fn, capture) { | ||
if ( obj.addEventListener ) { | ||
obj.addEventListener(evt, fn, capture || false); | ||
} else if ( obj.attachEvent ) { | ||
obj.attachEvent( "on" + evt, fn); | ||
} | ||
}; | ||
/** | ||
* Gets a string and returns the absolute URL | ||
* @param src | ||
* @returns {String} absolute URL | ||
* simple memoize function: | ||
*/ | ||
pf.makeUrl = (function() { | ||
var anchor = doc.createElement( "a" ); | ||
return function(src) { | ||
anchor.href = src; | ||
return anchor.href; | ||
var memoize = function(fn) { | ||
var cache = {}; | ||
return function(input) { | ||
if ( !(input in cache) ) { | ||
cache[ input ] = fn(input); | ||
} | ||
return cache[ input ]; | ||
}; | ||
})(); | ||
}; | ||
// UTILITY FUNCTIONS | ||
// Manual is faster than RegEx | ||
// http://jsperf.com/whitespace-character/5 | ||
function isSpace(c) { | ||
return (c === "\u0020" || // space | ||
c === "\u0009" || // horizontal tab | ||
c === "\u000A" || // new line | ||
c === "\u000C" || // form feed | ||
c === "\u000D"); // carriage return | ||
} | ||
/** | ||
* Shortcut method for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests ) | ||
* gets a mediaquery and returns a boolean or gets a css length and returns a number | ||
* @param css mediaqueries or css length | ||
* @returns {boolean|number} | ||
* | ||
* based on: https://gist.github.com/jonathantneal/db4f77009b155f083738 | ||
*/ | ||
pf.restrictsMixedContent = function() { | ||
return w.location.protocol === "https:"; | ||
var evalCSS = (function() { | ||
var regLength = /^([\d\.]+)(em|vw|px)$/; | ||
var replace = function() { | ||
var args = arguments, index = 0, string = args[0]; | ||
while (++index in args) { | ||
string = string.replace(args[index], args[++index]); | ||
} | ||
return string; | ||
}; | ||
var buidlStr = memoize(function(css) { | ||
return "return " + replace((css || "").toLowerCase(), | ||
// interpret `and` | ||
/\band\b/g, "&&", | ||
// interpret `,` | ||
/,/g, "||", | ||
// interpret `min-` as >= | ||
/min-([a-z-\s]+):/g, "e.$1>=", | ||
// interpret `max-` as <= | ||
/max-([a-z-\s]+):/g, "e.$1<=", | ||
//calc value | ||
/calc([^)]+)/g, "($1)", | ||
// interpret css values | ||
/(\d+[\.]*[\d]*)([a-z]+)/g, "($1 * e.$2)", | ||
//make eval less evil | ||
/^(?!(e.[a-z]|[0-9\.&=|><\+\-\*\(\)\/])).*/ig, "" | ||
) + ";"; | ||
}); | ||
return function(css, length) { | ||
var parsedLength; | ||
if (!(css in cssCache)) { | ||
cssCache[css] = false; | ||
if (length && (parsedLength = css.match( regLength ))) { | ||
cssCache[css] = parsedLength[ 1 ] * units[parsedLength[ 2 ]]; | ||
} else { | ||
/*jshint evil:true */ | ||
try{ | ||
cssCache[css] = new Function("e", buidlStr(css))(units); | ||
} catch(e) {} | ||
/*jshint evil:false */ | ||
} | ||
} | ||
return cssCache[css]; | ||
}; | ||
})(); | ||
var setResolution = function( candidate, sizesattr ) { | ||
if ( candidate.w ) { // h = means height: || descriptor.type === 'h' do not handle yet... | ||
candidate.cWidth = pf.calcListLength( sizesattr || "100vw" ); | ||
candidate.res = candidate.w / candidate.cWidth ; | ||
} else { | ||
candidate.res = candidate.d; | ||
} | ||
return candidate; | ||
}; | ||
/** | ||
* Shortcut method for matchMedia ( for easy overriding in tests ) | ||
* | ||
* @param opt | ||
*/ | ||
var picturefill = function( opt ) { | ||
var elements, i, plen; | ||
pf.matchesMedia = function( media ) { | ||
return w.matchMedia && w.matchMedia( media ).matches; | ||
}; | ||
var options = opt || {}; | ||
// Shortcut method for `devicePixelRatio` ( for easy overriding in tests ) | ||
pf.getDpr = function() { | ||
return ( w.devicePixelRatio || 1 ); | ||
if ( options.elements && options.elements.nodeType === 1 ) { | ||
if ( options.elements.nodeName.toUpperCase() === "IMG" ) { | ||
options.elements = [ options.elements ]; | ||
} else { | ||
options.context = options.elements; | ||
options.elements = null; | ||
} | ||
} | ||
elements = options.elements || pf.qsa( (options.context || document), ( options.reevaluate || options.reselect ) ? pf.sel : pf.selShort ); | ||
if ( (plen = elements.length) ) { | ||
pf.setupRun( options ); | ||
alreadyRun = true; | ||
// Loop through all elements | ||
for ( i = 0; i < plen; i++ ) { | ||
pf.fillImg(elements[ i ], options); | ||
} | ||
pf.teardownRun( options ); | ||
} | ||
}; | ||
/** | ||
* Get width in css pixel value from a "length" value | ||
* http://dev.w3.org/csswg/css-values-3/#length-value | ||
* outputs a warning for the developer | ||
* @param {message} | ||
* @type {Function} | ||
*/ | ||
pf.getWidthFromLength = function( length ) { | ||
var cssValue; | ||
// If a length is specified and doesn’t contain a percentage, and it is greater than 0 or using `calc`, use it. Else, abort. | ||
if ( !(length && length.indexOf( "%" ) > -1 === false && ( parseFloat( length ) > 0 || length.indexOf( "calc(" ) > -1 )) ) { | ||
return false; | ||
} | ||
warn = ( window.console && console.warn ) ? | ||
function( message ) { | ||
console.warn( message ); | ||
} : | ||
noop | ||
; | ||
/** | ||
* 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`? The risk of doing this is possible browser inconsistancies with vw vs % | ||
*/ | ||
length = length.replace( "vw", "%" ); | ||
if ( !(curSrcProp in image) ) { | ||
curSrcProp = "src"; | ||
} | ||
// Create a cached element for getting length value widths | ||
if ( !pf.lengthEl ) { | ||
pf.lengthEl = doc.createElement( "div" ); | ||
// Add support for standard mime types. | ||
types[ "image/jpeg" ] = true; | ||
types[ "image/gif" ] = true; | ||
types[ "image/png" ] = true; | ||
// Positioning styles help prevent padding/margin/width on `html` or `body` from throwing calculations off. | ||
pf.lengthEl.style.cssText = "border:0;display:block;font-size:1em;left:0;margin:0;padding:0;position:absolute;visibility:hidden"; | ||
function detectTypeSupport( type, typeUri ) { | ||
// based on Modernizr's lossless img-webp test | ||
// note: asynchronous | ||
var image = new window.Image(); | ||
image.onerror = function() { | ||
types[ type ] = false; | ||
picturefill(); | ||
}; | ||
image.onload = function() { | ||
types[ type ] = image.width === 1; | ||
picturefill(); | ||
}; | ||
image.src = typeUri; | ||
return "pending"; | ||
} | ||
// Add a class, so that everyone knows where this element comes from | ||
pf.lengthEl.className = "helper-from-picturefill-js"; | ||
} | ||
// test svg support | ||
types[ "image/svg+xml" ] = document.implementation.hasFeature( "http://wwwindow.w3.org/TR/SVG11/feature#Image", "1.1" ); | ||
pf.lengthEl.style.width = "0px"; | ||
/** | ||
* updates the internal vW property with the current viewport width in px | ||
*/ | ||
function updateMetrics() { | ||
try { | ||
pf.lengthEl.style.width = length; | ||
} catch ( e ) {} | ||
isVwDirty = false; | ||
DPR = window.devicePixelRatio; | ||
cssCache = {}; | ||
sizeLengthCache = {}; | ||
doc.body.appendChild(pf.lengthEl); | ||
pf.DPR = DPR || 1; | ||
cssValue = pf.lengthEl.offsetWidth; | ||
units.width = Math.max(window.innerWidth || 0, docElem.clientWidth); | ||
units.height = Math.max(window.innerHeight || 0, docElem.clientHeight); | ||
if ( cssValue <= 0 ) { | ||
cssValue = false; | ||
} | ||
units.vw = units.width / 100; | ||
units.vh = units.height / 100; | ||
doc.body.removeChild( pf.lengthEl ); | ||
evalId = [ units.height, units.width, DPR ].join("-"); | ||
return cssValue; | ||
}; | ||
units.em = pf.getEmValue(); | ||
units.rem = units.em; | ||
} | ||
pf.detectTypeSupport = function( type, typeUri ) { | ||
// based on Modernizr's lossless img-webp test | ||
// note: asynchronous | ||
var image = new w.Image(); | ||
image.onerror = function() { | ||
pf.types[ type ] = false; | ||
picturefill(); | ||
}; | ||
image.onload = function() { | ||
pf.types[ type ] = image.width === 1; | ||
picturefill(); | ||
}; | ||
image.src = typeUri; | ||
function chooseLowRes( lowerValue, higherValue, dprValue, isCached ) { | ||
var bonusFactor, tooMuch, bonus, meanDensity; | ||
return "pending"; | ||
}; | ||
// container of supported mime types that one might need to qualify before using | ||
pf.types = pf.types || {}; | ||
//experimental | ||
if (cfg.algorithm === "saveData" ){ | ||
if ( lowerValue > 2.7 ) { | ||
meanDensity = dprValue + 1; | ||
} else { | ||
tooMuch = higherValue - dprValue; | ||
bonusFactor = Math.pow(lowerValue - 0.6, 1.5); | ||
pf.initTypeDetects = function() { | ||
// Add support for standard mime types | ||
pf.types[ "image/jpeg" ] = true; | ||
pf.types[ "image/gif" ] = true; | ||
pf.types[ "image/png" ] = true; | ||
pf.types[ "image/svg+xml" ] = doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1"); | ||
pf.types[ "image/webp" ] = pf.detectTypeSupport("image/webp", "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA="); | ||
}; | ||
bonus = tooMuch * bonusFactor; | ||
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; | ||
if (isCached) { | ||
bonus += 0.1 * bonusFactor; | ||
} | ||
meanDensity = lowerValue + bonus; | ||
} | ||
} else { | ||
var pfType = pf.types[ type ]; | ||
// 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 pfType === "string" && pfType !== "pending") { | ||
pf.types[ type ] = pf.detectTypeSupport( type, pfType ); | ||
return "pending"; | ||
} else if ( typeof pfType === "function" ) { | ||
pfType(); | ||
return "pending"; | ||
} else { | ||
return pfType; | ||
meanDensity = (dprValue > 1) ? | ||
Math.sqrt(lowerValue * higherValue) : | ||
lowerValue; | ||
} | ||
return meanDensity > dprValue; | ||
} | ||
function applyBestCandidate( img ) { | ||
var srcSetCandidates; | ||
var matchingSet = pf.getSet( img ); | ||
var evaluated = false; | ||
if ( matchingSet !== "pending" ) { | ||
evaluated = evalId; | ||
if ( matchingSet ) { | ||
srcSetCandidates = pf.setRes( matchingSet ); | ||
pf.applySetCandidate( srcSetCandidates, img ); | ||
} | ||
} | ||
}; | ||
img[ pf.ns ].evaled = evaluated; | ||
} | ||
// 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] | ||
}; | ||
}; | ||
function ascendingSort( a, b ) { | ||
return a.res - b.res; | ||
} | ||
// 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; | ||
function setSrcToCur( img, src, set ) { | ||
var candidate; | ||
if ( !set && src ) { | ||
set = img[ pf.ns ].sets; | ||
set = set && set[set.length - 1]; | ||
} | ||
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; | ||
candidate = getCandidateForSrc(src, set); | ||
if ( !length ) { | ||
continue; | ||
if ( candidate ) { | ||
src = pf.makeUrl(src); | ||
img[ pf.ns ].curSrc = src; | ||
img[ pf.ns ].curCan = candidate; | ||
if ( !candidate.res ) { | ||
setResolution( candidate, candidate.set.sizes ); | ||
} | ||
// if there is no media query or it matches, choose this as our winning length | ||
if ( (!media || pf.matchesMedia( media )) && | ||
// 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 | ||
(winningLength = pf.getWidthFromLength( length )) ) { | ||
break; | ||
} | ||
return candidate; | ||
} | ||
function getCandidateForSrc( src, set ) { | ||
var i, candidate, candidates; | ||
if ( src && set ) { | ||
candidates = pf.parseSet( set ); | ||
src = pf.makeUrl(src); | ||
for ( i = 0; i < candidates.length; i++ ) { | ||
if ( src === pf.makeUrl(candidates[ i ].url) ) { | ||
candidate = candidates[ i ]; | ||
break; | ||
} | ||
} | ||
} | ||
return candidate; | ||
} | ||
//if we have no winningLength fallback to 100vw | ||
return winningLength || Math.max(w.innerWidth || 0, doc.documentElement.clientWidth); | ||
}; | ||
function getAllSourceElements( picture, candidates ) { | ||
var i, len, source, srcset; | ||
pf.parseSrcset = function( srcset ) { | ||
// SPEC mismatch intended for size and perf: | ||
// actually only source elements preceding the img should be used | ||
// also note: don't use qsa here, because IE8 sometimes doesn't like source as the key part in a selector | ||
var sources = picture.getElementsByTagName( "source" ); | ||
for ( i = 0, len = sources.length; i < len; i++ ) { | ||
source = sources[ i ]; | ||
source[ pf.ns ] = true; | ||
srcset = source.getAttribute( "srcset" ); | ||
// if source does not have a srcset attribute, skip | ||
if ( srcset ) { | ||
candidates.push( { | ||
srcset: srcset, | ||
media: source.getAttribute( "media" ), | ||
type: source.getAttribute( "type" ), | ||
sizes: source.getAttribute( "sizes" ) | ||
} ); | ||
} | ||
} | ||
} | ||
/** | ||
* Srcset Parser | ||
* By Alex Bell | MIT License | ||
* | ||
* @returns Array [{url: _, d: _, w: _, h:_, set:_(????)}, ...] | ||
* | ||
* Based super duper closely on the reference algorithm at: | ||
* https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-srcset-attribute | ||
*/ | ||
// 1. Let input be the value passed to this algorithm. | ||
// (TO-DO : Explain what "set" argument is here. Maybe choose a more | ||
// descriptive & more searchable name. Since passing the "set" in really has | ||
// nothing to do with parsing proper, I would prefer this assignment eventually | ||
// go in an external fn.) | ||
function parseSrcset(input, set) { | ||
function collectCharacters(regEx) { | ||
var chars, | ||
match = regEx.exec(input.substring(pos)); | ||
if (match) { | ||
chars = match[ 0 ]; | ||
pos += chars.length; | ||
return chars; | ||
} | ||
} | ||
var inputLength = input.length, | ||
url, | ||
descriptors, | ||
currentDescriptor, | ||
state, | ||
c, | ||
// 2. Let position be a pointer into input, initially pointing at the start | ||
// of the string. | ||
pos = 0, | ||
// 3. Let candidates be an initially empty source set. | ||
candidates = []; | ||
/** | ||
* A lot of this was pulled from Boris Smus’ parser for the now-defunct WHATWG `srcset` | ||
* https://github.com/borismus/srcset-polyfill/blob/master/js/srcset-info.js | ||
* | ||
* 1. Let input (`srcset`) be the value passed to this algorithm. | ||
* 2. Let position be a pointer into input, initially pointing at the start of the string. | ||
* 3. Let raw candidates be an initially empty ordered list of URLs with associated | ||
* unparsed descriptors. The order of entries in the list is the order in which entries | ||
* are added to the list. | ||
*/ | ||
var candidates = []; | ||
* Adds descriptor properties to a candidate, pushes to the candidates array | ||
* @return undefined | ||
*/ | ||
// (Declared outside of the while loop so that it's only created once. | ||
// (This fn is defined before it is used, in order to pass JSHINT. | ||
// Unfortunately this breaks the sequencing of the spec comments. :/ ) | ||
function parseDescriptors() { | ||
while ( srcset !== "" ) { | ||
srcset = srcset.replace( /^\s+/g, "" ); | ||
// 9. Descriptor parser: Let error be no. | ||
var pError = false, | ||
// 5. Collect a sequence of characters that are not space characters, and let that be url. | ||
var pos = srcset.search(/\s/g), | ||
url, descriptor = null; | ||
// 10. Let width be absent. | ||
// 11. Let density be absent. | ||
// 12. Let future-compat-h be absent. (We're implementing it now as h) | ||
w, d, h, i, | ||
candidate = {}, | ||
desc, lastChar, value, intVal, floatVal; | ||
if ( pos !== -1 ) { | ||
url = srcset.slice( 0, pos ); | ||
// 13. For each descriptor in descriptors, run the appropriate set of steps | ||
// from the following list: | ||
for (i = 0 ; i < descriptors.length; i++) { | ||
desc = descriptors[ i ]; | ||
var last = url.slice(-1); | ||
lastChar = desc[ desc.length - 1 ]; | ||
value = desc.substring(0, desc.length - 1); | ||
intVal = parseInt(value, 10); | ||
floatVal = parseFloat(value); | ||
// 6. If url ends with a U+002C COMMA character (,), remove that character from url | ||
// and let descriptors be the empty string. Otherwise, follow these substeps | ||
// 6.1. If url is empty, then jump to the step labeled descriptor parser. | ||
// If the descriptor consists of a valid non-negative integer followed by | ||
// a U+0077 LATIN SMALL LETTER W character | ||
if (regexNonNegativeInteger.test(value) && (lastChar === "w")) { | ||
if ( last === "," || url === "" ) { | ||
url = url.replace( /,+$/, "" ); | ||
descriptor = ""; | ||
} | ||
srcset = srcset.slice( pos + 1 ); | ||
// If width and density are not both absent, then let error be yes. | ||
if (w || d) {pError = true;} | ||
// 6.2. Collect a sequence of characters that are not U+002C COMMA characters (,), and | ||
// let that be descriptors. | ||
if ( descriptor === null ) { | ||
var descpos = srcset.indexOf( "," ); | ||
if ( descpos !== -1 ) { | ||
descriptor = srcset.slice( 0, descpos ); | ||
srcset = srcset.slice( descpos + 1 ); | ||
// Apply the rules for parsing non-negative integers to the descriptor. | ||
// If the result is zero, let error be yes. | ||
// Otherwise, let width be the result. | ||
if (intVal === 0) {pError = true;} else {w = intVal;} | ||
// If the descriptor consists of a valid floating-point number followed by | ||
// a U+0078 LATIN SMALL LETTER X character | ||
} else if (regexFloatingPoint.test(value) && (lastChar === "x")) { | ||
// If width, density and future-compat-h are not all absent, then let error | ||
// be yes. | ||
if (w || d || h) {pError = true;} | ||
// Apply the rules for parsing floating-point number values to the descriptor. | ||
// If the result is less than zero, let error be yes. Otherwise, let density | ||
// be the result. | ||
if (floatVal < 0) {pError = true;} else {d = floatVal;} | ||
// If the descriptor consists of a valid non-negative integer followed by | ||
// a U+0068 LATIN SMALL LETTER H character | ||
} else if (regexNonNegativeInteger.test(value) && (lastChar === "h")) { | ||
// If height and density are not both absent, then let error be yes. | ||
if (h || d) {pError = true;} | ||
// Apply the rules for parsing non-negative integers to the descriptor. | ||
// If the result is zero, let error be yes. Otherwise, let future-compat-h | ||
// be the result. | ||
if (intVal === 0) {pError = true;} else {h = intVal;} | ||
// Anything else, Let error be yes. | ||
} else {pError = true;} | ||
} // (close step 13 for loop) | ||
// 15. If error is still no, then append a new image source to candidates whose | ||
// URL is url, associated with a width width if not absent and a pixel | ||
// density density if not absent. Otherwise, there is a parse error. | ||
if (!pError) { | ||
candidate.url = url; | ||
if (w) { candidate.w = w;} | ||
if (d) { candidate.d = d;} | ||
if (h) { candidate.h = h;} | ||
if (!h && !d && !w) {candidate.d = 1;} | ||
if (candidate.d === 1) {set.has1x = true;} | ||
candidate.set = set; | ||
candidates.push(candidate); | ||
} | ||
} // (close parseDescriptors fn) | ||
/** | ||
* Tokenizes descriptor properties prior to parsing | ||
* Returns undefined. | ||
* (Again, this fn is defined before it is used, in order to pass JSHINT. | ||
* Unfortunately this breaks the logical sequencing of the spec comments. :/ ) | ||
*/ | ||
function tokenize() { | ||
// 8.1. Descriptor tokeniser: Skip whitespace | ||
collectCharacters(regexLeadingSpaces); | ||
// 8.2. Let current descriptor be the empty string. | ||
currentDescriptor = ""; | ||
// 8.3. Let state be in descriptor. | ||
state = "in descriptor"; | ||
while (true) { | ||
// 8.4. Let c be the character at position. | ||
c = input.charAt(pos); | ||
// Do the following depending on the value of state. | ||
// For the purpose of this step, "EOF" is a special character representing | ||
// that position is past the end of input. | ||
// In descriptor | ||
if (state === "in descriptor") { | ||
// Do the following, depending on the value of c: | ||
// Space character | ||
// If current descriptor is not empty, append current descriptor to | ||
// descriptors and let current descriptor be the empty string. | ||
// Set state to after descriptor. | ||
if (isSpace(c)) { | ||
if (currentDescriptor) { | ||
descriptors.push(currentDescriptor); | ||
currentDescriptor = ""; | ||
state = "after descriptor"; | ||
} | ||
// U+002C COMMA (,) | ||
// Advance position to the next character in input. If current descriptor | ||
// is not empty, append current descriptor to descriptors. Jump to the step | ||
// labeled descriptor parser. | ||
} else if (c === ",") { | ||
pos += 1; | ||
if (currentDescriptor) { | ||
descriptors.push(currentDescriptor); | ||
} | ||
parseDescriptors(); | ||
return; | ||
// U+0028 LEFT PARENTHESIS (() | ||
// Append c to current descriptor. Set state to in parens. | ||
} else if (c === "\u0028") { | ||
currentDescriptor = currentDescriptor + c; | ||
state = "in parens"; | ||
// EOF | ||
// If current descriptor is not empty, append current descriptor to | ||
// descriptors. Jump to the step labeled descriptor parser. | ||
} else if (c === "") { | ||
if (currentDescriptor) { | ||
descriptors.push(currentDescriptor); | ||
} | ||
parseDescriptors(); | ||
return; | ||
// Anything else | ||
// Append c to current descriptor. | ||
} else { | ||
descriptor = srcset; | ||
srcset = ""; | ||
currentDescriptor = currentDescriptor + c; | ||
} | ||
// (end "in descriptor" | ||
// In parens | ||
} else if (state === "in parens") { | ||
// U+0029 RIGHT PARENTHESIS ()) | ||
// Append c to current descriptor. Set state to in descriptor. | ||
if (c === ")") { | ||
currentDescriptor = currentDescriptor + c; | ||
state = "in descriptor"; | ||
// EOF | ||
// Append current descriptor to descriptors. Jump to the step labeled | ||
// descriptor parser. | ||
} else if (c === "") { | ||
descriptors.push(currentDescriptor); | ||
parseDescriptors(); | ||
return; | ||
// Anything else | ||
// Append c to current descriptor. | ||
} else { | ||
currentDescriptor = currentDescriptor + c; | ||
} | ||
// After descriptor | ||
} else if (state === "after descriptor") { | ||
// Do the following, depending on the value of c: | ||
// Space character: Stay in this state. | ||
if (isSpace(c)) { | ||
// EOF: Jump to the step labeled descriptor parser. | ||
} else if (c === "") { | ||
parseDescriptors(); | ||
return; | ||
// Anything else | ||
// Set state to in descriptor. Set position to the previous character in input. | ||
} else { | ||
state = "in descriptor"; | ||
pos -= 1; | ||
} | ||
} | ||
// Advance position to the next character in input. | ||
pos += 1; | ||
// Repeat this step. | ||
} // (close while true loop) | ||
} | ||
// 4. Splitting loop: Collect a sequence of characters that are space | ||
// characters or U+002C COMMA characters. If any U+002C COMMA characters | ||
// were collected, that is a parse error. | ||
while (true) { | ||
collectCharacters(regexLeadingCommasOrSpaces); | ||
// 5. If position is past the end of input, return candidates and abort these steps. | ||
if (pos >= inputLength) { | ||
return candidates; // (we're done, this is the sole return path) | ||
} | ||
// 6. Collect a sequence of characters that are not space characters, | ||
// and let that be url. | ||
url = collectCharacters(regexLeadingNotSpaces); | ||
// 7. Let descriptors be a new empty list. | ||
descriptors = []; | ||
// 8. If url ends with a U+002C COMMA character (,), follow these substeps: | ||
// (1). Remove all trailing U+002C COMMA characters from url. If this removed | ||
// more than one character, that is a parse error. | ||
if (url.slice(-1) === ",") { | ||
url = url.replace(regexTrailingCommas, ""); | ||
// (Jump ahead to step 9 to skip tokenization and just push the candidate). | ||
parseDescriptors(); | ||
// Otherwise, follow these substeps: | ||
} else { | ||
url = srcset; | ||
srcset = ""; | ||
tokenize(); | ||
} // (close else of step 8) | ||
// 16. Return to the step labeled splitting loop. | ||
} // (Close of big while loop.) | ||
} | ||
/* jshint ignore:start */ | ||
// jscs:disable | ||
/* | ||
* Sizes Parser | ||
* | ||
* By Alex Bell | MIT License | ||
* | ||
* Non-strict but accurate and lightweight JS Parser for the string value <img sizes="here"> | ||
* | ||
* Reference algorithm at: | ||
* https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute | ||
* | ||
* Most comments are copied in directly from the spec | ||
* (except for comments in parens). | ||
* | ||
* Grammar is: | ||
* <source-size-list> = <source-size># [ , <source-size-value> ]? | <source-size-value> | ||
* <source-size> = <media-condition> <source-size-value> | ||
* <source-size-value> = <length> | ||
* http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-sizes | ||
* | ||
* E.g. "(max-width: 30em) 100vw, (max-width: 50em) 70vw, 100vw" | ||
* or "(min-width: 30em), calc(30vw - 15px)" or just "30vw" | ||
* | ||
* Returns the first valid <css-length> with a media condition that evaluates to true, | ||
* or "100vw" if all valid media conditions evaluate to false. | ||
* | ||
*/ | ||
function parseSizes(strValue) { | ||
// (Percentage CSS lengths are not allowed in this case, to avoid confusion: | ||
// https://html.spec.whatwg.org/multipage/embedded-content.html#valid-source-size-list | ||
// CSS allows a single optional plus or minus sign: | ||
// http://www.w3.org/TR/CSS2/syndata.html#numbers | ||
// CSS is ASCII case-insensitive: | ||
// http://www.w3.org/TR/CSS2/syndata.html#characters ) | ||
// Spec allows exponential notation for <number> type: | ||
// http://dev.w3.org/csswg/css-values/#numbers | ||
var regexCssLengthWithUnits = /^(?:[+-]?[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?(?:ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmin|vmax|vw)$/i; | ||
// (This is a quick and lenient test. Because of optional unlimited-depth internal | ||
// grouping parens and strict spacing rules, this could get very complicated.) | ||
var regexCssCalc = /^calc\((?:[0-9a-z \.\+\-\*\/\(\)]+)\)$/i; | ||
var i; | ||
var unparsedSizesList; | ||
var unparsedSizesListLength; | ||
var unparsedSize; | ||
var lastComponentValue; | ||
var size; | ||
// UTILITY FUNCTIONS | ||
// (Toy CSS parser. The goals here are: | ||
// 1) expansive test coverage without the weight of a full CSS parser. | ||
// 2) Avoiding regex wherever convenient. | ||
// Quick tests: http://jsfiddle.net/gtntL4gr/3/ | ||
// Returns an array of arrays.) | ||
function parseComponentValues(str) { | ||
var chrctr; | ||
var component = ""; | ||
var componentArray = []; | ||
var listArray = []; | ||
var parenDepth = 0; | ||
var pos = 0; | ||
var inComment = false; | ||
function pushComponent() { | ||
if (component) { | ||
componentArray.push(component); | ||
component = ""; | ||
} | ||
} | ||
// 7. Add url to raw candidates, associated with descriptors. | ||
if ( url || descriptor ) { | ||
candidates.push({ | ||
url: url, | ||
descriptor: descriptor | ||
}); | ||
function pushComponentArray() { | ||
if (componentArray[0]) { | ||
listArray.push(componentArray); | ||
componentArray = []; | ||
} | ||
} | ||
// (Loop forwards from the beginning of the string.) | ||
while (true) { | ||
chrctr = str[pos]; | ||
if (chrctr === undefined) { // ( End of string reached.) | ||
pushComponent(); | ||
pushComponentArray(); | ||
return listArray; | ||
} else if (inComment) { | ||
if ((chrctr === "*") && (str[pos + 1] === "/")) { // (At end of a comment.) | ||
inComment = false; | ||
pos += 2; | ||
pushComponent(); | ||
continue; | ||
} else { | ||
pos += 1; // (Skip all characters inside comments.) | ||
continue; | ||
} | ||
} else if (isSpace(chrctr)) { | ||
// (If previous character in loop was also a space, or if | ||
// at the beginning of the string, do not add space char to | ||
// component.) | ||
if ((str[pos - 1] && isSpace(str[pos - 1])) || (!component)) { | ||
pos += 1; | ||
continue; | ||
} else if (parenDepth === 0) { | ||
pushComponent(); | ||
pos +=1; | ||
continue; | ||
} else { | ||
// (Replace any space character with a plain space for legibility.) | ||
chrctr = " "; | ||
} | ||
} else if (chrctr === "(") { | ||
parenDepth += 1; | ||
} else if (chrctr === ")") { | ||
parenDepth -= 1; | ||
} else if (chrctr === ",") { | ||
pushComponent() | ||
pushComponentArray(); | ||
pos += 1; | ||
continue; | ||
} else if ((chrctr === "/") && (str[pos + 1] === "*")) { | ||
inComment = true; | ||
pos += 2; | ||
continue; | ||
} | ||
component = component + chrctr; | ||
pos += 1; | ||
} | ||
} | ||
return candidates; | ||
}; | ||
pf.parseDescriptor = function( descriptor, sizesattr ) { | ||
// 11. Descriptor parser: Let candidates be an initially empty source set. The order of entries in the list | ||
// is the order in which entries are added to the list. | ||
var sizes = sizesattr || "100vw", | ||
sizeDescriptor = descriptor && descriptor.replace( /(^\s+|\s+$)/g, "" ), | ||
widthInCssPixels = pf.findWidthFromSourceSize( sizes ), | ||
resCandidate; | ||
function isValidNonNegativeSourceSizeValue(s) { | ||
if (regexCssLengthWithUnits.test(s) && (parseFloat(s) >= 0)) {return true;} | ||
if (regexCssCalc.test(s)) {return true;} | ||
// ( http://www.w3.org/TR/CSS2/syndata.html#numbers says: | ||
// "-0 is equivalent to 0 and is not a negative number." which means that | ||
// unitless zero and unitless negative zero must be accepted as special cases.) | ||
if ((s === "0") || (s === "-0") || (s === "+0")) {return true;} | ||
return false; | ||
} | ||
if ( sizeDescriptor ) { | ||
var splitDescriptor = sizeDescriptor.split(" "); | ||
// When asked to parse a sizes attribute from an element, parse a | ||
// comma-separated list of component values from the value of the element's | ||
// sizes attribute (or the empty string, if the attribute is absent), and let | ||
// unparsed sizes list be the result. | ||
// http://dev.w3.org/csswg/css-syntax/#parse-comma-separated-list-of-component-values | ||
for (var i = splitDescriptor.length - 1; i >= 0; i--) { | ||
var curr = splitDescriptor[ i ], | ||
lastchar = curr && curr.slice( curr.length - 1 ); | ||
unparsedSizesList = parseComponentValues(strValue); | ||
unparsedSizesListLength = unparsedSizesList.length; | ||
if ( ( lastchar === "h" || lastchar === "w" ) && !pf.sizesSupported ) { | ||
resCandidate = parseFloat( ( parseInt( curr, 10 ) / widthInCssPixels ) ); | ||
} else if ( lastchar === "x" ) { | ||
var res = curr && parseFloat( curr, 10 ); | ||
resCandidate = res && !isNaN( res ) ? res : 1; | ||
} | ||
} | ||
// For each unparsed size in unparsed sizes list: | ||
for (i = 0; i < unparsedSizesListLength; i++) { | ||
unparsedSize = unparsedSizesList[i]; | ||
// 1. Remove all consecutive <whitespace-token>s from the end of unparsed size. | ||
// ( parseComponentValues() already omits spaces outside of parens. ) | ||
// If unparsed size is now empty, that is a parse error; continue to the next | ||
// iteration of this algorithm. | ||
// ( parseComponentValues() won't push an empty array. ) | ||
// 2. If the last component value in unparsed size is a valid non-negative | ||
// <source-size-value>, let size be its value and remove the component value | ||
// from unparsed size. Any CSS function other than the calc() function is | ||
// invalid. Otherwise, there is a parse error; continue to the next iteration | ||
// of this algorithm. | ||
// http://dev.w3.org/csswg/css-syntax/#parse-component-value | ||
lastComponentValue = unparsedSize[unparsedSize.length - 1]; | ||
if (isValidNonNegativeSourceSizeValue(lastComponentValue)) { | ||
size = lastComponentValue; | ||
unparsedSize.pop(); | ||
} else { | ||
continue; | ||
} | ||
return resCandidate || 1; | ||
}; | ||
// 3. Remove all consecutive <whitespace-token>s from the end of unparsed | ||
// size. If unparsed size is now empty, return size and exit this algorithm. | ||
// If this was not the last item in unparsed sizes list, that is a parse error. | ||
if (unparsedSize.length === 0) { | ||
return size; | ||
} | ||
// 4. Parse the remaining component values in unparsed size as a | ||
// <media-condition>. If it does not parse correctly, or it does parse | ||
// correctly but the <media-condition> evaluates to false, continue to the | ||
// next iteration of this algorithm. | ||
// (Parsing all possible compound media conditions in JS is heavy, complicated, | ||
// and the payoff is unclear. Is there ever an situation where the | ||
// media condition parses incorrectly but still somehow evaluates to true? | ||
// Can we just rely on the browser/polyfill to do it?) | ||
unparsedSize = unparsedSize.join(" "); | ||
if (!(pf.matchesMedia( unparsedSize ) ) ) { | ||
continue; | ||
} | ||
// 5. Return size and exit this algorithm. | ||
return size; | ||
} | ||
// If the above algorithm exhausts unparsed sizes list without returning a | ||
// size value, return 100vw. | ||
return "100vw"; | ||
} | ||
// jscs: enable | ||
/* jshint ignore:end */ | ||
// namespace | ||
pf.ns = ("pf" + new Date().getTime()).substr(0, 9); | ||
// srcset support test | ||
pf.supSrcset = "srcset" in image; | ||
pf.supSizes = "sizes" in image; | ||
// using pf.qsa instead of dom traversing does scale much better, | ||
// especially on sites mixing responsive and non-responsive images | ||
pf.selShort = "picture>img,img[srcset]"; | ||
pf.sel = pf.selShort; | ||
pf.cfg = cfg; | ||
if ( pf.supSrcset ) { | ||
pf.sel += ",img[" + srcsetAttr + "]"; | ||
} | ||
/** | ||
* 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 | ||
* Shortcut property for `devicePixelRatio` ( for easy overriding in tests ) | ||
*/ | ||
pf.getCandidatesFromSourceSet = function( srcset, sizes ) { | ||
var candidates = pf.parseSrcset( srcset ), | ||
formattedCandidates = []; | ||
pf.DPR = (DPR || 1 ); | ||
pf.u = units; | ||
for ( var i = 0, len = candidates.length; i < len; i++ ) { | ||
var candidate = candidates[ i ]; | ||
// container of supported mime types that one might need to qualify before using | ||
pf.types = types; | ||
formattedCandidates.push({ | ||
url: candidate.url, | ||
resolution: pf.parseDescriptor( candidate.descriptor, sizes ) | ||
}); | ||
} | ||
return formattedCandidates; | ||
alwaysCheckWDescriptor = pf.supSrcset && !pf.supSizes; | ||
pf.setSize = noop; | ||
/** | ||
* Gets a string and returns the absolute URL | ||
* @param src | ||
* @returns {String} absolute URL | ||
*/ | ||
pf.makeUrl = memoize(function(src) { | ||
anchor.href = src; | ||
return anchor.href; | ||
}); | ||
/** | ||
* Gets a DOM element or document and a selctor and returns the found matches | ||
* Can be extended with jQuery/Sizzle for IE7 support | ||
* @param context | ||
* @param sel | ||
* @returns {NodeList} | ||
*/ | ||
pf.qsa = function(context, sel) { | ||
return context.querySelectorAll(sel); | ||
}; | ||
/** | ||
* 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 | ||
* Shortcut method for matchMedia ( for easy overriding in tests ) | ||
* wether native or pf.mMQ is used will be decided lazy on first call | ||
* @returns {boolean} | ||
*/ | ||
pf.dodgeSrcset = function( img ) { | ||
if ( img.srcset ) { | ||
img[ pf.ns ].srcset = img.srcset; | ||
img.srcset = ""; | ||
img.setAttribute( "data-pfsrcset", img[ pf.ns ].srcset ); | ||
pf.matchesMedia = function() { | ||
if ( window.matchMedia && (matchMedia( "(min-width: 0.1em)" ) || {}).matches ) { | ||
pf.matchesMedia = function( media ) { | ||
return !media || ( matchMedia( media ).matches ); | ||
}; | ||
} else { | ||
pf.matchesMedia = pf.mMQ; | ||
} | ||
return pf.matchesMedia.apply( this, arguments ); | ||
}; | ||
// 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 = []; | ||
/** | ||
* A simplified matchMedia implementation for IE8 and IE9 | ||
* handles only min-width/max-width with px or em values | ||
* @param media | ||
* @returns {boolean} | ||
*/ | ||
pf.mMQ = function( media ) { | ||
return media ? evalCSS(media) : true; | ||
}; | ||
// 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; | ||
/** | ||
* Returns the calculated length in css pixel from the given sourceSizeValue | ||
* http://dev.w3.org/csswg/css-values-3/#length-value | ||
* intended Spec mismatches: | ||
* * Does not check for invalid use of CSS functions | ||
* * Does handle a computed length of 0 the same as a negative and therefore invalid value | ||
* @param sourceSizeValue | ||
* @returns {Number} | ||
*/ | ||
pf.calcLength = function( sourceSizeValue ) { | ||
var value = evalCSS(sourceSizeValue, true) || false; | ||
if (value < 0) { | ||
value = false; | ||
} | ||
if ( srcset ) { | ||
candidates = pf.getCandidatesFromSourceSet( srcset, sizes ); | ||
return value; | ||
}; | ||
/** | ||
* Takes a type string and checks if its supported | ||
*/ | ||
pf.supportsType = function( type ) { | ||
return ( type ) ? types[ type ] : true; | ||
}; | ||
/** | ||
* Parses a sourceSize into mediaCondition (media) and sourceSizeValue (length) | ||
* @param sourceSizeStr | ||
* @returns {*} | ||
*/ | ||
pf.parseSize = memoize(function( sourceSizeStr ) { | ||
var match = ( sourceSizeStr || "" ).match(regSize); | ||
return { | ||
media: match && match[1], | ||
length: match && match[2] | ||
}; | ||
}); | ||
pf.parseSet = function( set ) { | ||
if ( !set.cands ) { | ||
set.cands = parseSrcset(set.srcset, set); | ||
} | ||
return candidates; | ||
return set.cands; | ||
}; | ||
pf.backfaceVisibilityFix = function( picImg ) { | ||
// See: https://github.com/scottjehl/picturefill/issues/332 | ||
var style = picImg.style || {}, | ||
WebkitBackfaceVisibility = "webkitBackfaceVisibility" in style, | ||
currentZoom = style.zoom; | ||
/** | ||
* returns 1em in css px for html/body default size | ||
* function taken from respondjs | ||
* @returns {*|number} | ||
*/ | ||
pf.getEmValue = function() { | ||
var body; | ||
if ( !eminpx && (body = document.body) ) { | ||
var div = document.createElement( "div" ), | ||
originalHTMLCSS = docElem.style.cssText, | ||
originalBodyCSS = body.style.cssText; | ||
if (WebkitBackfaceVisibility) { | ||
style.zoom = ".999"; | ||
div.style.cssText = baseStyle; | ||
WebkitBackfaceVisibility = picImg.offsetWidth; | ||
// 1em in a media query is the value of the default font size of the browser | ||
// reset docElem and body to ensure the correct value is returned | ||
docElem.style.cssText = fsCss; | ||
body.style.cssText = fsCss; | ||
style.zoom = currentZoom; | ||
body.appendChild( div ); | ||
eminpx = div.offsetWidth; | ||
body.removeChild( div ); | ||
//also update eminpx before returning | ||
eminpx = parseFloat( eminpx, 10 ); | ||
// restore the original values | ||
docElem.style.cssText = originalHTMLCSS; | ||
body.style.cssText = originalBodyCSS; | ||
} | ||
return eminpx || 16; | ||
}; | ||
pf.setIntrinsicSize = (function() { | ||
var urlCache = {}; | ||
var setSize = function( picImg, width, res ) { | ||
if ( width ) { | ||
picImg.setAttribute( "width", parseInt(width / res, 10) ); | ||
} | ||
}; | ||
return function( picImg, bestCandidate ) { | ||
var img; | ||
if ( !picImg[ pf.ns ] || w.pfStopIntrinsicSize ) { | ||
return; | ||
} | ||
if ( picImg[ pf.ns ].dims === undefined ) { | ||
picImg[ pf.ns].dims = picImg.getAttribute("width") || picImg.getAttribute("height"); | ||
} | ||
if ( picImg[ pf.ns].dims ) { return; } | ||
/** | ||
* Takes a string of sizes and returns the width in pixels as a number | ||
*/ | ||
pf.calcListLength = function( sourceSizeListStr ) { | ||
// Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33% | ||
// | ||
// or (min-width:30em) calc(30% - 15px) | ||
if ( !(sourceSizeListStr in sizeLengthCache) || cfg.uT ) { | ||
var winningLength = pf.calcLength( parseSizes( sourceSizeListStr ) ); | ||
if ( bestCandidate.url in urlCache ) { | ||
setSize( picImg, urlCache[bestCandidate.url], bestCandidate.resolution ); | ||
} else { | ||
img = doc.createElement( "img" ); | ||
img.onload = function() { | ||
urlCache[bestCandidate.url] = img.width; | ||
sizeLengthCache[ sourceSizeListStr ] = !winningLength ? units.width : winningLength; | ||
} | ||
//IE 10/11 don't calculate width for svg outside document | ||
if ( !urlCache[bestCandidate.url] ) { | ||
try { | ||
doc.body.appendChild( img ); | ||
urlCache[bestCandidate.url] = img.width || img.offsetWidth; | ||
doc.body.removeChild( img ); | ||
} catch(e){} | ||
} | ||
return sizeLengthCache[ sourceSizeListStr ]; | ||
}; | ||
if ( picImg.src === bestCandidate.url ) { | ||
setSize( picImg, urlCache[bestCandidate.url], bestCandidate.resolution ); | ||
} | ||
picImg = null; | ||
img.onload = null; | ||
img = null; | ||
}; | ||
img.src = bestCandidate.url; | ||
/** | ||
* Takes a candidate object with a srcset property 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, res is calculated | ||
*/ | ||
pf.setRes = function( set ) { | ||
var candidates; | ||
if ( set ) { | ||
candidates = pf.parseSet( set ); | ||
for ( var i = 0, len = candidates.length; i < len; i++ ) { | ||
setResolution( candidates[ i ], set.sizes ); | ||
} | ||
}; | ||
})(); | ||
} | ||
return candidates; | ||
}; | ||
pf.applyBestCandidate = function( candidates, picImg ) { | ||
pf.setRes.res = setResolution; | ||
pf.applySetCandidate = function( candidates, img ) { | ||
if ( !candidates.length ) {return;} | ||
var candidate, | ||
i, | ||
j, | ||
length, | ||
bestCandidate; | ||
bestCandidate, | ||
curSrc, | ||
curCan, | ||
isSameSet, | ||
candidateSrc, | ||
abortCurSrc; | ||
candidates.sort( pf.ascendingSort ); | ||
var imageData = img[ pf.ns ]; | ||
var dpr = pf.DPR; | ||
length = candidates.length; | ||
bestCandidate = candidates[ length - 1 ]; | ||
curSrc = imageData.curSrc || img[curSrcProp]; | ||
for ( var i = 0; i < length; i++ ) { | ||
candidate = candidates[ i ]; | ||
if ( candidate.resolution >= pf.getDpr() ) { | ||
bestCandidate = candidate; | ||
break; | ||
curCan = imageData.curCan || setSrcToCur(img, curSrc, candidates[0].set); | ||
// if we have a current source, we might either become lazy or give this source some advantage | ||
if ( curCan && curCan.set === candidates[ 0 ].set ) { | ||
// if browser can abort image request and the image has a higher pixel density than needed | ||
// and this image isn't downloaded yet, we skip next part and try to save bandwidth | ||
abortCurSrc = (supportAbort && !img.complete && curCan.res - 0.1 > dpr); | ||
if ( !abortCurSrc ) { | ||
curCan.cached = true; | ||
// if current candidate is "best", "better" or "okay", | ||
// set it to bestCandidate | ||
if ( curCan && isSameSet && curCan.res >= dpr ) { | ||
bestCandidate = curCan; | ||
} | ||
} | ||
} | ||
if ( bestCandidate ) { | ||
if ( !bestCandidate ) { | ||
bestCandidate.url = pf.makeUrl( bestCandidate.url ); | ||
candidates.sort( ascendingSort ); | ||
if ( picImg.src !== bestCandidate.url ) { | ||
if ( pf.restrictsMixedContent() && bestCandidate.url.substr(0, "http:".length).toLowerCase() === "http:" ) { | ||
if ( window.console !== undefined ) { | ||
console.warn( "Blocked mixed content image " + bestCandidate.url ); | ||
length = candidates.length; | ||
bestCandidate = candidates[ length - 1 ]; | ||
for ( i = 0; i < length; i++ ) { | ||
candidate = candidates[ i ]; | ||
if ( candidate.res >= dpr ) { | ||
j = i - 1; | ||
// we have found the perfect candidate, | ||
// but let's improve this a little bit with some assumptions ;-) | ||
if (candidates[ j ] && | ||
(abortCurSrc || curSrc !== pf.makeUrl( candidate.url )) && | ||
chooseLowRes(candidates[ j ].res, candidate.res, dpr, candidates[ j ].cached)) { | ||
bestCandidate = candidates[ j ]; | ||
} else { | ||
bestCandidate = candidate; | ||
} | ||
} else { | ||
picImg.src = bestCandidate.url; | ||
// currentSrc attribute and property to match | ||
// http://picture.responsiveimages.org/#the-img-element | ||
if ( !pf.curSrcSupported ) { | ||
picImg.currentSrc = picImg.src; | ||
} | ||
pf.backfaceVisibilityFix( picImg ); | ||
break; | ||
} | ||
} | ||
pf.setIntrinsicSize(picImg, bestCandidate); | ||
} | ||
}; | ||
pf.ascendingSort = function( a, b ) { | ||
return a.resolution - b.resolution; | ||
}; | ||
if ( bestCandidate ) { | ||
/** | ||
* 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 ); | ||
candidateSrc = pf.makeUrl( bestCandidate.url ); | ||
imageData.curSrc = candidateSrc; | ||
imageData.curCan = bestCandidate; | ||
if ( candidateSrc !== curSrc ) { | ||
pf.setSrc( img, bestCandidate ); | ||
} | ||
// Remove the video element once we're finished removing its children | ||
video.parentNode.removeChild( video ); | ||
pf.setSize( img ); | ||
} | ||
}; | ||
/** | ||
* Find all `img` elements, and add them to the candidate list if they have | ||
* a `picture` parent, a `sizes` attribute in basic `srcset` supporting browsers, | ||
* a `srcset` attribute at all, and they haven’t been evaluated already. | ||
*/ | ||
pf.getAllElements = function() { | ||
var elems = [], | ||
imgs = doc.getElementsByTagName( "img" ); | ||
pf.setSrc = function( img, bestCandidate ) { | ||
var origWidth; | ||
img.src = bestCandidate.url; | ||
for ( var h = 0, len = imgs.length; h < len; h++ ) { | ||
var currImg = imgs[ h ]; | ||
// although this is a specific Safari issue, we don't want to take too much different code paths | ||
if ( bestCandidate.set.type === "image/svg+xml" ) { | ||
origWidth = img.style.width; | ||
img.style.width = (img.offsetWidth + 1) + "px"; | ||
if ( currImg.parentNode.nodeName.toUpperCase() === "PICTURE" || | ||
( currImg.getAttribute( "srcset" ) !== null ) || currImg[ pf.ns ] && currImg[ pf.ns ].srcset !== null ) { | ||
elems.push( currImg ); | ||
// next line only should trigger a repaint | ||
// if... is only done to trick dead code removal | ||
if ( img.offsetWidth + 1 ) { | ||
img.style.width = origWidth; | ||
} | ||
} | ||
return elems; | ||
}; | ||
pf.getMatch = function( img, picture ) { | ||
var sources = picture.childNodes, | ||
match; | ||
pf.getSet = function( img ) { | ||
var i, set, supportsType; | ||
var match = false; | ||
var sets = img [ pf.ns ].sets; | ||
// 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 ]; | ||
for ( i = 0; i < sets.length && !match; i++ ) { | ||
set = sets[i]; | ||
// ignore non-element nodes | ||
if ( source.nodeType !== 1 ) { | ||
if ( !set.srcset || !pf.matchesMedia( set.media ) || !(supportsType = pf.supportsType( set.type )) ) { | ||
continue; | ||
} | ||
// Hitting the `img` element that started everything stops the search for `sources`. | ||
// If no previous `source` matches, the `img` itself is evaluated later. | ||
if ( source === img ) { | ||
return match; | ||
if ( supportsType === "pending" ) { | ||
set = supportsType; | ||
} | ||
// ignore non-`source` nodes | ||
if ( source.nodeName.toUpperCase() !== "SOURCE" ) { | ||
continue; | ||
} | ||
// if it's a source element that has the `src` property set, throw a warning in the console | ||
if ( source.getAttribute( "src" ) !== null && typeof console !== undefined ) { | ||
console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`."); | ||
} | ||
match = set; | ||
break; | ||
} | ||
var media = source.getAttribute( "media" ); | ||
return match; | ||
}; | ||
// if source does not have a srcset attribute, skip | ||
if ( !source.getAttribute( "srcset" ) ) { | ||
continue; | ||
} | ||
pf.parseSets = function( element, parent, options ) { | ||
var srcsetAttribute, imageSet, isWDescripor, srcsetParsed; | ||
// if there's no media specified, OR w.matchMedia is supported | ||
if ( ( !media || pf.matchesMedia( media ) ) ) { | ||
var typeSupported = pf.verifyTypeSupport( source ); | ||
var hasPicture = parent && parent.nodeName.toUpperCase() === "PICTURE"; | ||
var imageData = element[ pf.ns ]; | ||
if ( typeSupported === true ) { | ||
match = source; | ||
break; | ||
} else if ( typeSupported === "pending" ) { | ||
return false; | ||
} | ||
if ( imageData.src === undefined || options.src ) { | ||
imageData.src = getImgAttr.call( element, "src" ); | ||
if ( imageData.src ) { | ||
setImgAttr.call( element, srcAttr, imageData.src ); | ||
} else { | ||
removeImgAttr.call( element, srcAttr ); | ||
} | ||
} | ||
return match; | ||
}; | ||
if ( imageData.srcset === undefined || options.srcset || !pf.supSrcset || element.srcset ) { | ||
srcsetAttribute = getImgAttr.call( element, "srcset" ); | ||
imageData.srcset = srcsetAttribute; | ||
srcsetParsed = true; | ||
} | ||
function picturefill( opt ) { | ||
var elements, | ||
element, | ||
parent, | ||
firstMatch, | ||
candidates, | ||
options = opt || {}; | ||
imageData.sets = []; | ||
elements = options.elements || pf.getAllElements(); | ||
if ( hasPicture ) { | ||
imageData.pic = true; | ||
getAllSourceElements( parent, imageData.sets ); | ||
} | ||
// Loop through all elements | ||
for ( var i = 0, plen = elements.length; i < plen; i++ ) { | ||
element = elements[ i ]; | ||
parent = element.parentNode; | ||
firstMatch = undefined; | ||
candidates = undefined; | ||
if ( imageData.srcset ) { | ||
imageSet = { | ||
srcset: imageData.srcset, | ||
sizes: getImgAttr.call( element, "sizes" ) | ||
}; | ||
// immediately skip non-`img` nodes | ||
if ( element.nodeName.toUpperCase() !== "IMG" ) { | ||
continue; | ||
} | ||
imageData.sets.push( imageSet ); | ||
// expando for caching data on the img | ||
if ( !element[ pf.ns ] ) { | ||
element[ pf.ns ] = {}; | ||
} | ||
isWDescripor = (alwaysCheckWDescriptor || imageData.src) && regWDesc.test(imageData.srcset || ""); | ||
// if the element has already been evaluated, skip it unless | ||
// `options.reevaluate` is set to true ( this, for example, | ||
// is set to true when running `picturefill` on `resize` ). | ||
if ( !options.reevaluate && element[ pf.ns ].evaluated ) { | ||
continue; | ||
// add normal src as candidate, if source has no w descriptor | ||
if ( !isWDescripor && imageData.src && !getCandidateForSrc(imageData.src, imageSet) && !imageSet.has1x ) { | ||
imageSet.srcset += ", " + imageData.src; | ||
imageSet.cands.push({ | ||
url: imageData.src, | ||
d: 1, | ||
set: imageSet | ||
}); | ||
} | ||
// if `img` is in a `picture` element | ||
if ( parent && parent.nodeName.toUpperCase() === "PICTURE" ) { | ||
} else if ( imageData.src ) { | ||
imageData.sets.push( { | ||
srcset: imageData.src, | ||
sizes: null | ||
} ); | ||
} | ||
// IE9 video workaround | ||
pf.removeVideoShim( parent ); | ||
imageData.curCan = null; | ||
imageData.curSrc = undefined; | ||
// 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, parent ); | ||
// if img has picture or the srcset was removed or has a srcset and does not support srcset at all | ||
// or has a w descriptor (and does not support sizes) set support to false to evaluate | ||
imageData.supported = !( hasPicture || ( imageSet && !pf.supSrcset ) || isWDescripor ); | ||
// 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; | ||
} | ||
if ( srcsetParsed && pf.supSrcset && !imageData.supported ) { | ||
if ( srcsetAttribute ) { | ||
setImgAttr.call( element, srcsetAttr, srcsetAttribute ); | ||
element.srcset = ""; | ||
} else { | ||
firstMatch = undefined; | ||
removeImgAttr.call( element, srcsetAttr ); | ||
} | ||
} | ||
// Cache and remove `srcset` if present and we’re going to be doing `picture`/`srcset`/`sizes` polyfilling to it. | ||
if ( ( parent && parent.nodeName.toUpperCase() === "PICTURE" ) || | ||
( !pf.sizesSupported && ( element.srcset && regWDesc.test( element.srcset ) ) ) ) { | ||
pf.dodgeSrcset( element ); | ||
if (imageData.supported && !imageData.srcset && ((!imageData.src && element.src) || element.src !== pf.makeUrl(imageData.src))) { | ||
if (imageData.src === null) { | ||
element.removeAttribute("src"); | ||
} else { | ||
element.src = imageData.src; | ||
} | ||
} | ||
if ( firstMatch ) { | ||
candidates = pf.processSourceSet( firstMatch ); | ||
pf.applyBestCandidate( candidates, element ); | ||
} else { | ||
// No sources matched, so we’re down to processing the inner `img` as a source. | ||
candidates = pf.processSourceSet( element ); | ||
imageData.parsed = true; | ||
}; | ||
if ( element.srcset === undefined || element[ pf.ns ].srcset ) { | ||
// Either `srcset` is completely unsupported, or we need to polyfill `sizes` functionality. | ||
pf.applyBestCandidate( candidates, element ); | ||
} // Else, resolution-only `srcset` is supported natively. | ||
} | ||
pf.fillImg = function(element, options) { | ||
var imageData; | ||
var extreme = options.reselect || options.reevaluate; | ||
// set evaluated to true to avoid unnecessary reparsing | ||
element[ pf.ns ].evaluated = true; | ||
// expando for caching data on the img | ||
if ( !element[ pf.ns ] ) { | ||
element[ pf.ns ] = {}; | ||
} | ||
} | ||
/** | ||
* 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() { | ||
pf.initTypeDetects(); | ||
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 | ||
picturefill(); | ||
imageData = element[ pf.ns ]; | ||
if ( /^loaded|^i|^c/.test( doc.readyState ) ) { | ||
clearInterval( intervalId ); | ||
return; | ||
} | ||
}, 250 ); | ||
// if the element has already been evaluated, skip it | ||
// unless `options.reevaluate` is set to true ( this, for example, | ||
// is set to true when running `picturefill` on `resize` ). | ||
if ( !extreme && imageData.evaled === evalId ) { | ||
return; | ||
} | ||
var resizeTimer; | ||
var handleResize = function() { | ||
picturefill({ reevaluate: true }); | ||
}; | ||
function checkResize() { | ||
clearTimeout(resizeTimer); | ||
resizeTimer = setTimeout( handleResize, 60 ); | ||
if ( !imageData.parsed || options.reevaluate ) { | ||
pf.parseSets( element, element.parentNode, options ); | ||
} | ||
if ( w.addEventListener ) { | ||
w.addEventListener( "resize", checkResize, false ); | ||
} else if ( w.attachEvent ) { | ||
w.attachEvent( "onresize", checkResize ); | ||
if ( !imageData.supported ) { | ||
applyBestCandidate( element ); | ||
} else { | ||
imageData.evaled = evalId; | ||
} | ||
}; | ||
pf.setupRun = function() { | ||
if ( !alreadyRun || isVwDirty || (DPR !== window.devicePixelRatio) ) { | ||
updateMetrics(); | ||
} | ||
}; | ||
// If picture is supported, well, that's awesome. | ||
if ( window.HTMLPictureElement ) { | ||
picturefill = noop; | ||
pf.fillImg = noop; | ||
} else { | ||
// Set up picture polyfill by polling the document | ||
(function() { | ||
var isDomReady; | ||
var regReady = window.attachEvent ? /d$|^c/ : /d$|^c|^i/; | ||
var run = function() { | ||
var readyState = document.readyState || ""; | ||
timerId = setTimeout(run, readyState === "loading" ? 200 : 999); | ||
if ( document.body ) { | ||
pf.fillImgs(); | ||
isDomReady = isDomReady || regReady.test(readyState); | ||
if ( isDomReady ) { | ||
clearTimeout( timerId ); | ||
} | ||
} | ||
}; | ||
var timerId = setTimeout(run, document.body ? 9 : 99); | ||
// Also attach picturefill on resize and readystatechange | ||
// http://modernjavascript.blogspot.com/2013/08/building-better-debounce.html | ||
var debounce = function(func, wait) { | ||
var timeout, timestamp; | ||
var later = function() { | ||
var last = (new Date()) - timestamp; | ||
if (last < wait) { | ||
timeout = setTimeout(later, wait - last); | ||
} else { | ||
timeout = null; | ||
func(); | ||
} | ||
}; | ||
return function() { | ||
timestamp = new Date(); | ||
if (!timeout) { | ||
timeout = setTimeout(later, wait); | ||
} | ||
}; | ||
}; | ||
var onResize = function() { | ||
isVwDirty = true; | ||
pf.fillImgs(); | ||
}; | ||
on( window, "resize", debounce(onResize, 99 ) ); | ||
on( document, "readystatechange", run ); | ||
types[ "image/webp" ] = detectTypeSupport("image/webp", "data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==" ); | ||
})(); | ||
} | ||
runPicturefill(); | ||
pf.picturefill = picturefill; | ||
//use this internally for easy monkey patching/performance testing | ||
pf.fillImgs = picturefill; | ||
pf.teardownRun = noop; | ||
@@ -734,4 +1464,33 @@ /* expose methods for testing */ | ||
expose( picturefill ); | ||
window.picturefillCFG = { | ||
pf: pf, | ||
push: function(args) { | ||
var name = args.shift(); | ||
if (typeof pf[name] === "function") { | ||
pf[name].apply(pf, args); | ||
} else { | ||
cfg[name] = args[0]; | ||
if (alreadyRun) { | ||
pf.fillImgs( { reselect: true } ); | ||
} | ||
} | ||
} | ||
}; | ||
} )( window, window.document, new window.Image() ); | ||
while (setOptions && setOptions.length) { | ||
window.picturefillCFG.push(setOptions.shift()); | ||
} | ||
/* expose picturefill */ | ||
window.picturefill = picturefill; | ||
/* expose picturefill */ | ||
if ( typeof module === "object" && typeof module.exports === "object" ) { | ||
// CommonJS, just export | ||
module.exports = picturefill; | ||
} else if ( typeof define === "function" && define.amd ) { | ||
// AMD support | ||
define( "picturefill", function() { return picturefill; } ); | ||
} | ||
} )( window, document ); |
@@ -1,4 +0,4 @@ | ||
/*! Picturefill - v2.3.1 - 2015-04-09 | ||
/*! Picturefill - v3.0.0-alpha1 - 2015-06-24 | ||
* http://scottjehl.github.io/picturefill | ||
* Copyright (c) 2015 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT */ | ||
window.matchMedia||(window.matchMedia=function(){"use strict";var a=window.styleMedia||window.media;if(!a){var b=document.createElement("style"),c=document.getElementsByTagName("script")[0],d=null;b.type="text/css",b.id="matchmediajs-test",c.parentNode.insertBefore(b,c),d="getComputedStyle"in window&&window.getComputedStyle(b,null)||b.currentStyle,a={matchMedium:function(a){var c="@media "+a+"{ #matchmediajs-test { width: 1px; } }";return b.styleSheet?b.styleSheet.cssText=c:b.textContent=c,"1px"===d.width}}}return function(b){return{matches:a.matchMedium(b||"all"),media:b||"all"}}}()),function(a,b,c){"use strict";function d(b){"object"==typeof module&&"object"==typeof module.exports?module.exports=b:"function"==typeof define&&define.amd&&define("picturefill",function(){return b}),"object"==typeof a&&(a.picturefill=b)}function e(a){var b,c,d,e,f,i=a||{};b=i.elements||g.getAllElements();for(var j=0,k=b.length;k>j;j++)if(c=b[j],d=c.parentNode,e=void 0,f=void 0,"IMG"===c.nodeName.toUpperCase()&&(c[g.ns]||(c[g.ns]={}),i.reevaluate||!c[g.ns].evaluated)){if(d&&"PICTURE"===d.nodeName.toUpperCase()){if(g.removeVideoShim(d),e=g.getMatch(c,d),e===!1)continue}else e=void 0;(d&&"PICTURE"===d.nodeName.toUpperCase()||!g.sizesSupported&&c.srcset&&h.test(c.srcset))&&g.dodgeSrcset(c),e?(f=g.processSourceSet(e),g.applyBestCandidate(f,c)):(f=g.processSourceSet(c),(void 0===c.srcset||c[g.ns].srcset)&&g.applyBestCandidate(f,c)),c[g.ns].evaluated=!0}}function f(){function c(){clearTimeout(d),d=setTimeout(h,60)}g.initTypeDetects(),e();var d,f=setInterval(function(){return e(),/^loaded|^i|^c/.test(b.readyState)?void clearInterval(f):void 0},250),h=function(){e({reevaluate:!0})};a.addEventListener?a.addEventListener("resize",c,!1):a.attachEvent&&a.attachEvent("onresize",c)}if(a.HTMLPictureElement)return void d(function(){});b.createElement("picture");var g=a.picturefill||{},h=/\s+\+?\d+(e\d+)?w/;g.ns="picturefill",function(){g.srcsetSupported="srcset"in c,g.sizesSupported="sizes"in c,g.curSrcSupported="currentSrc"in c}(),g.trim=function(a){return a.trim?a.trim():a.replace(/^\s+|\s+$/g,"")},g.makeUrl=function(){var a=b.createElement("a");return function(b){return a.href=b,a.href}}(),g.restrictsMixedContent=function(){return"https:"===a.location.protocol},g.matchesMedia=function(b){return a.matchMedia&&a.matchMedia(b).matches},g.getDpr=function(){return a.devicePixelRatio||1},g.getWidthFromLength=function(a){var c;if(!a||a.indexOf("%")>-1!=!1||!(parseFloat(a)>0||a.indexOf("calc(")>-1))return!1;a=a.replace("vw","%"),g.lengthEl||(g.lengthEl=b.createElement("div"),g.lengthEl.style.cssText="border:0;display:block;font-size:1em;left:0;margin:0;padding:0;position:absolute;visibility:hidden",g.lengthEl.className="helper-from-picturefill-js"),g.lengthEl.style.width="0px";try{g.lengthEl.style.width=a}catch(d){}return b.body.appendChild(g.lengthEl),c=g.lengthEl.offsetWidth,0>=c&&(c=!1),b.body.removeChild(g.lengthEl),c},g.detectTypeSupport=function(b,c){var d=new a.Image;return d.onerror=function(){g.types[b]=!1,e()},d.onload=function(){g.types[b]=1===d.width,e()},d.src=c,"pending"},g.types=g.types||{},g.initTypeDetects=function(){g.types["image/jpeg"]=!0,g.types["image/gif"]=!0,g.types["image/png"]=!0,g.types["image/svg+xml"]=b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1"),g.types["image/webp"]=g.detectTypeSupport("image/webp","data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=")},g.verifyTypeSupport=function(a){var b=a.getAttribute("type");if(null===b||""===b)return!0;var c=g.types[b];return"string"==typeof c&&"pending"!==c?(g.types[b]=g.detectTypeSupport(b,c),"pending"):"function"==typeof c?(c(),"pending"):c},g.parseSize=function(a){var b=/(\([^)]+\))?\s*(.+)/g.exec(a);return{media:b&&b[1],length:b&&b[2]}},g.findWidthFromSourceSize=function(c){for(var d,e=g.trim(c).split(/\s*,\s*/),f=0,h=e.length;h>f;f++){var i=e[f],j=g.parseSize(i),k=j.length,l=j.media;if(k&&(!l||g.matchesMedia(l))&&(d=g.getWidthFromLength(k)))break}return d||Math.max(a.innerWidth||0,b.documentElement.clientWidth)},g.parseSrcset=function(a){for(var b=[];""!==a;){a=a.replace(/^\s+/g,"");var c,d=a.search(/\s/g),e=null;if(-1!==d){c=a.slice(0,d);var f=c.slice(-1);if((","===f||""===c)&&(c=c.replace(/,+$/,""),e=""),a=a.slice(d+1),null===e){var g=a.indexOf(",");-1!==g?(e=a.slice(0,g),a=a.slice(g+1)):(e=a,a="")}}else c=a,a="";(c||e)&&b.push({url:c,descriptor:e})}return b},g.parseDescriptor=function(a,b){var c,d=b||"100vw",e=a&&a.replace(/(^\s+|\s+$)/g,""),f=g.findWidthFromSourceSize(d);if(e)for(var h=e.split(" "),i=h.length-1;i>=0;i--){var j=h[i],k=j&&j.slice(j.length-1);if("h"!==k&&"w"!==k||g.sizesSupported){if("x"===k){var l=j&&parseFloat(j,10);c=l&&!isNaN(l)?l:1}}else c=parseFloat(parseInt(j,10)/f)}return c||1},g.getCandidatesFromSourceSet=function(a,b){for(var c=g.parseSrcset(a),d=[],e=0,f=c.length;f>e;e++){var h=c[e];d.push({url:h.url,resolution:g.parseDescriptor(h.descriptor,b)})}return d},g.dodgeSrcset=function(a){a.srcset&&(a[g.ns].srcset=a.srcset,a.srcset="",a.setAttribute("data-pfsrcset",a[g.ns].srcset))},g.processSourceSet=function(a){var b=a.getAttribute("srcset"),c=a.getAttribute("sizes"),d=[];return"IMG"===a.nodeName.toUpperCase()&&a[g.ns]&&a[g.ns].srcset&&(b=a[g.ns].srcset),b&&(d=g.getCandidatesFromSourceSet(b,c)),d},g.backfaceVisibilityFix=function(a){var b=a.style||{},c="webkitBackfaceVisibility"in b,d=b.zoom;c&&(b.zoom=".999",c=a.offsetWidth,b.zoom=d)},g.setIntrinsicSize=function(){var c={},d=function(a,b,c){b&&a.setAttribute("width",parseInt(b/c,10))};return function(e,f){var h;e[g.ns]&&!a.pfStopIntrinsicSize&&(void 0===e[g.ns].dims&&(e[g.ns].dims=e.getAttribute("width")||e.getAttribute("height")),e[g.ns].dims||(f.url in c?d(e,c[f.url],f.resolution):(h=b.createElement("img"),h.onload=function(){if(c[f.url]=h.width,!c[f.url])try{b.body.appendChild(h),c[f.url]=h.width||h.offsetWidth,b.body.removeChild(h)}catch(a){}e.src===f.url&&d(e,c[f.url],f.resolution),e=null,h.onload=null,h=null},h.src=f.url)))}}(),g.applyBestCandidate=function(a,b){var c,d,e;a.sort(g.ascendingSort),d=a.length,e=a[d-1];for(var f=0;d>f;f++)if(c=a[f],c.resolution>=g.getDpr()){e=c;break}e&&(e.url=g.makeUrl(e.url),b.src!==e.url&&(g.restrictsMixedContent()&&"http:"===e.url.substr(0,"http:".length).toLowerCase()?void 0!==window.console&&console.warn("Blocked mixed content image "+e.url):(b.src=e.url,g.curSrcSupported||(b.currentSrc=b.src),g.backfaceVisibilityFix(b))),g.setIntrinsicSize(b,e))},g.ascendingSort=function(a,b){return a.resolution-b.resolution},g.removeVideoShim=function(a){var b=a.getElementsByTagName("video");if(b.length){for(var c=b[0],d=c.getElementsByTagName("source");d.length;)a.insertBefore(d[0],c);c.parentNode.removeChild(c)}},g.getAllElements=function(){for(var a=[],c=b.getElementsByTagName("img"),d=0,e=c.length;e>d;d++){var f=c[d];("PICTURE"===f.parentNode.nodeName.toUpperCase()||null!==f.getAttribute("srcset")||f[g.ns]&&null!==f[g.ns].srcset)&&a.push(f)}return a},g.getMatch=function(a,b){for(var c,d=b.childNodes,e=0,f=d.length;f>e;e++){var h=d[e];if(1===h.nodeType){if(h===a)return c;if("SOURCE"===h.nodeName.toUpperCase()){null!==h.getAttribute("src")&&void 0!==typeof console&&console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`.");var i=h.getAttribute("media");if(h.getAttribute("srcset")&&(!i||g.matchesMedia(i))){var j=g.verifyTypeSupport(h);if(j===!0){c=h;break}if("pending"===j)return!1}}}}return c},f(),e._=g,d(e)}(window,window.document,new window.Image); | ||
!function(a){var b=navigator.userAgent;a.HTMLPictureElement&&/ecko/.test(b)&&b.match(/rv\:(\d+)/)&&RegExp.$1<41&&addEventListener("resize",function(){var b,c=document.createElement("source"),d=function(a){var b,d,e=a.parentNode;"PICTURE"===e.nodeName.toUpperCase()?(b=c.cloneNode(),e.insertBefore(b,e.firstElementChild),setTimeout(function(){e.removeChild(b)})):(!a._pfLastSize||a.offsetWidth>a._pfLastSize)&&(a._pfLastSize=a.offsetWidth,d=a.sizes,a.sizes+=",100vw",setTimeout(function(){a.sizes=d}))},e=function(){var a,b=document.querySelectorAll("picture > img, img[srcset][sizes]");for(a=0;a<b.length;a++)d(b[a])},f=function(){clearTimeout(b),b=setTimeout(e,99)},g=a.matchMedia&&matchMedia("(orientation: landscape)"),h=function(){f(),g&&g.addListener&&g.addListener(f)};return c.srcset="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",/^[c|i]|d$/.test(document.readyState||"")?h():document.addEventListener("DOMContentLoaded",h),f}())}(window),function(a,b,c){"use strict";function d(a){return" "===a||" "===a||"\n"===a||"\f"===a||"\r"===a}function e(b,c){var d=new a.Image;return d.onerror=function(){z[b]=!1,aa()},d.onload=function(){z[b]=1===d.width,aa()},d.src=c,"pending"}function f(){L=!1,O=a.devicePixelRatio,M={},N={},s.DPR=O||1,P.width=Math.max(a.innerWidth||0,y.clientWidth),P.height=Math.max(a.innerHeight||0,y.clientHeight),P.vw=P.width/100,P.vh=P.height/100,r=[P.height,P.width,O].join("-"),P.em=s.getEmValue(),P.rem=P.em}function g(a,b,c,d){var e,f,g,h;return"saveData"===A.algorithm?a>2.7?h=c+1:(f=b-c,e=Math.pow(a-.6,1.5),g=f*e,d&&(g+=.1*e),h=a+g):h=c>1?Math.sqrt(a*b):a,h>c}function h(a){var b,c=s.getSet(a),d=!1;"pending"!==c&&(d=r,c&&(b=s.setRes(c),s.applySetCandidate(b,a))),a[s.ns].evaled=d}function i(a,b){return a.res-b.res}function j(a,b,c){var d;return!c&&b&&(c=a[s.ns].sets,c=c&&c[c.length-1]),d=k(b,c),d&&(b=s.makeUrl(b),a[s.ns].curSrc=b,a[s.ns].curCan=d,d.res||_(d,d.set.sizes)),d}function k(a,b){var c,d,e;if(a&&b)for(e=s.parseSet(b),a=s.makeUrl(a),c=0;c<e.length;c++)if(a===s.makeUrl(e[c].url)){d=e[c];break}return d}function l(a,b){var c,d,e,f,g=a.getElementsByTagName("source");for(c=0,d=g.length;d>c;c++)e=g[c],e[s.ns]=!0,f=e.getAttribute("srcset"),f&&b.push({srcset:f,media:e.getAttribute("media"),type:e.getAttribute("type"),sizes:e.getAttribute("sizes")})}function m(a,b){function c(b){var c,d=b.exec(a.substring(m));return d?(c=d[0],m+=c.length,c):void 0}function e(){var a,c,d,e,f,i,j,k,l,m=!1,o={};for(e=0;e<h.length;e++)f=h[e],i=f[f.length-1],j=f.substring(0,f.length-1),k=parseInt(j,10),l=parseFloat(j),W.test(j)&&"w"===i?((a||c)&&(m=!0),0===k?m=!0:a=k):X.test(j)&&"x"===i?((a||c||d)&&(m=!0),0>l?m=!0:c=l):W.test(j)&&"h"===i?((d||c)&&(m=!0),0===k?m=!0:d=k):m=!0;m||(o.url=g,a&&(o.w=a),c&&(o.d=c),d&&(o.h=d),d||c||a||(o.d=1),1===o.d&&(b.has1x=!0),o.set=b,n.push(o))}function f(){for(c(S),i="",j="in descriptor";;){if(k=a.charAt(m),"in descriptor"===j)if(d(k))i&&(h.push(i),i="",j="after descriptor");else{if(","===k)return m+=1,i&&h.push(i),void e();if("("===k)i+=k,j="in parens";else{if(""===k)return i&&h.push(i),void e();i+=k}}else if("in parens"===j)if(")"===k)i+=k,j="in descriptor";else{if(""===k)return h.push(i),void e();i+=k}else if("after descriptor"===j)if(d(k));else{if(""===k)return void e();j="in descriptor",m-=1}m+=1}}for(var g,h,i,j,k,l=a.length,m=0,n=[];;){if(c(T),m>=l)return n;g=c(U),h=[],","===g.slice(-1)?(g=g.replace(V,""),e()):f()}}function n(a){function b(a){function b(){g&&(h.push(g),g="")}function e(){h[0]&&(i.push(h),h=[])}for(var f,g="",h=[],i=[],j=0,k=0,l=!1;;){if(f=a[k],f===c)return b(),e(),i;if(l){if("*"===f&&"/"===a[k+1]){l=!1,k+=2,b();continue}k+=1}else{if(d(f)){if(a[k-1]&&d(a[k-1])||!g){k+=1;continue}if(0===j){b(),k+=1;continue}f=" "}else if("("===f)j+=1;else if(")"===f)j-=1;else{if(","===f){b(),e(),k+=1;continue}if("/"===f&&"*"===a[k+1]){l=!0,k+=2;continue}}g+=f,k+=1}}}function e(a){return l.test(a)&&parseFloat(a)>=0?!0:m.test(a)?!0:"0"===a||"-0"===a||"+0"===a?!0:!1}var f,g,h,i,j,k,l=/^(?:[+-]?[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?(?:ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmin|vmax|vw)$/i,m=/^calc\((?:[0-9a-z \.\+\-\*\/\(\)]+)\)$/i;for(g=b(a),h=g.length,f=0;h>f;f++)if(i=g[f],j=i[i.length-1],e(j)){if(k=j,i.pop(),0===i.length)return k;if(i=i.join(" "),s.matchesMedia(i))return k}return"100vw"}b.createElement("picture");var o,p,q,r,s={},t=function(){},u=b.createElement("img"),v=u.getAttribute,w=u.setAttribute,x=u.removeAttribute,y=b.documentElement,z={},A={algorithm:""},B="data-pfsrc",C=B+"set",D=navigator.userAgent,E=/rident/.test(D)||/ecko/.test(D)&&D.match(/rv\:(\d+)/)&&RegExp.$1>35,F="currentSrc",G=/\s+\+?\d+(e\d+)?w/,H=/(\([^)]+\))?\s*(.+)/,I=a.picturefillCFG,J="position:absolute;left:0;visibility:hidden;display:block;padding:0;border:none;font-size:1em;width:1em;overflow:hidden;clip:rect(0px, 0px, 0px, 0px)",K="font-size:100%!important;",L=!0,M={},N={},O=a.devicePixelRatio,P={px:1,"in":96},Q=b.createElement("a"),R=!1,S=/^[ \t\n\r\u000c]+/,T=/^[, \t\n\r\u000c]+/,U=/^[^ \t\n\r\u000c]+/,V=/[,]+$/,W=/^\d+$/,X=/^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/,Y=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d||!1):a.attachEvent&&a.attachEvent("on"+b,c)},Z=function(a){var b={};return function(c){return c in b||(b[c]=a(c)),b[c]}},$=function(){var a=/^([\d\.]+)(em|vw|px)$/,b=function(){for(var a=arguments,b=0,c=a[0];++b in a;)c=c.replace(a[b],a[++b]);return c},c=Z(function(a){return"return "+b((a||"").toLowerCase(),/\band\b/g,"&&",/,/g,"||",/min-([a-z-\s]+):/g,"e.$1>=",/max-([a-z-\s]+):/g,"e.$1<=",/calc([^)]+)/g,"($1)",/(\d+[\.]*[\d]*)([a-z]+)/g,"($1 * e.$2)",/^(?!(e.[a-z]|[0-9\.&=|><\+\-\*\(\)\/])).*/gi,"")+";"});return function(b,d){var e;if(!(b in M))if(M[b]=!1,d&&(e=b.match(a)))M[b]=e[1]*P[e[2]];else try{M[b]=new Function("e",c(b))(P)}catch(f){}return M[b]}}(),_=function(a,b){return a.w?(a.cWidth=s.calcListLength(b||"100vw"),a.res=a.w/a.cWidth):a.res=a.d,a},aa=function(a){var c,d,e,f=a||{};if(f.elements&&1===f.elements.nodeType&&("IMG"===f.elements.nodeName.toUpperCase()?f.elements=[f.elements]:(f.context=f.elements,f.elements=null)),c=f.elements||s.qsa(f.context||b,f.reevaluate||f.reselect?s.sel:s.selShort),e=c.length){for(s.setupRun(f),R=!0,d=0;e>d;d++)s.fillImg(c[d],f);s.teardownRun(f)}};o=a.console&&console.warn?function(a){console.warn(a)}:t,F in u||(F="src"),z["image/jpeg"]=!0,z["image/gif"]=!0,z["image/png"]=!0,z["image/svg+xml"]=b.implementation.hasFeature("http://wwwindow.w3.org/TR/SVG11/feature#Image","1.1"),s.ns=("pf"+(new Date).getTime()).substr(0,9),s.supSrcset="srcset"in u,s.supSizes="sizes"in u,s.selShort="picture>img,img[srcset]",s.sel=s.selShort,s.cfg=A,s.supSrcset&&(s.sel+=",img["+C+"]"),s.DPR=O||1,s.u=P,s.types=z,q=s.supSrcset&&!s.supSizes,s.setSize=t,s.makeUrl=Z(function(a){return Q.href=a,Q.href}),s.qsa=function(a,b){return a.querySelectorAll(b)},s.matchesMedia=function(){return a.matchMedia&&(matchMedia("(min-width: 0.1em)")||{}).matches?s.matchesMedia=function(a){return!a||matchMedia(a).matches}:s.matchesMedia=s.mMQ,s.matchesMedia.apply(this,arguments)},s.mMQ=function(a){return a?$(a):!0},s.calcLength=function(a){var b=$(a,!0)||!1;return 0>b&&(b=!1),b},s.supportsType=function(a){return a?z[a]:!0},s.parseSize=Z(function(a){var b=(a||"").match(H);return{media:b&&b[1],length:b&&b[2]}}),s.parseSet=function(a){return a.cands||(a.cands=m(a.srcset,a)),a.cands},s.getEmValue=function(){var a;if(!p&&(a=b.body)){var c=b.createElement("div"),d=y.style.cssText,e=a.style.cssText;c.style.cssText=J,y.style.cssText=K,a.style.cssText=K,a.appendChild(c),p=c.offsetWidth,a.removeChild(c),p=parseFloat(p,10),y.style.cssText=d,a.style.cssText=e}return p||16},s.calcListLength=function(a){if(!(a in N)||A.uT){var b=s.calcLength(n(a));N[a]=b?b:P.width}return N[a]},s.setRes=function(a){var b;if(a){b=s.parseSet(a);for(var c=0,d=b.length;d>c;c++)_(b[c],a.sizes)}return b},s.setRes.res=_,s.applySetCandidate=function(a,b){if(a.length){var c,d,e,f,h,k,l,m,n,o,p=b[s.ns],q=s.DPR;if(k=p.curSrc||b[F],l=p.curCan||j(b,k,a[0].set),l&&l.set===a[0].set&&(o=E&&!b.complete&&l.res-.1>q,o||(l.cached=!0,l&&m&&l.res>=q&&(h=l))),!h)for(a.sort(i),f=a.length,h=a[f-1],d=0;f>d;d++)if(c=a[d],c.res>=q){e=d-1,h=a[e]&&(o||k!==s.makeUrl(c.url))&&g(a[e].res,c.res,q,a[e].cached)?a[e]:c;break}h&&(n=s.makeUrl(h.url),p.curSrc=n,p.curCan=h,n!==k&&s.setSrc(b,h),s.setSize(b))}},s.setSrc=function(a,b){var c;a.src=b.url,"image/svg+xml"===b.set.type&&(c=a.style.width,a.style.width=a.offsetWidth+1+"px",a.offsetWidth+1&&(a.style.width=c))},s.getSet=function(a){var b,c,d,e=!1,f=a[s.ns].sets;for(b=0;b<f.length&&!e;b++)if(c=f[b],c.srcset&&s.matchesMedia(c.media)&&(d=s.supportsType(c.type))){"pending"===d&&(c=d),e=c;break}return e},s.parseSets=function(a,b,d){var e,f,g,h,i=b&&"PICTURE"===b.nodeName.toUpperCase(),j=a[s.ns];(j.src===c||d.src)&&(j.src=v.call(a,"src"),j.src?w.call(a,B,j.src):x.call(a,B)),(j.srcset===c||d.srcset||!s.supSrcset||a.srcset)&&(e=v.call(a,"srcset"),j.srcset=e,h=!0),j.sets=[],i&&(j.pic=!0,l(b,j.sets)),j.srcset?(f={srcset:j.srcset,sizes:v.call(a,"sizes")},j.sets.push(f),g=(q||j.src)&&G.test(j.srcset||""),g||!j.src||k(j.src,f)||f.has1x||(f.srcset+=", "+j.src,f.cands.push({url:j.src,d:1,set:f}))):j.src&&j.sets.push({srcset:j.src,sizes:null}),j.curCan=null,j.curSrc=c,j.supported=!(i||f&&!s.supSrcset||g),h&&s.supSrcset&&!j.supported&&(e?(w.call(a,C,e),a.srcset=""):x.call(a,C)),j.supported&&!j.srcset&&(!j.src&&a.src||a.src!==s.makeUrl(j.src))&&(null===j.src?a.removeAttribute("src"):a.src=j.src),j.parsed=!0},s.fillImg=function(a,b){var c,d=b.reselect||b.reevaluate;a[s.ns]||(a[s.ns]={}),c=a[s.ns],(d||c.evaled!==r)&&((!c.parsed||b.reevaluate)&&s.parseSets(a,a.parentNode,b),c.supported?c.evaled=r:h(a))},s.setupRun=function(){(!R||L||O!==a.devicePixelRatio)&&f()},a.HTMLPictureElement?(aa=t,s.fillImg=t):!function(){var c,d=a.attachEvent?/d$|^c/:/d$|^c|^i/,f=function(){var a=b.readyState||"";g=setTimeout(f,"loading"===a?200:999),b.body&&(s.fillImgs(),c=c||d.test(a),c&&clearTimeout(g))},g=setTimeout(f,b.body?9:99),h=function(a,b){var c,d,e=function(){var f=new Date-d;b>f?c=setTimeout(e,b-f):(c=null,a())};return function(){d=new Date,c||(c=setTimeout(e,b))}},i=function(){L=!0,s.fillImgs()};Y(a,"resize",h(i,99)),Y(b,"readystatechange",f),z["image/webp"]=e("image/webp","data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==")}(),s.picturefill=aa,s.fillImgs=aa,s.teardownRun=t,aa._=s,a.picturefillCFG={pf:s,push:function(a){var b=a.shift();"function"==typeof s[b]?s[b].apply(s,a):(A[b]=a[0],R&&s.fillImgs({reselect:!0}))}};for(;I&&I.length;)a.picturefillCFG.push(I.shift());a.picturefill=aa,"object"==typeof module&&"object"==typeof module.exports?module.exports=aa:"function"==typeof define&&define.amd&&define("picturefill",function(){return aa})}(window,document); |
@@ -5,3 +5,3 @@ /*global module:true*/ | ||
var pkg, toConcat = [ "src/external/matchmedia.js", "src/picturefill.js" ], supportTypes = ""; | ||
var pkg; | ||
module.exports = function(grunt) { | ||
@@ -22,4 +22,17 @@ | ||
}, | ||
copy: { | ||
plugins: { | ||
files: [ | ||
{ | ||
expand: true, | ||
cwd: "src/plugins/", | ||
src: [ "**", "!gecko-picture/*" ], | ||
dest: "dist/plugins/", | ||
filter: "isFile" | ||
} | ||
], | ||
}, | ||
}, | ||
concat: { | ||
dist: { | ||
@@ -30,3 +43,3 @@ options: { | ||
}, | ||
src: toConcat, | ||
src: [ "src/plugins/gecko-picture/pf.gecko-picture.js", "src/picturefill.js" ], | ||
dest: "dist/picturefill.js" | ||
@@ -40,9 +53,17 @@ } | ||
dist: { | ||
src: [ "<%= concat.dist.src %>" ], | ||
dest: "dist/picturefill.min.js" | ||
files: [ | ||
{ | ||
expand: true, | ||
cwd: "dist/", | ||
src: [ "**/*.js", "!*.min.js", "!**/*.min.js" ], | ||
dest: "dist/", | ||
ext: ".min.js", | ||
extDot: "last" | ||
} | ||
] | ||
} | ||
}, | ||
qunit: { | ||
files: [ "tests/**/*.html" ] | ||
files: [ "tests/*.html" ] | ||
}, | ||
@@ -54,3 +75,3 @@ jshint: { | ||
}, | ||
src: [ "Gruntfile.js", "src/*.js", "src/includes/*.js", "tests/*.js" ] | ||
src: [ "Gruntfile.js", "src/**/*.js" ] | ||
} | ||
@@ -63,6 +84,19 @@ }, | ||
}, | ||
"gh-pages": { | ||
options: { | ||
base: "." | ||
}, | ||
src: [ "**/*", "!node_modules/**/*", "!test/**/*", "!src/**/*" ] | ||
}, | ||
release: { | ||
options: { | ||
commitMessage: "Picturefill <%= version %>", | ||
tagMessage: "Picturefill <%= version %>", | ||
afterRelease: [ "gh-pages" ] | ||
} | ||
}, | ||
watch: { | ||
gruntfile: { | ||
files: [ "Gruntfile.js", "src/*.js", "src/includes/*.js", "tests/*.js" ], | ||
tasks: [ "support-types" + supportTypes, "default" ], | ||
tasks: [ "default" ], | ||
options: { | ||
@@ -87,2 +121,3 @@ spawn: false | ||
grunt.loadNpmTasks("grunt-contrib-clean"); | ||
grunt.loadNpmTasks("grunt-contrib-copy"); | ||
grunt.loadNpmTasks("grunt-contrib-concat"); | ||
@@ -94,33 +129,10 @@ grunt.loadNpmTasks("grunt-contrib-jshint"); | ||
grunt.loadNpmTasks("grunt-jscs-checker"); | ||
grunt.task.registerTask("support-types", "insert support for image types dev wants to include", function() { | ||
var supportTypes = ""; | ||
for (var i = 0; i < arguments.length; i++) { | ||
var arg = arguments[i]; | ||
switch ( arg ) { | ||
case "webp": | ||
case "svg": | ||
case "jxr": | ||
case "jp2": | ||
case "apng": | ||
toConcat.push("src/includes/" + arg + ".js"); | ||
} | ||
supportTypes += ":" + arg; | ||
} | ||
if (!supportTypes) { | ||
supportTypes = supportTypes; | ||
} | ||
grunt.task.run("default"); | ||
console.log("files to concatenate", toConcat); | ||
} ); | ||
grunt.loadNpmTasks("grunt-gh-pages"); | ||
grunt.loadNpmTasks("grunt-release"); | ||
// Default task. | ||
grunt.registerTask("default", [ "jscs", "test", "clean", "concat", "uglify" ]); | ||
grunt.registerTask("default", [ "jscs", "test", "clean", "concat", "copy", "uglify" ]); | ||
grunt.registerTask("test", [ "jscs", "jshint", "qunit" ]); | ||
grunt.registerTask("publish", [ "gh-pages" ]); | ||
}; | ||
})(); |
{ | ||
"name": "picturefill", | ||
"description": "A responsive image polyfill.", | ||
"version": "2.3.1", | ||
"homepage": "https://scottjehl.github.io/picturefill/", | ||
"bugs": "https://github.com/scottjehl/picturefill/issues", | ||
"license": "MIT", | ||
"author": "Scott Jehl <scottjehl@gmail.com>", | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:scottjehl/picturefill.git" | ||
}, | ||
"keywords": [ | ||
"picturefill", | ||
"srcset", | ||
"picture", | ||
"responsive", | ||
"responsive images" | ||
], | ||
"engines": { | ||
"node": ">= 0.8.0" | ||
}, | ||
"main": "./dist/picturefill", | ||
"scripts": { | ||
"test": "grunt test --verbose" | ||
}, | ||
"devDependencies": { | ||
"grunt": "~0.4.2", | ||
"grunt-cli": "~0.1", | ||
"grunt-contrib-clean": "~0.4.0", | ||
"grunt-contrib-concat": "~0.3.0", | ||
"grunt-contrib-jshint": "~0.10.0", | ||
"grunt-contrib-qunit": "~0.2.0", | ||
"grunt-contrib-uglify": "~0.2.0", | ||
"grunt-contrib-watch": "~0.6.0", | ||
"grunt-jscs-checker": "~0.4.3", | ||
"grunt-insert": "~0.1.0" | ||
} | ||
"name": "picturefill", | ||
"description": "A responsive image polyfill.", | ||
"version": "3.0.0-beta1", | ||
"homepage": "https://scottjehl.github.io/picturefill/", | ||
"bugs": "https://github.com/scottjehl/picturefill/issues", | ||
"license": "MIT", | ||
"author": "Scott Jehl <scottjehl@gmail.com>", | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:scottjehl/picturefill.git" | ||
}, | ||
"keywords": [ | ||
"picturefill", | ||
"srcset", | ||
"picture", | ||
"responsive", | ||
"responsive images" | ||
], | ||
"engines": { | ||
"node": ">= 0.8.0" | ||
}, | ||
"main": "./dist/picturefill", | ||
"scripts": { | ||
"test": "grunt test --verbose" | ||
}, | ||
"devDependencies": { | ||
"grunt": "~0.4.2", | ||
"grunt-cli": "~0.1", | ||
"grunt-contrib-clean": "~0.4.0", | ||
"grunt-contrib-concat": "~0.3.0", | ||
"grunt-contrib-copy": "^0.8.0", | ||
"grunt-contrib-jshint": "~0.10.0", | ||
"grunt-contrib-qunit": "~0.2.0", | ||
"grunt-contrib-uglify": "~0.2.0", | ||
"grunt-contrib-watch": "~0.6.0", | ||
"grunt-gh-pages": "^0.10.0", | ||
"grunt-insert": "~0.1.0", | ||
"grunt-jscs-checker": "~0.4.3", | ||
"grunt-release": "^0.12.0" | ||
} | ||
} |
@@ -5,3 +5,3 @@ { | ||
"description": "A Polyfill for the HTML Picture Element (http://picture.responsiveimages.org/) that you can use today.", | ||
"version": "2.3.1", | ||
"version": "3.0.0-beta1", | ||
"homepage": "http://scottjehl.github.io/picturefill", | ||
@@ -8,0 +8,0 @@ "author": { |
@@ -1,2 +0,2 @@ | ||
# Picturefill | ||
# Picturefill | ||
A [responsive image](http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#embedded-content) polyfill. | ||
@@ -3,0 +3,0 @@ * Authors: Scott Jehl, Mat Marquis, Shawn Jansepar (2.0 refactor lead), and many more: see Authors.txt |
/*! 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, image ) { | ||
* Author: Scott Jehl, Filament Group, 2012 ( new proposal implemented by Shawn Jansepar ) | ||
* License: MIT | ||
* Spec: http://picture.responsiveimages.org/ | ||
*/ | ||
(function( window, document, undefined ) { | ||
/* global parseSizes */ | ||
// Enable strict mode | ||
"use strict"; | ||
function expose(picturefill) { | ||
/* expose picturefill */ | ||
if ( typeof module === "object" && typeof module.exports === "object" ) { | ||
// CommonJS, just export | ||
module.exports = picturefill; | ||
} else if ( typeof define === "function" && define.amd ) { | ||
// AMD support | ||
define( "picturefill", function() { return picturefill; } ); | ||
} | ||
if ( typeof w === "object" ) { | ||
// If no AMD and we are in the browser, attach to window | ||
w.picturefill = picturefill; | ||
} | ||
} | ||
// If picture is supported, well, that's awesome. Let's get outta here... | ||
if ( w.HTMLPictureElement ) { | ||
expose(function() { }); | ||
return; | ||
} | ||
// HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround) | ||
doc.createElement( "picture" ); | ||
document.createElement( "picture" ); | ||
var warn, eminpx, alwaysCheckWDescriptor, evalId; | ||
// local object for method references and testing exposure | ||
var pf = w.picturefill || {}; | ||
var pf = {}; | ||
var noop = function() {}; | ||
var image = document.createElement( "img" ); | ||
var getImgAttr = image.getAttribute; | ||
var setImgAttr = image.setAttribute; | ||
var removeImgAttr = image.removeAttribute; | ||
var docElem = document.documentElement; | ||
var types = {}; | ||
var cfg = { | ||
//resource selection: | ||
algorithm: "" | ||
}; | ||
var srcAttr = "data-pfsrc"; | ||
var srcsetAttr = srcAttr + "set"; | ||
// ua sniffing is done for undetectable img loading features, | ||
// to do some non crucial perf optimizations | ||
var ua = navigator.userAgent; | ||
var supportAbort = (/rident/).test(ua) || ((/ecko/).test(ua) && ua.match(/rv\:(\d+)/) && RegExp.$1 > 35 ); | ||
var curSrcProp = "currentSrc"; | ||
var regWDesc = /\s+\+?\d+(e\d+)?w/; | ||
var regSize = /(\([^)]+\))?\s*(.+)/; | ||
var setOptions = window.picturefillCFG; | ||
/** | ||
* Shortcut property for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests ) | ||
*/ | ||
// baseStyle also used by getEmValue (i.e.: width: 1em is important) | ||
var baseStyle = "position:absolute;left:0;visibility:hidden;display:block;padding:0;border:none;font-size:1em;width:1em;overflow:hidden;clip:rect(0px, 0px, 0px, 0px)"; | ||
var fsCss = "font-size:100%!important;"; | ||
var isVwDirty = true; | ||
// namespace | ||
pf.ns = "picturefill"; | ||
var cssCache = {}; | ||
var sizeLengthCache = {}; | ||
var DPR = window.devicePixelRatio; | ||
var units = { | ||
px: 1, | ||
"in": 96 | ||
}; | ||
var anchor = document.createElement( "a" ); | ||
/** | ||
* alreadyRun flag used for setOptions. is it true setOptions will reevaluate | ||
* @type {boolean} | ||
*/ | ||
var alreadyRun = false; | ||
// srcset support test | ||
(function() { | ||
pf.srcsetSupported = "srcset" in image; | ||
pf.sizesSupported = "sizes" in image; | ||
pf.curSrcSupported = "currentSrc" in image; | ||
})(); | ||
// Reusable, non-"g" Regexes | ||
// just a string trim workaround | ||
pf.trim = function( str ) { | ||
return str.trim ? str.trim() : str.replace( /^\s+|\s+$/g, "" ); | ||
// (Don't use \s, to avoid matching non-breaking space.) | ||
var regexLeadingSpaces = /^[ \t\n\r\u000c]+/, | ||
regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/, | ||
regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/, | ||
regexTrailingCommas = /[,]+$/, | ||
regexNonNegativeInteger = /^\d+$/, | ||
// ( Positive or negative or unsigned integers or decimals, without or without exponents. | ||
// Must include at least one digit. | ||
// According to spec tests any decimal point must be followed by a digit. | ||
// No leading plus sign is allowed.) | ||
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number | ||
regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/; | ||
var on = function(obj, evt, fn, capture) { | ||
if ( obj.addEventListener ) { | ||
obj.addEventListener(evt, fn, capture || false); | ||
} else if ( obj.attachEvent ) { | ||
obj.attachEvent( "on" + evt, fn); | ||
} | ||
}; | ||
/** | ||
* Gets a string and returns the absolute URL | ||
* @param src | ||
* @returns {String} absolute URL | ||
* simple memoize function: | ||
*/ | ||
pf.makeUrl = (function() { | ||
var anchor = doc.createElement( "a" ); | ||
return function(src) { | ||
anchor.href = src; | ||
return anchor.href; | ||
var memoize = function(fn) { | ||
var cache = {}; | ||
return function(input) { | ||
if ( !(input in cache) ) { | ||
cache[ input ] = fn(input); | ||
} | ||
return cache[ input ]; | ||
}; | ||
})(); | ||
}; | ||
// UTILITY FUNCTIONS | ||
// Manual is faster than RegEx | ||
// http://jsperf.com/whitespace-character/5 | ||
function isSpace(c) { | ||
return (c === "\u0020" || // space | ||
c === "\u0009" || // horizontal tab | ||
c === "\u000A" || // new line | ||
c === "\u000C" || // form feed | ||
c === "\u000D"); // carriage return | ||
} | ||
/** | ||
* Shortcut method for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests ) | ||
* gets a mediaquery and returns a boolean or gets a css length and returns a number | ||
* @param css mediaqueries or css length | ||
* @returns {boolean|number} | ||
* | ||
* based on: https://gist.github.com/jonathantneal/db4f77009b155f083738 | ||
*/ | ||
pf.restrictsMixedContent = function() { | ||
return w.location.protocol === "https:"; | ||
var evalCSS = (function() { | ||
var regLength = /^([\d\.]+)(em|vw|px)$/; | ||
var replace = function() { | ||
var args = arguments, index = 0, string = args[0]; | ||
while (++index in args) { | ||
string = string.replace(args[index], args[++index]); | ||
} | ||
return string; | ||
}; | ||
var buidlStr = memoize(function(css) { | ||
return "return " + replace((css || "").toLowerCase(), | ||
// interpret `and` | ||
/\band\b/g, "&&", | ||
// interpret `,` | ||
/,/g, "||", | ||
// interpret `min-` as >= | ||
/min-([a-z-\s]+):/g, "e.$1>=", | ||
// interpret `max-` as <= | ||
/max-([a-z-\s]+):/g, "e.$1<=", | ||
//calc value | ||
/calc([^)]+)/g, "($1)", | ||
// interpret css values | ||
/(\d+[\.]*[\d]*)([a-z]+)/g, "($1 * e.$2)", | ||
//make eval less evil | ||
/^(?!(e.[a-z]|[0-9\.&=|><\+\-\*\(\)\/])).*/ig, "" | ||
) + ";"; | ||
}); | ||
return function(css, length) { | ||
var parsedLength; | ||
if (!(css in cssCache)) { | ||
cssCache[css] = false; | ||
if (length && (parsedLength = css.match( regLength ))) { | ||
cssCache[css] = parsedLength[ 1 ] * units[parsedLength[ 2 ]]; | ||
} else { | ||
/*jshint evil:true */ | ||
try{ | ||
cssCache[css] = new Function("e", buidlStr(css))(units); | ||
} catch(e) {} | ||
/*jshint evil:false */ | ||
} | ||
} | ||
return cssCache[css]; | ||
}; | ||
})(); | ||
var setResolution = function( candidate, sizesattr ) { | ||
if ( candidate.w ) { // h = means height: || descriptor.type === 'h' do not handle yet... | ||
candidate.cWidth = pf.calcListLength( sizesattr || "100vw" ); | ||
candidate.res = candidate.w / candidate.cWidth ; | ||
} else { | ||
candidate.res = candidate.d; | ||
} | ||
return candidate; | ||
}; | ||
/** | ||
* Shortcut method for matchMedia ( for easy overriding in tests ) | ||
* | ||
* @param opt | ||
*/ | ||
var picturefill = function( opt ) { | ||
var elements, i, plen; | ||
pf.matchesMedia = function( media ) { | ||
return w.matchMedia && w.matchMedia( media ).matches; | ||
}; | ||
var options = opt || {}; | ||
// Shortcut method for `devicePixelRatio` ( for easy overriding in tests ) | ||
pf.getDpr = function() { | ||
return ( w.devicePixelRatio || 1 ); | ||
if ( options.elements && options.elements.nodeType === 1 ) { | ||
if ( options.elements.nodeName.toUpperCase() === "IMG" ) { | ||
options.elements = [ options.elements ]; | ||
} else { | ||
options.context = options.elements; | ||
options.elements = null; | ||
} | ||
} | ||
elements = options.elements || pf.qsa( (options.context || document), ( options.reevaluate || options.reselect ) ? pf.sel : pf.selShort ); | ||
if ( (plen = elements.length) ) { | ||
pf.setupRun( options ); | ||
alreadyRun = true; | ||
// Loop through all elements | ||
for ( i = 0; i < plen; i++ ) { | ||
pf.fillImg(elements[ i ], options); | ||
} | ||
pf.teardownRun( options ); | ||
} | ||
}; | ||
/** | ||
* Get width in css pixel value from a "length" value | ||
* http://dev.w3.org/csswg/css-values-3/#length-value | ||
* outputs a warning for the developer | ||
* @param {message} | ||
* @type {Function} | ||
*/ | ||
pf.getWidthFromLength = function( length ) { | ||
var cssValue; | ||
// If a length is specified and doesn’t contain a percentage, and it is greater than 0 or using `calc`, use it. Else, abort. | ||
if ( !(length && length.indexOf( "%" ) > -1 === false && ( parseFloat( length ) > 0 || length.indexOf( "calc(" ) > -1 )) ) { | ||
return false; | ||
} | ||
warn = ( window.console && console.warn ) ? | ||
function( message ) { | ||
console.warn( message ); | ||
} : | ||
noop | ||
; | ||
/** | ||
* 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`? The risk of doing this is possible browser inconsistancies with vw vs % | ||
*/ | ||
length = length.replace( "vw", "%" ); | ||
if ( !(curSrcProp in image) ) { | ||
curSrcProp = "src"; | ||
} | ||
// Create a cached element for getting length value widths | ||
if ( !pf.lengthEl ) { | ||
pf.lengthEl = doc.createElement( "div" ); | ||
// Add support for standard mime types. | ||
types[ "image/jpeg" ] = true; | ||
types[ "image/gif" ] = true; | ||
types[ "image/png" ] = true; | ||
// Positioning styles help prevent padding/margin/width on `html` or `body` from throwing calculations off. | ||
pf.lengthEl.style.cssText = "border:0;display:block;font-size:1em;left:0;margin:0;padding:0;position:absolute;visibility:hidden"; | ||
function detectTypeSupport( type, typeUri ) { | ||
// based on Modernizr's lossless img-webp test | ||
// note: asynchronous | ||
var image = new window.Image(); | ||
image.onerror = function() { | ||
types[ type ] = false; | ||
picturefill(); | ||
}; | ||
image.onload = function() { | ||
types[ type ] = image.width === 1; | ||
picturefill(); | ||
}; | ||
image.src = typeUri; | ||
return "pending"; | ||
} | ||
// Add a class, so that everyone knows where this element comes from | ||
pf.lengthEl.className = "helper-from-picturefill-js"; | ||
} | ||
// test svg support | ||
types[ "image/svg+xml" ] = document.implementation.hasFeature( "http://wwwindow.w3.org/TR/SVG11/feature#Image", "1.1" ); | ||
pf.lengthEl.style.width = "0px"; | ||
/** | ||
* updates the internal vW property with the current viewport width in px | ||
*/ | ||
function updateMetrics() { | ||
try { | ||
pf.lengthEl.style.width = length; | ||
} catch ( e ) {} | ||
isVwDirty = false; | ||
DPR = window.devicePixelRatio; | ||
cssCache = {}; | ||
sizeLengthCache = {}; | ||
doc.body.appendChild(pf.lengthEl); | ||
pf.DPR = DPR || 1; | ||
cssValue = pf.lengthEl.offsetWidth; | ||
units.width = Math.max(window.innerWidth || 0, docElem.clientWidth); | ||
units.height = Math.max(window.innerHeight || 0, docElem.clientHeight); | ||
if ( cssValue <= 0 ) { | ||
cssValue = false; | ||
} | ||
units.vw = units.width / 100; | ||
units.vh = units.height / 100; | ||
doc.body.removeChild( pf.lengthEl ); | ||
evalId = [ units.height, units.width, DPR ].join("-"); | ||
return cssValue; | ||
}; | ||
units.em = pf.getEmValue(); | ||
units.rem = units.em; | ||
} | ||
pf.detectTypeSupport = function( type, typeUri ) { | ||
// based on Modernizr's lossless img-webp test | ||
// note: asynchronous | ||
var image = new w.Image(); | ||
image.onerror = function() { | ||
pf.types[ type ] = false; | ||
picturefill(); | ||
}; | ||
image.onload = function() { | ||
pf.types[ type ] = image.width === 1; | ||
picturefill(); | ||
}; | ||
image.src = typeUri; | ||
function chooseLowRes( lowerValue, higherValue, dprValue, isCached ) { | ||
var bonusFactor, tooMuch, bonus, meanDensity; | ||
return "pending"; | ||
}; | ||
// container of supported mime types that one might need to qualify before using | ||
pf.types = pf.types || {}; | ||
//experimental | ||
if (cfg.algorithm === "saveData" ){ | ||
if ( lowerValue > 2.7 ) { | ||
meanDensity = dprValue + 1; | ||
} else { | ||
tooMuch = higherValue - dprValue; | ||
bonusFactor = Math.pow(lowerValue - 0.6, 1.5); | ||
pf.initTypeDetects = function() { | ||
// Add support for standard mime types | ||
pf.types[ "image/jpeg" ] = true; | ||
pf.types[ "image/gif" ] = true; | ||
pf.types[ "image/png" ] = true; | ||
pf.types[ "image/svg+xml" ] = doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1"); | ||
pf.types[ "image/webp" ] = pf.detectTypeSupport("image/webp", "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA="); | ||
}; | ||
bonus = tooMuch * bonusFactor; | ||
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; | ||
if (isCached) { | ||
bonus += 0.1 * bonusFactor; | ||
} | ||
meanDensity = lowerValue + bonus; | ||
} | ||
} else { | ||
var pfType = pf.types[ type ]; | ||
// 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 pfType === "string" && pfType !== "pending") { | ||
pf.types[ type ] = pf.detectTypeSupport( type, pfType ); | ||
return "pending"; | ||
} else if ( typeof pfType === "function" ) { | ||
pfType(); | ||
return "pending"; | ||
} else { | ||
return pfType; | ||
meanDensity = (dprValue > 1) ? | ||
Math.sqrt(lowerValue * higherValue) : | ||
lowerValue; | ||
} | ||
return meanDensity > dprValue; | ||
} | ||
function applyBestCandidate( img ) { | ||
var srcSetCandidates; | ||
var matchingSet = pf.getSet( img ); | ||
var evaluated = false; | ||
if ( matchingSet !== "pending" ) { | ||
evaluated = evalId; | ||
if ( matchingSet ) { | ||
srcSetCandidates = pf.setRes( matchingSet ); | ||
pf.applySetCandidate( srcSetCandidates, img ); | ||
} | ||
} | ||
}; | ||
img[ pf.ns ].evaled = evaluated; | ||
} | ||
// 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] | ||
}; | ||
}; | ||
function ascendingSort( a, b ) { | ||
return a.res - b.res; | ||
} | ||
// 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; | ||
function setSrcToCur( img, src, set ) { | ||
var candidate; | ||
if ( !set && src ) { | ||
set = img[ pf.ns ].sets; | ||
set = set && set[set.length - 1]; | ||
} | ||
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; | ||
candidate = getCandidateForSrc(src, set); | ||
if ( !length ) { | ||
continue; | ||
if ( candidate ) { | ||
src = pf.makeUrl(src); | ||
img[ pf.ns ].curSrc = src; | ||
img[ pf.ns ].curCan = candidate; | ||
if ( !candidate.res ) { | ||
setResolution( candidate, candidate.set.sizes ); | ||
} | ||
// if there is no media query or it matches, choose this as our winning length | ||
if ( (!media || pf.matchesMedia( media )) && | ||
// 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 | ||
(winningLength = pf.getWidthFromLength( length )) ) { | ||
break; | ||
} | ||
return candidate; | ||
} | ||
function getCandidateForSrc( src, set ) { | ||
var i, candidate, candidates; | ||
if ( src && set ) { | ||
candidates = pf.parseSet( set ); | ||
src = pf.makeUrl(src); | ||
for ( i = 0; i < candidates.length; i++ ) { | ||
if ( src === pf.makeUrl(candidates[ i ].url) ) { | ||
candidate = candidates[ i ]; | ||
break; | ||
} | ||
} | ||
} | ||
return candidate; | ||
} | ||
//if we have no winningLength fallback to 100vw | ||
return winningLength || Math.max(w.innerWidth || 0, doc.documentElement.clientWidth); | ||
}; | ||
function getAllSourceElements( picture, candidates ) { | ||
var i, len, source, srcset; | ||
pf.parseSrcset = function( srcset ) { | ||
// SPEC mismatch intended for size and perf: | ||
// actually only source elements preceding the img should be used | ||
// also note: don't use qsa here, because IE8 sometimes doesn't like source as the key part in a selector | ||
var sources = picture.getElementsByTagName( "source" ); | ||
for ( i = 0, len = sources.length; i < len; i++ ) { | ||
source = sources[ i ]; | ||
source[ pf.ns ] = true; | ||
srcset = source.getAttribute( "srcset" ); | ||
// if source does not have a srcset attribute, skip | ||
if ( srcset ) { | ||
candidates.push( { | ||
srcset: srcset, | ||
media: source.getAttribute( "media" ), | ||
type: source.getAttribute( "type" ), | ||
sizes: source.getAttribute( "sizes" ) | ||
} ); | ||
} | ||
} | ||
} | ||
/** | ||
* Srcset Parser | ||
* By Alex Bell | MIT License | ||
* | ||
* @returns Array [{url: _, d: _, w: _, h:_, set:_(????)}, ...] | ||
* | ||
* Based super duper closely on the reference algorithm at: | ||
* https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-srcset-attribute | ||
*/ | ||
// 1. Let input be the value passed to this algorithm. | ||
// (TO-DO : Explain what "set" argument is here. Maybe choose a more | ||
// descriptive & more searchable name. Since passing the "set" in really has | ||
// nothing to do with parsing proper, I would prefer this assignment eventually | ||
// go in an external fn.) | ||
function parseSrcset(input, set) { | ||
function collectCharacters(regEx) { | ||
var chars, | ||
match = regEx.exec(input.substring(pos)); | ||
if (match) { | ||
chars = match[ 0 ]; | ||
pos += chars.length; | ||
return chars; | ||
} | ||
} | ||
var inputLength = input.length, | ||
url, | ||
descriptors, | ||
currentDescriptor, | ||
state, | ||
c, | ||
// 2. Let position be a pointer into input, initially pointing at the start | ||
// of the string. | ||
pos = 0, | ||
// 3. Let candidates be an initially empty source set. | ||
candidates = []; | ||
/** | ||
* A lot of this was pulled from Boris Smus’ parser for the now-defunct WHATWG `srcset` | ||
* https://github.com/borismus/srcset-polyfill/blob/master/js/srcset-info.js | ||
* | ||
* 1. Let input (`srcset`) be the value passed to this algorithm. | ||
* 2. Let position be a pointer into input, initially pointing at the start of the string. | ||
* 3. Let raw candidates be an initially empty ordered list of URLs with associated | ||
* unparsed descriptors. The order of entries in the list is the order in which entries | ||
* are added to the list. | ||
*/ | ||
var candidates = []; | ||
* Adds descriptor properties to a candidate, pushes to the candidates array | ||
* @return undefined | ||
*/ | ||
// (Declared outside of the while loop so that it's only created once. | ||
// (This fn is defined before it is used, in order to pass JSHINT. | ||
// Unfortunately this breaks the sequencing of the spec comments. :/ ) | ||
function parseDescriptors() { | ||
while ( srcset !== "" ) { | ||
srcset = srcset.replace( /^\s+/g, "" ); | ||
// 9. Descriptor parser: Let error be no. | ||
var pError = false, | ||
// 5. Collect a sequence of characters that are not space characters, and let that be url. | ||
var pos = srcset.search(/\s/g), | ||
url, descriptor = null; | ||
// 10. Let width be absent. | ||
// 11. Let density be absent. | ||
// 12. Let future-compat-h be absent. (We're implementing it now as h) | ||
w, d, h, i, | ||
candidate = {}, | ||
desc, lastChar, value, intVal, floatVal; | ||
if ( pos !== -1 ) { | ||
url = srcset.slice( 0, pos ); | ||
// 13. For each descriptor in descriptors, run the appropriate set of steps | ||
// from the following list: | ||
for (i = 0 ; i < descriptors.length; i++) { | ||
desc = descriptors[ i ]; | ||
var last = url.slice(-1); | ||
lastChar = desc[ desc.length - 1 ]; | ||
value = desc.substring(0, desc.length - 1); | ||
intVal = parseInt(value, 10); | ||
floatVal = parseFloat(value); | ||
// 6. If url ends with a U+002C COMMA character (,), remove that character from url | ||
// and let descriptors be the empty string. Otherwise, follow these substeps | ||
// 6.1. If url is empty, then jump to the step labeled descriptor parser. | ||
// If the descriptor consists of a valid non-negative integer followed by | ||
// a U+0077 LATIN SMALL LETTER W character | ||
if (regexNonNegativeInteger.test(value) && (lastChar === "w")) { | ||
if ( last === "," || url === "" ) { | ||
url = url.replace( /,+$/, "" ); | ||
descriptor = ""; | ||
} | ||
srcset = srcset.slice( pos + 1 ); | ||
// If width and density are not both absent, then let error be yes. | ||
if (w || d) {pError = true;} | ||
// 6.2. Collect a sequence of characters that are not U+002C COMMA characters (,), and | ||
// let that be descriptors. | ||
if ( descriptor === null ) { | ||
var descpos = srcset.indexOf( "," ); | ||
if ( descpos !== -1 ) { | ||
descriptor = srcset.slice( 0, descpos ); | ||
srcset = srcset.slice( descpos + 1 ); | ||
// Apply the rules for parsing non-negative integers to the descriptor. | ||
// If the result is zero, let error be yes. | ||
// Otherwise, let width be the result. | ||
if (intVal === 0) {pError = true;} else {w = intVal;} | ||
// If the descriptor consists of a valid floating-point number followed by | ||
// a U+0078 LATIN SMALL LETTER X character | ||
} else if (regexFloatingPoint.test(value) && (lastChar === "x")) { | ||
// If width, density and future-compat-h are not all absent, then let error | ||
// be yes. | ||
if (w || d || h) {pError = true;} | ||
// Apply the rules for parsing floating-point number values to the descriptor. | ||
// If the result is less than zero, let error be yes. Otherwise, let density | ||
// be the result. | ||
if (floatVal < 0) {pError = true;} else {d = floatVal;} | ||
// If the descriptor consists of a valid non-negative integer followed by | ||
// a U+0068 LATIN SMALL LETTER H character | ||
} else if (regexNonNegativeInteger.test(value) && (lastChar === "h")) { | ||
// If height and density are not both absent, then let error be yes. | ||
if (h || d) {pError = true;} | ||
// Apply the rules for parsing non-negative integers to the descriptor. | ||
// If the result is zero, let error be yes. Otherwise, let future-compat-h | ||
// be the result. | ||
if (intVal === 0) {pError = true;} else {h = intVal;} | ||
// Anything else, Let error be yes. | ||
} else {pError = true;} | ||
} // (close step 13 for loop) | ||
// 15. If error is still no, then append a new image source to candidates whose | ||
// URL is url, associated with a width width if not absent and a pixel | ||
// density density if not absent. Otherwise, there is a parse error. | ||
if (!pError) { | ||
candidate.url = url; | ||
if (w) { candidate.w = w;} | ||
if (d) { candidate.d = d;} | ||
if (h) { candidate.h = h;} | ||
if (!h && !d && !w) {candidate.d = 1;} | ||
if (candidate.d === 1) {set.has1x = true;} | ||
candidate.set = set; | ||
candidates.push(candidate); | ||
} | ||
} // (close parseDescriptors fn) | ||
/** | ||
* Tokenizes descriptor properties prior to parsing | ||
* Returns undefined. | ||
* (Again, this fn is defined before it is used, in order to pass JSHINT. | ||
* Unfortunately this breaks the logical sequencing of the spec comments. :/ ) | ||
*/ | ||
function tokenize() { | ||
// 8.1. Descriptor tokeniser: Skip whitespace | ||
collectCharacters(regexLeadingSpaces); | ||
// 8.2. Let current descriptor be the empty string. | ||
currentDescriptor = ""; | ||
// 8.3. Let state be in descriptor. | ||
state = "in descriptor"; | ||
while (true) { | ||
// 8.4. Let c be the character at position. | ||
c = input.charAt(pos); | ||
// Do the following depending on the value of state. | ||
// For the purpose of this step, "EOF" is a special character representing | ||
// that position is past the end of input. | ||
// In descriptor | ||
if (state === "in descriptor") { | ||
// Do the following, depending on the value of c: | ||
// Space character | ||
// If current descriptor is not empty, append current descriptor to | ||
// descriptors and let current descriptor be the empty string. | ||
// Set state to after descriptor. | ||
if (isSpace(c)) { | ||
if (currentDescriptor) { | ||
descriptors.push(currentDescriptor); | ||
currentDescriptor = ""; | ||
state = "after descriptor"; | ||
} | ||
// U+002C COMMA (,) | ||
// Advance position to the next character in input. If current descriptor | ||
// is not empty, append current descriptor to descriptors. Jump to the step | ||
// labeled descriptor parser. | ||
} else if (c === ",") { | ||
pos += 1; | ||
if (currentDescriptor) { | ||
descriptors.push(currentDescriptor); | ||
} | ||
parseDescriptors(); | ||
return; | ||
// U+0028 LEFT PARENTHESIS (() | ||
// Append c to current descriptor. Set state to in parens. | ||
} else if (c === "\u0028") { | ||
currentDescriptor = currentDescriptor + c; | ||
state = "in parens"; | ||
// EOF | ||
// If current descriptor is not empty, append current descriptor to | ||
// descriptors. Jump to the step labeled descriptor parser. | ||
} else if (c === "") { | ||
if (currentDescriptor) { | ||
descriptors.push(currentDescriptor); | ||
} | ||
parseDescriptors(); | ||
return; | ||
// Anything else | ||
// Append c to current descriptor. | ||
} else { | ||
descriptor = srcset; | ||
srcset = ""; | ||
currentDescriptor = currentDescriptor + c; | ||
} | ||
// (end "in descriptor" | ||
// In parens | ||
} else if (state === "in parens") { | ||
// U+0029 RIGHT PARENTHESIS ()) | ||
// Append c to current descriptor. Set state to in descriptor. | ||
if (c === ")") { | ||
currentDescriptor = currentDescriptor + c; | ||
state = "in descriptor"; | ||
// EOF | ||
// Append current descriptor to descriptors. Jump to the step labeled | ||
// descriptor parser. | ||
} else if (c === "") { | ||
descriptors.push(currentDescriptor); | ||
parseDescriptors(); | ||
return; | ||
// Anything else | ||
// Append c to current descriptor. | ||
} else { | ||
currentDescriptor = currentDescriptor + c; | ||
} | ||
// After descriptor | ||
} else if (state === "after descriptor") { | ||
// Do the following, depending on the value of c: | ||
// Space character: Stay in this state. | ||
if (isSpace(c)) { | ||
// EOF: Jump to the step labeled descriptor parser. | ||
} else if (c === "") { | ||
parseDescriptors(); | ||
return; | ||
// Anything else | ||
// Set state to in descriptor. Set position to the previous character in input. | ||
} else { | ||
state = "in descriptor"; | ||
pos -= 1; | ||
} | ||
} | ||
// Advance position to the next character in input. | ||
pos += 1; | ||
// Repeat this step. | ||
} // (close while true loop) | ||
} | ||
// 4. Splitting loop: Collect a sequence of characters that are space | ||
// characters or U+002C COMMA characters. If any U+002C COMMA characters | ||
// were collected, that is a parse error. | ||
while (true) { | ||
collectCharacters(regexLeadingCommasOrSpaces); | ||
// 5. If position is past the end of input, return candidates and abort these steps. | ||
if (pos >= inputLength) { | ||
return candidates; // (we're done, this is the sole return path) | ||
} | ||
// 6. Collect a sequence of characters that are not space characters, | ||
// and let that be url. | ||
url = collectCharacters(regexLeadingNotSpaces); | ||
// 7. Let descriptors be a new empty list. | ||
descriptors = []; | ||
// 8. If url ends with a U+002C COMMA character (,), follow these substeps: | ||
// (1). Remove all trailing U+002C COMMA characters from url. If this removed | ||
// more than one character, that is a parse error. | ||
if (url.slice(-1) === ",") { | ||
url = url.replace(regexTrailingCommas, ""); | ||
// (Jump ahead to step 9 to skip tokenization and just push the candidate). | ||
parseDescriptors(); | ||
// Otherwise, follow these substeps: | ||
} else { | ||
url = srcset; | ||
srcset = ""; | ||
tokenize(); | ||
} // (close else of step 8) | ||
// 16. Return to the step labeled splitting loop. | ||
} // (Close of big while loop.) | ||
} | ||
/* jshint ignore:start */ | ||
// jscs:disable | ||
/* | ||
* Sizes Parser | ||
* | ||
* By Alex Bell | MIT License | ||
* | ||
* Non-strict but accurate and lightweight JS Parser for the string value <img sizes="here"> | ||
* | ||
* Reference algorithm at: | ||
* https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute | ||
* | ||
* Most comments are copied in directly from the spec | ||
* (except for comments in parens). | ||
* | ||
* Grammar is: | ||
* <source-size-list> = <source-size># [ , <source-size-value> ]? | <source-size-value> | ||
* <source-size> = <media-condition> <source-size-value> | ||
* <source-size-value> = <length> | ||
* http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-sizes | ||
* | ||
* E.g. "(max-width: 30em) 100vw, (max-width: 50em) 70vw, 100vw" | ||
* or "(min-width: 30em), calc(30vw - 15px)" or just "30vw" | ||
* | ||
* Returns the first valid <css-length> with a media condition that evaluates to true, | ||
* or "100vw" if all valid media conditions evaluate to false. | ||
* | ||
*/ | ||
function parseSizes(strValue) { | ||
// (Percentage CSS lengths are not allowed in this case, to avoid confusion: | ||
// https://html.spec.whatwg.org/multipage/embedded-content.html#valid-source-size-list | ||
// CSS allows a single optional plus or minus sign: | ||
// http://www.w3.org/TR/CSS2/syndata.html#numbers | ||
// CSS is ASCII case-insensitive: | ||
// http://www.w3.org/TR/CSS2/syndata.html#characters ) | ||
// Spec allows exponential notation for <number> type: | ||
// http://dev.w3.org/csswg/css-values/#numbers | ||
var regexCssLengthWithUnits = /^(?:[+-]?[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?(?:ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmin|vmax|vw)$/i; | ||
// (This is a quick and lenient test. Because of optional unlimited-depth internal | ||
// grouping parens and strict spacing rules, this could get very complicated.) | ||
var regexCssCalc = /^calc\((?:[0-9a-z \.\+\-\*\/\(\)]+)\)$/i; | ||
var i; | ||
var unparsedSizesList; | ||
var unparsedSizesListLength; | ||
var unparsedSize; | ||
var lastComponentValue; | ||
var size; | ||
// UTILITY FUNCTIONS | ||
// (Toy CSS parser. The goals here are: | ||
// 1) expansive test coverage without the weight of a full CSS parser. | ||
// 2) Avoiding regex wherever convenient. | ||
// Quick tests: http://jsfiddle.net/gtntL4gr/3/ | ||
// Returns an array of arrays.) | ||
function parseComponentValues(str) { | ||
var chrctr; | ||
var component = ""; | ||
var componentArray = []; | ||
var listArray = []; | ||
var parenDepth = 0; | ||
var pos = 0; | ||
var inComment = false; | ||
function pushComponent() { | ||
if (component) { | ||
componentArray.push(component); | ||
component = ""; | ||
} | ||
} | ||
// 7. Add url to raw candidates, associated with descriptors. | ||
if ( url || descriptor ) { | ||
candidates.push({ | ||
url: url, | ||
descriptor: descriptor | ||
}); | ||
function pushComponentArray() { | ||
if (componentArray[0]) { | ||
listArray.push(componentArray); | ||
componentArray = []; | ||
} | ||
} | ||
// (Loop forwards from the beginning of the string.) | ||
while (true) { | ||
chrctr = str[pos]; | ||
if (chrctr === undefined) { // ( End of string reached.) | ||
pushComponent(); | ||
pushComponentArray(); | ||
return listArray; | ||
} else if (inComment) { | ||
if ((chrctr === "*") && (str[pos + 1] === "/")) { // (At end of a comment.) | ||
inComment = false; | ||
pos += 2; | ||
pushComponent(); | ||
continue; | ||
} else { | ||
pos += 1; // (Skip all characters inside comments.) | ||
continue; | ||
} | ||
} else if (isSpace(chrctr)) { | ||
// (If previous character in loop was also a space, or if | ||
// at the beginning of the string, do not add space char to | ||
// component.) | ||
if ((str[pos - 1] && isSpace(str[pos - 1])) || (!component)) { | ||
pos += 1; | ||
continue; | ||
} else if (parenDepth === 0) { | ||
pushComponent(); | ||
pos +=1; | ||
continue; | ||
} else { | ||
// (Replace any space character with a plain space for legibility.) | ||
chrctr = " "; | ||
} | ||
} else if (chrctr === "(") { | ||
parenDepth += 1; | ||
} else if (chrctr === ")") { | ||
parenDepth -= 1; | ||
} else if (chrctr === ",") { | ||
pushComponent() | ||
pushComponentArray(); | ||
pos += 1; | ||
continue; | ||
} else if ((chrctr === "/") && (str[pos + 1] === "*")) { | ||
inComment = true; | ||
pos += 2; | ||
continue; | ||
} | ||
component = component + chrctr; | ||
pos += 1; | ||
} | ||
} | ||
return candidates; | ||
}; | ||
pf.parseDescriptor = function( descriptor, sizesattr ) { | ||
// 11. Descriptor parser: Let candidates be an initially empty source set. The order of entries in the list | ||
// is the order in which entries are added to the list. | ||
var sizes = sizesattr || "100vw", | ||
sizeDescriptor = descriptor && descriptor.replace( /(^\s+|\s+$)/g, "" ), | ||
widthInCssPixels = pf.findWidthFromSourceSize( sizes ), | ||
resCandidate; | ||
function isValidNonNegativeSourceSizeValue(s) { | ||
if (regexCssLengthWithUnits.test(s) && (parseFloat(s) >= 0)) {return true;} | ||
if (regexCssCalc.test(s)) {return true;} | ||
// ( http://www.w3.org/TR/CSS2/syndata.html#numbers says: | ||
// "-0 is equivalent to 0 and is not a negative number." which means that | ||
// unitless zero and unitless negative zero must be accepted as special cases.) | ||
if ((s === "0") || (s === "-0") || (s === "+0")) {return true;} | ||
return false; | ||
} | ||
if ( sizeDescriptor ) { | ||
var splitDescriptor = sizeDescriptor.split(" "); | ||
// When asked to parse a sizes attribute from an element, parse a | ||
// comma-separated list of component values from the value of the element's | ||
// sizes attribute (or the empty string, if the attribute is absent), and let | ||
// unparsed sizes list be the result. | ||
// http://dev.w3.org/csswg/css-syntax/#parse-comma-separated-list-of-component-values | ||
for (var i = splitDescriptor.length - 1; i >= 0; i--) { | ||
var curr = splitDescriptor[ i ], | ||
lastchar = curr && curr.slice( curr.length - 1 ); | ||
unparsedSizesList = parseComponentValues(strValue); | ||
unparsedSizesListLength = unparsedSizesList.length; | ||
if ( ( lastchar === "h" || lastchar === "w" ) && !pf.sizesSupported ) { | ||
resCandidate = parseFloat( ( parseInt( curr, 10 ) / widthInCssPixels ) ); | ||
} else if ( lastchar === "x" ) { | ||
var res = curr && parseFloat( curr, 10 ); | ||
resCandidate = res && !isNaN( res ) ? res : 1; | ||
} | ||
} | ||
// For each unparsed size in unparsed sizes list: | ||
for (i = 0; i < unparsedSizesListLength; i++) { | ||
unparsedSize = unparsedSizesList[i]; | ||
// 1. Remove all consecutive <whitespace-token>s from the end of unparsed size. | ||
// ( parseComponentValues() already omits spaces outside of parens. ) | ||
// If unparsed size is now empty, that is a parse error; continue to the next | ||
// iteration of this algorithm. | ||
// ( parseComponentValues() won't push an empty array. ) | ||
// 2. If the last component value in unparsed size is a valid non-negative | ||
// <source-size-value>, let size be its value and remove the component value | ||
// from unparsed size. Any CSS function other than the calc() function is | ||
// invalid. Otherwise, there is a parse error; continue to the next iteration | ||
// of this algorithm. | ||
// http://dev.w3.org/csswg/css-syntax/#parse-component-value | ||
lastComponentValue = unparsedSize[unparsedSize.length - 1]; | ||
if (isValidNonNegativeSourceSizeValue(lastComponentValue)) { | ||
size = lastComponentValue; | ||
unparsedSize.pop(); | ||
} else { | ||
continue; | ||
} | ||
return resCandidate || 1; | ||
}; | ||
// 3. Remove all consecutive <whitespace-token>s from the end of unparsed | ||
// size. If unparsed size is now empty, return size and exit this algorithm. | ||
// If this was not the last item in unparsed sizes list, that is a parse error. | ||
if (unparsedSize.length === 0) { | ||
return size; | ||
} | ||
// 4. Parse the remaining component values in unparsed size as a | ||
// <media-condition>. If it does not parse correctly, or it does parse | ||
// correctly but the <media-condition> evaluates to false, continue to the | ||
// next iteration of this algorithm. | ||
// (Parsing all possible compound media conditions in JS is heavy, complicated, | ||
// and the payoff is unclear. Is there ever an situation where the | ||
// media condition parses incorrectly but still somehow evaluates to true? | ||
// Can we just rely on the browser/polyfill to do it?) | ||
unparsedSize = unparsedSize.join(" "); | ||
if (!(pf.matchesMedia( unparsedSize ) ) ) { | ||
continue; | ||
} | ||
// 5. Return size and exit this algorithm. | ||
return size; | ||
} | ||
// If the above algorithm exhausts unparsed sizes list without returning a | ||
// size value, return 100vw. | ||
return "100vw"; | ||
} | ||
// jscs: enable | ||
/* jshint ignore:end */ | ||
// namespace | ||
pf.ns = ("pf" + new Date().getTime()).substr(0, 9); | ||
// srcset support test | ||
pf.supSrcset = "srcset" in image; | ||
pf.supSizes = "sizes" in image; | ||
// using pf.qsa instead of dom traversing does scale much better, | ||
// especially on sites mixing responsive and non-responsive images | ||
pf.selShort = "picture>img,img[srcset]"; | ||
pf.sel = pf.selShort; | ||
pf.cfg = cfg; | ||
if ( pf.supSrcset ) { | ||
pf.sel += ",img[" + srcsetAttr + "]"; | ||
} | ||
/** | ||
* 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 | ||
* Shortcut property for `devicePixelRatio` ( for easy overriding in tests ) | ||
*/ | ||
pf.getCandidatesFromSourceSet = function( srcset, sizes ) { | ||
var candidates = pf.parseSrcset( srcset ), | ||
formattedCandidates = []; | ||
pf.DPR = (DPR || 1 ); | ||
pf.u = units; | ||
for ( var i = 0, len = candidates.length; i < len; i++ ) { | ||
var candidate = candidates[ i ]; | ||
// container of supported mime types that one might need to qualify before using | ||
pf.types = types; | ||
formattedCandidates.push({ | ||
url: candidate.url, | ||
resolution: pf.parseDescriptor( candidate.descriptor, sizes ) | ||
}); | ||
} | ||
return formattedCandidates; | ||
alwaysCheckWDescriptor = pf.supSrcset && !pf.supSizes; | ||
pf.setSize = noop; | ||
/** | ||
* Gets a string and returns the absolute URL | ||
* @param src | ||
* @returns {String} absolute URL | ||
*/ | ||
pf.makeUrl = memoize(function(src) { | ||
anchor.href = src; | ||
return anchor.href; | ||
}); | ||
/** | ||
* Gets a DOM element or document and a selctor and returns the found matches | ||
* Can be extended with jQuery/Sizzle for IE7 support | ||
* @param context | ||
* @param sel | ||
* @returns {NodeList} | ||
*/ | ||
pf.qsa = function(context, sel) { | ||
return context.querySelectorAll(sel); | ||
}; | ||
/** | ||
* 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 | ||
* Shortcut method for matchMedia ( for easy overriding in tests ) | ||
* wether native or pf.mMQ is used will be decided lazy on first call | ||
* @returns {boolean} | ||
*/ | ||
pf.dodgeSrcset = function( img ) { | ||
if ( img.srcset ) { | ||
img[ pf.ns ].srcset = img.srcset; | ||
img.srcset = ""; | ||
img.setAttribute( "data-pfsrcset", img[ pf.ns ].srcset ); | ||
pf.matchesMedia = function() { | ||
if ( window.matchMedia && (matchMedia( "(min-width: 0.1em)" ) || {}).matches ) { | ||
pf.matchesMedia = function( media ) { | ||
return !media || ( matchMedia( media ).matches ); | ||
}; | ||
} else { | ||
pf.matchesMedia = pf.mMQ; | ||
} | ||
return pf.matchesMedia.apply( this, arguments ); | ||
}; | ||
// 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 = []; | ||
/** | ||
* A simplified matchMedia implementation for IE8 and IE9 | ||
* handles only min-width/max-width with px or em values | ||
* @param media | ||
* @returns {boolean} | ||
*/ | ||
pf.mMQ = function( media ) { | ||
return media ? evalCSS(media) : true; | ||
}; | ||
// 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; | ||
/** | ||
* Returns the calculated length in css pixel from the given sourceSizeValue | ||
* http://dev.w3.org/csswg/css-values-3/#length-value | ||
* intended Spec mismatches: | ||
* * Does not check for invalid use of CSS functions | ||
* * Does handle a computed length of 0 the same as a negative and therefore invalid value | ||
* @param sourceSizeValue | ||
* @returns {Number} | ||
*/ | ||
pf.calcLength = function( sourceSizeValue ) { | ||
var value = evalCSS(sourceSizeValue, true) || false; | ||
if (value < 0) { | ||
value = false; | ||
} | ||
if ( srcset ) { | ||
candidates = pf.getCandidatesFromSourceSet( srcset, sizes ); | ||
return value; | ||
}; | ||
/** | ||
* Takes a type string and checks if its supported | ||
*/ | ||
pf.supportsType = function( type ) { | ||
return ( type ) ? types[ type ] : true; | ||
}; | ||
/** | ||
* Parses a sourceSize into mediaCondition (media) and sourceSizeValue (length) | ||
* @param sourceSizeStr | ||
* @returns {*} | ||
*/ | ||
pf.parseSize = memoize(function( sourceSizeStr ) { | ||
var match = ( sourceSizeStr || "" ).match(regSize); | ||
return { | ||
media: match && match[1], | ||
length: match && match[2] | ||
}; | ||
}); | ||
pf.parseSet = function( set ) { | ||
if ( !set.cands ) { | ||
set.cands = parseSrcset(set.srcset, set); | ||
} | ||
return candidates; | ||
return set.cands; | ||
}; | ||
pf.backfaceVisibilityFix = function( picImg ) { | ||
// See: https://github.com/scottjehl/picturefill/issues/332 | ||
var style = picImg.style || {}, | ||
WebkitBackfaceVisibility = "webkitBackfaceVisibility" in style, | ||
currentZoom = style.zoom; | ||
/** | ||
* returns 1em in css px for html/body default size | ||
* function taken from respondjs | ||
* @returns {*|number} | ||
*/ | ||
pf.getEmValue = function() { | ||
var body; | ||
if ( !eminpx && (body = document.body) ) { | ||
var div = document.createElement( "div" ), | ||
originalHTMLCSS = docElem.style.cssText, | ||
originalBodyCSS = body.style.cssText; | ||
if (WebkitBackfaceVisibility) { | ||
style.zoom = ".999"; | ||
div.style.cssText = baseStyle; | ||
WebkitBackfaceVisibility = picImg.offsetWidth; | ||
// 1em in a media query is the value of the default font size of the browser | ||
// reset docElem and body to ensure the correct value is returned | ||
docElem.style.cssText = fsCss; | ||
body.style.cssText = fsCss; | ||
style.zoom = currentZoom; | ||
body.appendChild( div ); | ||
eminpx = div.offsetWidth; | ||
body.removeChild( div ); | ||
//also update eminpx before returning | ||
eminpx = parseFloat( eminpx, 10 ); | ||
// restore the original values | ||
docElem.style.cssText = originalHTMLCSS; | ||
body.style.cssText = originalBodyCSS; | ||
} | ||
return eminpx || 16; | ||
}; | ||
pf.setIntrinsicSize = (function() { | ||
var urlCache = {}; | ||
var setSize = function( picImg, width, res ) { | ||
if ( width ) { | ||
picImg.setAttribute( "width", parseInt(width / res, 10) ); | ||
} | ||
}; | ||
return function( picImg, bestCandidate ) { | ||
var img; | ||
if ( !picImg[ pf.ns ] || w.pfStopIntrinsicSize ) { | ||
return; | ||
} | ||
if ( picImg[ pf.ns ].dims === undefined ) { | ||
picImg[ pf.ns].dims = picImg.getAttribute("width") || picImg.getAttribute("height"); | ||
} | ||
if ( picImg[ pf.ns].dims ) { return; } | ||
/** | ||
* Takes a string of sizes and returns the width in pixels as a number | ||
*/ | ||
pf.calcListLength = function( sourceSizeListStr ) { | ||
// Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33% | ||
// | ||
// or (min-width:30em) calc(30% - 15px) | ||
if ( !(sourceSizeListStr in sizeLengthCache) || cfg.uT ) { | ||
var winningLength = pf.calcLength( parseSizes( sourceSizeListStr ) ); | ||
if ( bestCandidate.url in urlCache ) { | ||
setSize( picImg, urlCache[bestCandidate.url], bestCandidate.resolution ); | ||
} else { | ||
img = doc.createElement( "img" ); | ||
img.onload = function() { | ||
urlCache[bestCandidate.url] = img.width; | ||
sizeLengthCache[ sourceSizeListStr ] = !winningLength ? units.width : winningLength; | ||
} | ||
//IE 10/11 don't calculate width for svg outside document | ||
if ( !urlCache[bestCandidate.url] ) { | ||
try { | ||
doc.body.appendChild( img ); | ||
urlCache[bestCandidate.url] = img.width || img.offsetWidth; | ||
doc.body.removeChild( img ); | ||
} catch(e){} | ||
} | ||
return sizeLengthCache[ sourceSizeListStr ]; | ||
}; | ||
if ( picImg.src === bestCandidate.url ) { | ||
setSize( picImg, urlCache[bestCandidate.url], bestCandidate.resolution ); | ||
} | ||
picImg = null; | ||
img.onload = null; | ||
img = null; | ||
}; | ||
img.src = bestCandidate.url; | ||
/** | ||
* Takes a candidate object with a srcset property 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, res is calculated | ||
*/ | ||
pf.setRes = function( set ) { | ||
var candidates; | ||
if ( set ) { | ||
candidates = pf.parseSet( set ); | ||
for ( var i = 0, len = candidates.length; i < len; i++ ) { | ||
setResolution( candidates[ i ], set.sizes ); | ||
} | ||
}; | ||
})(); | ||
} | ||
return candidates; | ||
}; | ||
pf.applyBestCandidate = function( candidates, picImg ) { | ||
pf.setRes.res = setResolution; | ||
pf.applySetCandidate = function( candidates, img ) { | ||
if ( !candidates.length ) {return;} | ||
var candidate, | ||
i, | ||
j, | ||
length, | ||
bestCandidate; | ||
bestCandidate, | ||
curSrc, | ||
curCan, | ||
isSameSet, | ||
candidateSrc, | ||
abortCurSrc; | ||
candidates.sort( pf.ascendingSort ); | ||
var imageData = img[ pf.ns ]; | ||
var dpr = pf.DPR; | ||
length = candidates.length; | ||
bestCandidate = candidates[ length - 1 ]; | ||
curSrc = imageData.curSrc || img[curSrcProp]; | ||
for ( var i = 0; i < length; i++ ) { | ||
candidate = candidates[ i ]; | ||
if ( candidate.resolution >= pf.getDpr() ) { | ||
bestCandidate = candidate; | ||
break; | ||
curCan = imageData.curCan || setSrcToCur(img, curSrc, candidates[0].set); | ||
// if we have a current source, we might either become lazy or give this source some advantage | ||
if ( curCan && curCan.set === candidates[ 0 ].set ) { | ||
// if browser can abort image request and the image has a higher pixel density than needed | ||
// and this image isn't downloaded yet, we skip next part and try to save bandwidth | ||
abortCurSrc = (supportAbort && !img.complete && curCan.res - 0.1 > dpr); | ||
if ( !abortCurSrc ) { | ||
curCan.cached = true; | ||
// if current candidate is "best", "better" or "okay", | ||
// set it to bestCandidate | ||
if ( curCan && isSameSet && curCan.res >= dpr ) { | ||
bestCandidate = curCan; | ||
} | ||
} | ||
} | ||
if ( bestCandidate ) { | ||
if ( !bestCandidate ) { | ||
bestCandidate.url = pf.makeUrl( bestCandidate.url ); | ||
candidates.sort( ascendingSort ); | ||
if ( picImg.src !== bestCandidate.url ) { | ||
if ( pf.restrictsMixedContent() && bestCandidate.url.substr(0, "http:".length).toLowerCase() === "http:" ) { | ||
if ( window.console !== undefined ) { | ||
console.warn( "Blocked mixed content image " + bestCandidate.url ); | ||
length = candidates.length; | ||
bestCandidate = candidates[ length - 1 ]; | ||
for ( i = 0; i < length; i++ ) { | ||
candidate = candidates[ i ]; | ||
if ( candidate.res >= dpr ) { | ||
j = i - 1; | ||
// we have found the perfect candidate, | ||
// but let's improve this a little bit with some assumptions ;-) | ||
if (candidates[ j ] && | ||
(abortCurSrc || curSrc !== pf.makeUrl( candidate.url )) && | ||
chooseLowRes(candidates[ j ].res, candidate.res, dpr, candidates[ j ].cached)) { | ||
bestCandidate = candidates[ j ]; | ||
} else { | ||
bestCandidate = candidate; | ||
} | ||
} else { | ||
picImg.src = bestCandidate.url; | ||
// currentSrc attribute and property to match | ||
// http://picture.responsiveimages.org/#the-img-element | ||
if ( !pf.curSrcSupported ) { | ||
picImg.currentSrc = picImg.src; | ||
} | ||
pf.backfaceVisibilityFix( picImg ); | ||
break; | ||
} | ||
} | ||
pf.setIntrinsicSize(picImg, bestCandidate); | ||
} | ||
}; | ||
pf.ascendingSort = function( a, b ) { | ||
return a.resolution - b.resolution; | ||
}; | ||
if ( bestCandidate ) { | ||
/** | ||
* 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 ); | ||
candidateSrc = pf.makeUrl( bestCandidate.url ); | ||
imageData.curSrc = candidateSrc; | ||
imageData.curCan = bestCandidate; | ||
if ( candidateSrc !== curSrc ) { | ||
pf.setSrc( img, bestCandidate ); | ||
} | ||
// Remove the video element once we're finished removing its children | ||
video.parentNode.removeChild( video ); | ||
pf.setSize( img ); | ||
} | ||
}; | ||
/** | ||
* Find all `img` elements, and add them to the candidate list if they have | ||
* a `picture` parent, a `sizes` attribute in basic `srcset` supporting browsers, | ||
* a `srcset` attribute at all, and they haven’t been evaluated already. | ||
*/ | ||
pf.getAllElements = function() { | ||
var elems = [], | ||
imgs = doc.getElementsByTagName( "img" ); | ||
pf.setSrc = function( img, bestCandidate ) { | ||
var origWidth; | ||
img.src = bestCandidate.url; | ||
for ( var h = 0, len = imgs.length; h < len; h++ ) { | ||
var currImg = imgs[ h ]; | ||
// although this is a specific Safari issue, we don't want to take too much different code paths | ||
if ( bestCandidate.set.type === "image/svg+xml" ) { | ||
origWidth = img.style.width; | ||
img.style.width = (img.offsetWidth + 1) + "px"; | ||
if ( currImg.parentNode.nodeName.toUpperCase() === "PICTURE" || | ||
( currImg.getAttribute( "srcset" ) !== null ) || currImg[ pf.ns ] && currImg[ pf.ns ].srcset !== null ) { | ||
elems.push( currImg ); | ||
// next line only should trigger a repaint | ||
// if... is only done to trick dead code removal | ||
if ( img.offsetWidth + 1 ) { | ||
img.style.width = origWidth; | ||
} | ||
} | ||
return elems; | ||
}; | ||
pf.getMatch = function( img, picture ) { | ||
var sources = picture.childNodes, | ||
match; | ||
pf.getSet = function( img ) { | ||
var i, set, supportsType; | ||
var match = false; | ||
var sets = img [ pf.ns ].sets; | ||
// 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 ]; | ||
for ( i = 0; i < sets.length && !match; i++ ) { | ||
set = sets[i]; | ||
// ignore non-element nodes | ||
if ( source.nodeType !== 1 ) { | ||
if ( !set.srcset || !pf.matchesMedia( set.media ) || !(supportsType = pf.supportsType( set.type )) ) { | ||
continue; | ||
} | ||
// Hitting the `img` element that started everything stops the search for `sources`. | ||
// If no previous `source` matches, the `img` itself is evaluated later. | ||
if ( source === img ) { | ||
return match; | ||
if ( supportsType === "pending" ) { | ||
set = supportsType; | ||
} | ||
// ignore non-`source` nodes | ||
if ( source.nodeName.toUpperCase() !== "SOURCE" ) { | ||
continue; | ||
} | ||
// if it's a source element that has the `src` property set, throw a warning in the console | ||
if ( source.getAttribute( "src" ) !== null && typeof console !== undefined ) { | ||
console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`."); | ||
} | ||
match = set; | ||
break; | ||
} | ||
var media = source.getAttribute( "media" ); | ||
return match; | ||
}; | ||
// if source does not have a srcset attribute, skip | ||
if ( !source.getAttribute( "srcset" ) ) { | ||
continue; | ||
} | ||
pf.parseSets = function( element, parent, options ) { | ||
var srcsetAttribute, imageSet, isWDescripor, srcsetParsed; | ||
// if there's no media specified, OR w.matchMedia is supported | ||
if ( ( !media || pf.matchesMedia( media ) ) ) { | ||
var typeSupported = pf.verifyTypeSupport( source ); | ||
var hasPicture = parent && parent.nodeName.toUpperCase() === "PICTURE"; | ||
var imageData = element[ pf.ns ]; | ||
if ( typeSupported === true ) { | ||
match = source; | ||
break; | ||
} else if ( typeSupported === "pending" ) { | ||
return false; | ||
} | ||
if ( imageData.src === undefined || options.src ) { | ||
imageData.src = getImgAttr.call( element, "src" ); | ||
if ( imageData.src ) { | ||
setImgAttr.call( element, srcAttr, imageData.src ); | ||
} else { | ||
removeImgAttr.call( element, srcAttr ); | ||
} | ||
} | ||
return match; | ||
}; | ||
if ( imageData.srcset === undefined || options.srcset || !pf.supSrcset || element.srcset ) { | ||
srcsetAttribute = getImgAttr.call( element, "srcset" ); | ||
imageData.srcset = srcsetAttribute; | ||
srcsetParsed = true; | ||
} | ||
function picturefill( opt ) { | ||
var elements, | ||
element, | ||
parent, | ||
firstMatch, | ||
candidates, | ||
options = opt || {}; | ||
imageData.sets = []; | ||
elements = options.elements || pf.getAllElements(); | ||
if ( hasPicture ) { | ||
imageData.pic = true; | ||
getAllSourceElements( parent, imageData.sets ); | ||
} | ||
// Loop through all elements | ||
for ( var i = 0, plen = elements.length; i < plen; i++ ) { | ||
element = elements[ i ]; | ||
parent = element.parentNode; | ||
firstMatch = undefined; | ||
candidates = undefined; | ||
if ( imageData.srcset ) { | ||
imageSet = { | ||
srcset: imageData.srcset, | ||
sizes: getImgAttr.call( element, "sizes" ) | ||
}; | ||
// immediately skip non-`img` nodes | ||
if ( element.nodeName.toUpperCase() !== "IMG" ) { | ||
continue; | ||
} | ||
imageData.sets.push( imageSet ); | ||
// expando for caching data on the img | ||
if ( !element[ pf.ns ] ) { | ||
element[ pf.ns ] = {}; | ||
} | ||
isWDescripor = (alwaysCheckWDescriptor || imageData.src) && regWDesc.test(imageData.srcset || ""); | ||
// if the element has already been evaluated, skip it unless | ||
// `options.reevaluate` is set to true ( this, for example, | ||
// is set to true when running `picturefill` on `resize` ). | ||
if ( !options.reevaluate && element[ pf.ns ].evaluated ) { | ||
continue; | ||
// add normal src as candidate, if source has no w descriptor | ||
if ( !isWDescripor && imageData.src && !getCandidateForSrc(imageData.src, imageSet) && !imageSet.has1x ) { | ||
imageSet.srcset += ", " + imageData.src; | ||
imageSet.cands.push({ | ||
url: imageData.src, | ||
d: 1, | ||
set: imageSet | ||
}); | ||
} | ||
// if `img` is in a `picture` element | ||
if ( parent && parent.nodeName.toUpperCase() === "PICTURE" ) { | ||
} else if ( imageData.src ) { | ||
imageData.sets.push( { | ||
srcset: imageData.src, | ||
sizes: null | ||
} ); | ||
} | ||
// IE9 video workaround | ||
pf.removeVideoShim( parent ); | ||
imageData.curCan = null; | ||
imageData.curSrc = undefined; | ||
// 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, parent ); | ||
// if img has picture or the srcset was removed or has a srcset and does not support srcset at all | ||
// or has a w descriptor (and does not support sizes) set support to false to evaluate | ||
imageData.supported = !( hasPicture || ( imageSet && !pf.supSrcset ) || isWDescripor ); | ||
// 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; | ||
} | ||
if ( srcsetParsed && pf.supSrcset && !imageData.supported ) { | ||
if ( srcsetAttribute ) { | ||
setImgAttr.call( element, srcsetAttr, srcsetAttribute ); | ||
element.srcset = ""; | ||
} else { | ||
firstMatch = undefined; | ||
removeImgAttr.call( element, srcsetAttr ); | ||
} | ||
} | ||
// Cache and remove `srcset` if present and we’re going to be doing `picture`/`srcset`/`sizes` polyfilling to it. | ||
if ( ( parent && parent.nodeName.toUpperCase() === "PICTURE" ) || | ||
( !pf.sizesSupported && ( element.srcset && regWDesc.test( element.srcset ) ) ) ) { | ||
pf.dodgeSrcset( element ); | ||
if (imageData.supported && !imageData.srcset && ((!imageData.src && element.src) || element.src !== pf.makeUrl(imageData.src))) { | ||
if (imageData.src === null) { | ||
element.removeAttribute("src"); | ||
} else { | ||
element.src = imageData.src; | ||
} | ||
} | ||
if ( firstMatch ) { | ||
candidates = pf.processSourceSet( firstMatch ); | ||
pf.applyBestCandidate( candidates, element ); | ||
} else { | ||
// No sources matched, so we’re down to processing the inner `img` as a source. | ||
candidates = pf.processSourceSet( element ); | ||
imageData.parsed = true; | ||
}; | ||
if ( element.srcset === undefined || element[ pf.ns ].srcset ) { | ||
// Either `srcset` is completely unsupported, or we need to polyfill `sizes` functionality. | ||
pf.applyBestCandidate( candidates, element ); | ||
} // Else, resolution-only `srcset` is supported natively. | ||
} | ||
pf.fillImg = function(element, options) { | ||
var imageData; | ||
var extreme = options.reselect || options.reevaluate; | ||
// set evaluated to true to avoid unnecessary reparsing | ||
element[ pf.ns ].evaluated = true; | ||
// expando for caching data on the img | ||
if ( !element[ pf.ns ] ) { | ||
element[ pf.ns ] = {}; | ||
} | ||
} | ||
/** | ||
* 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() { | ||
pf.initTypeDetects(); | ||
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 | ||
picturefill(); | ||
imageData = element[ pf.ns ]; | ||
if ( /^loaded|^i|^c/.test( doc.readyState ) ) { | ||
clearInterval( intervalId ); | ||
return; | ||
} | ||
}, 250 ); | ||
// if the element has already been evaluated, skip it | ||
// unless `options.reevaluate` is set to true ( this, for example, | ||
// is set to true when running `picturefill` on `resize` ). | ||
if ( !extreme && imageData.evaled === evalId ) { | ||
return; | ||
} | ||
var resizeTimer; | ||
var handleResize = function() { | ||
picturefill({ reevaluate: true }); | ||
}; | ||
function checkResize() { | ||
clearTimeout(resizeTimer); | ||
resizeTimer = setTimeout( handleResize, 60 ); | ||
if ( !imageData.parsed || options.reevaluate ) { | ||
pf.parseSets( element, element.parentNode, options ); | ||
} | ||
if ( w.addEventListener ) { | ||
w.addEventListener( "resize", checkResize, false ); | ||
} else if ( w.attachEvent ) { | ||
w.attachEvent( "onresize", checkResize ); | ||
if ( !imageData.supported ) { | ||
applyBestCandidate( element ); | ||
} else { | ||
imageData.evaled = evalId; | ||
} | ||
}; | ||
pf.setupRun = function() { | ||
if ( !alreadyRun || isVwDirty || (DPR !== window.devicePixelRatio) ) { | ||
updateMetrics(); | ||
} | ||
}; | ||
// If picture is supported, well, that's awesome. | ||
if ( window.HTMLPictureElement ) { | ||
picturefill = noop; | ||
pf.fillImg = noop; | ||
} else { | ||
// Set up picture polyfill by polling the document | ||
(function() { | ||
var isDomReady; | ||
var regReady = window.attachEvent ? /d$|^c/ : /d$|^c|^i/; | ||
var run = function() { | ||
var readyState = document.readyState || ""; | ||
timerId = setTimeout(run, readyState === "loading" ? 200 : 999); | ||
if ( document.body ) { | ||
pf.fillImgs(); | ||
isDomReady = isDomReady || regReady.test(readyState); | ||
if ( isDomReady ) { | ||
clearTimeout( timerId ); | ||
} | ||
} | ||
}; | ||
var timerId = setTimeout(run, document.body ? 9 : 99); | ||
// Also attach picturefill on resize and readystatechange | ||
// http://modernjavascript.blogspot.com/2013/08/building-better-debounce.html | ||
var debounce = function(func, wait) { | ||
var timeout, timestamp; | ||
var later = function() { | ||
var last = (new Date()) - timestamp; | ||
if (last < wait) { | ||
timeout = setTimeout(later, wait - last); | ||
} else { | ||
timeout = null; | ||
func(); | ||
} | ||
}; | ||
return function() { | ||
timestamp = new Date(); | ||
if (!timeout) { | ||
timeout = setTimeout(later, wait); | ||
} | ||
}; | ||
}; | ||
var onResize = function() { | ||
isVwDirty = true; | ||
pf.fillImgs(); | ||
}; | ||
on( window, "resize", debounce(onResize, 99 ) ); | ||
on( document, "readystatechange", run ); | ||
types[ "image/webp" ] = detectTypeSupport("image/webp", "data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==" ); | ||
})(); | ||
} | ||
runPicturefill(); | ||
pf.picturefill = picturefill; | ||
//use this internally for easy monkey patching/performance testing | ||
pf.fillImgs = picturefill; | ||
pf.teardownRun = noop; | ||
@@ -685,4 +1397,33 @@ /* expose methods for testing */ | ||
expose( picturefill ); | ||
window.picturefillCFG = { | ||
pf: pf, | ||
push: function(args) { | ||
var name = args.shift(); | ||
if (typeof pf[name] === "function") { | ||
pf[name].apply(pf, args); | ||
} else { | ||
cfg[name] = args[0]; | ||
if (alreadyRun) { | ||
pf.fillImgs( { reselect: true } ); | ||
} | ||
} | ||
} | ||
}; | ||
} )( window, window.document, new window.Image() ); | ||
while (setOptions && setOptions.length) { | ||
window.picturefillCFG.push(setOptions.shift()); | ||
} | ||
/* expose picturefill */ | ||
window.picturefill = picturefill; | ||
/* expose picturefill */ | ||
if ( typeof module === "object" && typeof module.exports === "object" ) { | ||
// CommonJS, just export | ||
module.exports = picturefill; | ||
} else if ( typeof define === "function" && define.amd ) { | ||
// AMD support | ||
define( "picturefill", function() { return picturefill; } ); | ||
} | ||
} )( window, document ); |
1169
tests/tests.js
(function(window, jQuery) { | ||
if ( window.HTMLPictureElement ){ | ||
test( "Picture is natively supported", function() { | ||
ok( window.HTMLPictureElement ); | ||
ok( window.picturefill ); | ||
}); | ||
return; | ||
// jscs:disable | ||
if(!window.picturefillCFG){ | ||
window.picturefillCFG = []; | ||
} | ||
var pf, originalDprMethod, | ||
originalVideoShimMethod, | ||
originalMatchesMedia, | ||
originalProcessSourceSet, | ||
originalGetWidthFromLength, | ||
originalRestrictsMixedContentMethod; | ||
window.picturefillCFG.push(['uT', true]); | ||
pf = picturefill._; | ||
var startTests = function() { | ||
var op = picturefill._; | ||
// reset stubbing | ||
module( "method", { | ||
setup: function() { | ||
originalDprMethod = pf.getDpr; | ||
originalVideoShimMethod = pf.removeVideoShim; | ||
originalMatchesMedia = pf.matchesMedia; | ||
originalProcessSourceSet = pf.processSourceSet; | ||
originalGetWidthFromLength = pf.getWidthFromLength; | ||
originalrestrictsMixedContentMethod = pf.restrictsMixedContent; | ||
}, | ||
var saveCache = {}; | ||
teardown: function() { | ||
pf.getDpr = originalDprMethod; | ||
pf.removeVideoShim = originalVideoShimMethod; | ||
pf.matchesMedia = originalMatchesMedia; | ||
pf.processSourceSet = originalProcessSourceSet; | ||
pf.restrictsMixedContent = originalrestrictsMixedContentMethod; | ||
} | ||
}); | ||
var forceElementParsing = function( element, options ) { | ||
if ( true || !element[ op.ns ] ) { | ||
element[ op.ns ] = {}; | ||
op.parseSets( element, element.parentNode, options || {} ); | ||
} | ||
}; | ||
test("getWidthFromLength", function() { | ||
var calcTest = (function() { | ||
var gotWidth = pf.getWidthFromLength("calc(766px - 16px)"); | ||
var returnValue = ( gotWidth === 750 || gotWidth === false ); | ||
return returnValue; | ||
}()); | ||
picturefill(); | ||
equal( pf.getWidthFromLength("750px"), 750, "returns int value of width string" ); | ||
ok( calcTest, "If `calc` is supported, `calc(766px - 16px)` returned `750px`. If `calc` is unsupported, the value is `false`."); | ||
equal( pf.getWidthFromLength("calc(160px + 1de)"), false, "calc(160px + 1de)"); | ||
}); | ||
// reset stubbing | ||
test("findWidthFromSourceSize", function() { | ||
var width; | ||
var sizes = " (max-width: 30em) 1000px, (max-width: 50em) 750px, 500px "; | ||
module( "method", { | ||
beforeEach: function() { | ||
var prop; | ||
op.setupRun({reevaluate: true}); | ||
for ( prop in op ) { | ||
if ( op.hasOwnProperty( prop ) ) { | ||
if($.isPlainObject(op[ prop ])){ | ||
saveCache[ prop ] = $.extend(true, {}, op[ prop ]); | ||
} else { | ||
saveCache[ prop ] = op[ prop ]; | ||
} | ||
} | ||
} | ||
}, | ||
pf.matchesMedia = function(media) { | ||
return true; | ||
}; | ||
width = pf.findWidthFromSourceSize(sizes); | ||
equal(width, 1000, "returns 1000 when match media returns true"); | ||
afterEach: function() { | ||
var prop; | ||
for ( prop in saveCache ) { | ||
if ( op.hasOwnProperty(prop) && (prop in saveCache) && saveCache[prop] != op[ prop ] ) { | ||
if($.isPlainObject(op[ prop ]) && $.isPlainObject(saveCache[prop])){ | ||
$.extend(true, op[prop], saveCache[prop]); | ||
} else { | ||
op[prop] = saveCache[prop]; | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
pf.matchesMedia = function(media) { | ||
return false; | ||
}; | ||
width = pf.findWidthFromSourceSize(sizes); | ||
equal(width, 500, "returns 500 when match media returns false"); | ||
test( "picturefill: Picture fill is loaded and has its API ready", function() { | ||
ok( window.picturefill ); | ||
sizes = "100foo, 200px"; | ||
width = pf.findWidthFromSourceSize(sizes); | ||
equal(width, 200, "returns 200 when there was an unknown css length"); | ||
ok( window.picturefill._ ); | ||
sizes = "100foo, sd2300bar"; | ||
width = pf.findWidthFromSourceSize(sizes); | ||
ok( window.picturefill._.fillImg ); | ||
equal(width, Math.max(window.innerWidth || 0, document.documentElement.clientWidth), "returns 100vw when all sizes are an unknown css length"); | ||
}); | ||
ok( window.picturefill._.fillImgs ); | ||
}); | ||
asyncTest("setIntrinsicSize", function() { | ||
var imgInitialHeight = document.createElement( "img" ); | ||
var imgInitialWidth = document.createElement( "img" ); | ||
var imgWithoutDimensions = document.createElement( "img" ); | ||
var candidate = { | ||
url: pf.makeUrl( "../examples/images/small.jpg" ), | ||
resolution: 2 | ||
}; | ||
asyncTest( "picturefill: global integration test", function() { | ||
imgWithoutDimensions.onload = function() { | ||
ok( !imgInitialHeight.getAttribute("width"), "No natural width calculation is performed if a `height` attribute already exists." ); | ||
op.DPR = 1; | ||
equal( imgInitialWidth.width, 10, "No natural width calculation is performed if a `width` attribute already exists." ); | ||
op.calcLength = function() { | ||
return 310; | ||
}; | ||
var countedElements = 0; | ||
var polyfillElements = 10; | ||
var $srcsetImageW = $( "<img />" ) | ||
.attr({ | ||
srcset: "resources/medium.jpg 480w,\n resources/small.jpg 320w" | ||
}) | ||
.prependTo("#qunit-fixture") | ||
; | ||
var $srcsetImageX = $( "<img />" ) | ||
.attr({ | ||
srcset: "resources/oneX.jpg 1x, resources/twoX.jpg 2x" | ||
}) | ||
.prependTo("#qunit-fixture") | ||
; | ||
equal( imgWithoutDimensions.width, 90, "width attribute is set to `naturalWidth / resolution`" ); | ||
start(); | ||
}; | ||
var $normalImg = $(".prop-check"); | ||
imgInitialHeight.src = candidate.url; | ||
imgInitialWidth.src = candidate.url; | ||
imgWithoutDimensions.src = candidate.url; | ||
window.picturefill(); | ||
imgInitialHeight[ pf.ns ] = {}; | ||
imgInitialWidth[ pf.ns ] = {}; | ||
imgWithoutDimensions[ pf.ns ] = {}; | ||
$( "img[srcset], picture > img" ).each( function() { | ||
if ( $(this).prop( op.ns ) ){ | ||
countedElements++; | ||
} | ||
imgInitialHeight.setAttribute( "height", 10 ); | ||
imgInitialWidth.setAttribute( "width", 10 ); | ||
picturefill._.fillImg( this, {} ); | ||
pf.setIntrinsicSize(imgInitialHeight, candidate ); | ||
pf.setIntrinsicSize(imgInitialWidth, candidate ); | ||
pf.setIntrinsicSize(imgWithoutDimensions, candidate ); | ||
if ( $(this).prop( op.ns ) ) { | ||
countedElements++; | ||
} | ||
} ); | ||
}); | ||
if ( window.HTMLPictureElement && op.supSrcset ) { | ||
equal( countedElements, 0, "picturefill is noop in supporting browsers"); | ||
} else if ( !window.HTMLPictureElement && !op.supSrcset ) { | ||
equal( countedElements, polyfillElements * 2, "picturefill finds all elements and polyfills them"); | ||
} | ||
test("parseSize", function() { | ||
var size1 = ""; | ||
var expected1 = { | ||
length: null, | ||
media: null | ||
}; | ||
deepEqual(pf.parseSize(size1), expected1, "Length and Media are empty"); | ||
if ( window.HTMLPictureElement ) { | ||
equal( $("picture > img" ).prop( op.ns ), undefined, "picturefill doesn't touch images in supporting browsers." ); | ||
} else { | ||
var size2 = "( max-width: 50em ) 50%"; | ||
var expected2 = { | ||
length: "50%", | ||
media: "( max-width: 50em )" | ||
}; | ||
deepEqual(pf.parseSize(size2), expected2, "Length and Media are properly parsed"); | ||
ok( $("picture > img" ).prop( op.ns ), "picturefill modifies images in non-supporting browsers." ); | ||
} | ||
var size3 = "(min-width:30em) calc(30% - 15px)"; | ||
var expected3 = { | ||
length: "calc(30% - 15px)", | ||
media: "(min-width:30em)" | ||
}; | ||
deepEqual(pf.parseSize(size3), expected3, "Length and Media are properly parsed"); | ||
}); | ||
if ( window.HTMLPictureElement || op.supSrcset ) { | ||
test("getCandidatesFromSourceSet", function() { | ||
// Basic test | ||
var candidate1 = "images/pic-medium.png"; | ||
var expectedFormattedCandidates1 = [ | ||
{ | ||
resolution: 1, | ||
url: "images/pic-medium.png" | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(candidate1), expectedFormattedCandidates1, "`" + candidate1 + "` is parsed correctly"); | ||
equal( ($srcsetImageX.prop( op.ns ) || { supported: true }).supported, true, "picturefill doesn't touch images in supporting browsers." ); | ||
equal( $srcsetImageX.prop( "src" ), "", "picturefill doesn't touch image src in supporting browsers." ); | ||
equal( imgGet.call( $srcsetImageX[0], "srcset" ), "resources/oneX.jpg 1x, resources/twoX.jpg 2x", "picturefill doesn't touch image srcset in supporting browsers." ); | ||
var candidate1a = "images/pic-medium.png 1x"; | ||
var expectedFormattedCandidates1a = [ | ||
{ | ||
resolution: 1, | ||
url: "images/pic-medium.png" | ||
} else { | ||
ok( $srcsetImageX.prop( op.ns ), "picturefill modifies images in non-supporting browsers." ); | ||
equal( $srcsetImageX.prop( "src" ), op.makeUrl( "resources/oneX.jpg" ), "picturefill changes source of image1" ); | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(candidate1a), expectedFormattedCandidates1a, "`" + candidate1a + "` is parsed correctly" ); | ||
var candidate2 = "images/pic-medium.png, images/pic-medium-2x.png 2x"; | ||
var expectedFormattedCandidates2 = [ | ||
{ | ||
resolution: 1, | ||
url: "images/pic-medium.png" | ||
}, | ||
{ | ||
resolution: 2, | ||
url: "images/pic-medium-2x.png" | ||
if ( window.HTMLPictureElement || (op.supSrcset && op.supSizes) ) { | ||
equal( ($srcsetImageX.prop( op.ns ) || { supported: true }).supported, true, "picturefill doesn't touch images in supporting browsers." ); | ||
equal( $srcsetImageW.prop( "src" ), "", "picturefill doesn't touch image sources in supporting browsers." ); | ||
} else { | ||
ok( $srcsetImageW.prop( op.ns ), "picturefill modifies images in non-supporting browsers." ); | ||
equal( $srcsetImageW.prop( "src" ), op.makeUrl( "resources/small.jpg" ), "picturefill changes source of image" ); | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(candidate2), expectedFormattedCandidates2, "`" + candidate2 + "` is parsed correctly" ); | ||
equal( $normalImg.prop( op.ns ), undefined, "picturefill doesn't touch normal images in any browsers." ); | ||
equal( $normalImg.prop( "src" ), op.makeUrl( "bar" ), "picturefill leaves src attribute of normal images untouched." ); | ||
var candidate2a = "images/pic-medium.png 1x, images/pic-medium-2x.png 2x"; | ||
var expectedFormattedCandidates2a = [ | ||
{ | ||
resolution: 1, | ||
url: "images/pic-medium.png" | ||
}, | ||
{ | ||
resolution: 2, | ||
url: "images/pic-medium-2x.png" | ||
if ( !window.HTMLPictureElement ) { | ||
window.picturefill( { elements: $normalImg } ); | ||
ok( $normalImg.prop( op.ns).supported, "picturefill doesn't touch normal images in any browsers too much even if it is called explicitly." ); | ||
equal( $normalImg.prop( "src" ), op.makeUrl( "bar" ), "picturefill leaves src attribute of normal images untouched." ); | ||
} | ||
]; | ||
op.DPR = 2; | ||
deepEqual(pf.getCandidatesFromSourceSet(candidate2a), expectedFormattedCandidates2a, "`" + candidate2a + "` is parsed correctly"); | ||
op.calcLength = function() { | ||
return 360; | ||
}; | ||
// Test with multiple spaces | ||
var candidate3 = " images/pic-medium.png 1x , images/pic-medium-2x.png 2x "; | ||
deepEqual(pf.getCandidatesFromSourceSet(candidate3), expectedFormattedCandidates2, "`" + candidate3 + "` is parsed correctly" ); | ||
window.picturefill( { reevaluate: true } ); | ||
setTimeout(function(){ | ||
if ( !op.supSizes ) { | ||
window.picturefill( { reevaluate: true } ); | ||
// Test with decimals | ||
var candidate4 = " images/pic-smallest.png 0.25x , images/pic-small.png 0.5x , images/pic-medium.png 1x"; | ||
var expectedFormattedCandidates4 = [ | ||
{ | ||
resolution: 0.25, | ||
url: "images/pic-smallest.png" | ||
}, | ||
{ | ||
resolution: 0.5, | ||
url: "images/pic-small.png" | ||
}, | ||
{ | ||
resolution: 1, | ||
url: "images/pic-medium.png" | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(candidate4), expectedFormattedCandidates4, "`" + candidate4 + "` is parsed correctly" ); | ||
// Test with "sizes" passed with a px length specified | ||
var candidate5 = " images/pic-smallest.png 250w , images/pic-small.png 500w , images/pic-medium.png 1000w"; | ||
var sizes5 = "1000px"; | ||
deepEqual(pf.getCandidatesFromSourceSet(candidate5, sizes5), expectedFormattedCandidates4, "`" + candidate4 + "` is parsed correctly"); | ||
if ( !op.supSrcset ) { | ||
equal( $srcsetImageX.prop( "src" ), op.makeUrl("resources/twoX.jpg"), "picturefill changes source of image" ); | ||
} | ||
equal( $srcsetImageW.prop( "src" ), op.makeUrl( "resources/medium.jpg" ), "picturefill changes source of image" ); | ||
} | ||
start(); | ||
}, 99); | ||
}); | ||
// Test with "sizes" passed with % lengths specified | ||
var candidate6 = "\npic320.png 320w , pic640.png 640w, pic768.png 768w, \ | ||
\npic1536.png 1536w, pic2048.png 2048w "; | ||
var sizes6 = " (max-width: 30em) 100%, (max-width: 50em) 50%, 33%"; | ||
var expectedCandidates = [ | ||
{ | ||
resolution: 0.5, | ||
url: "pic320.png" | ||
}, | ||
{ | ||
resolution: 1, | ||
url: "pic640.png" | ||
}, | ||
{ | ||
resolution: 1.2, | ||
url: "pic768.png" | ||
}, | ||
{ | ||
resolution: 2.4, | ||
url: "pic1536.png" | ||
}, | ||
{ | ||
resolution: 3.2, | ||
url: "pic2048.png" | ||
} | ||
]; | ||
test("parseSets", function() { | ||
//forceElementParsing | ||
var $srcsetImageW = $( "<img />" ) | ||
.attr({ | ||
srcset: "medium.jpg 480w,\n small.jpg 320w", | ||
src: "normalw.jpg" | ||
}) | ||
.prependTo("#qunit-fixture") | ||
; | ||
var $srcsetImageX = $( "<img />" ) | ||
.attr({ | ||
srcset: "twoX.jpg 2x, threeX.png 3x", | ||
src: "normalx.jpg" | ||
}) | ||
.prependTo("#qunit-fixture") | ||
; | ||
var $source = $( document.createElement( "source" ) ) | ||
.attr({ | ||
srcset: "twoX.jpg 2x, threeX.png 3x", | ||
media: "(min-width: 800px)" | ||
}); | ||
var $pictureSet = $( "<picture />" ) | ||
.append( $source ) | ||
.append("<img src='normal.jpg' />") | ||
.prependTo("#qunit-fixture") | ||
; | ||
pf.getWidthFromLength = function(width) { | ||
return 640; | ||
}; | ||
$.each([ | ||
{ | ||
name: "srcset with w descriptor + additional src", | ||
elem: $srcsetImageW, | ||
sets: 1, | ||
candidates: [ 2 ] | ||
}, | ||
{ | ||
name: "picture srcset with x descriptor + additional src", | ||
elem: $srcsetImageX, | ||
sets: 1, | ||
candidates: [ 3 ] | ||
}, | ||
{ | ||
name: "picture srcset with x descriptor + additional src", | ||
elem: $pictureSet.find( "img" ), | ||
sets: 2, | ||
candidates: [ 2, 1 ] | ||
} | ||
], function(i, testData) { | ||
pf.matchesMedia = function(media) { | ||
return true; | ||
}; | ||
forceElementParsing( testData.elem[0] ); | ||
var sets = testData.elem.prop( op.ns ).sets; | ||
equal( sets.length, testData.sets, "parseSets parses right amount of sets. " + testData.name ); | ||
deepEqual(pf.getCandidatesFromSourceSet(candidate6, sizes6), expectedCandidates, "`" + candidate6 + "` is parsed correctly" ); | ||
$.each( sets, function( i, set ) { | ||
op.parseSet( set ); | ||
equal( set.cands.length, testData.candidates[ i ], "parseSets parses right amount of candidates inside a set. " + testData.name ); | ||
} ); | ||
var srcset1 = "foo,bar.png 320w, bar,baz.png 320w"; | ||
var expectedresult1 = [ | ||
{ | ||
url: "foo,bar.png", | ||
resolution: 0.5 | ||
},{ | ||
url: "bar,baz.png", | ||
resolution: 0.5 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset1), expectedresult1, "`" + srcset1 + "` is parsed correctly" ); | ||
}); | ||
}); | ||
var srcset2 = "foo,bar.png 320w,bar,baz.png 320w"; | ||
var expectedresult2 = [ | ||
{ | ||
url: "foo,bar.png", | ||
resolution: 0.5 | ||
},{ | ||
url: "bar,baz.png", | ||
resolution: 0.5 | ||
} | ||
]; | ||
test("calcLength", function() { | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset2), expectedresult2, "`" + srcset2 + "` is parsed correctly" ); | ||
op.u.em = 16; | ||
op.getEmValue = function() { | ||
return 16; | ||
}; | ||
var srcset3 = "foo.png 1x, bar.png -2x"; | ||
var expectedresult3 = [ | ||
{ | ||
url: "foo.png", | ||
resolution: 1 | ||
},{ | ||
url: "bar.png", | ||
resolution: -2 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset3), expectedresult3, "`" + srcset3 + "` is parsed correctly" ); | ||
op.u.vw = 2; | ||
var srcset4 = "foo.png 1x, bar.png 2q"; | ||
var expectedresult4 = [ | ||
{ | ||
url: "foo.png", | ||
resolution: 1 | ||
},{ | ||
url: "bar.png", | ||
resolution: 1 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset4), expectedresult4, "`" + srcset4 + "` is parsed correctly" ); | ||
equal( op.calcLength("750px"), 750, "returns int value of width string" ); | ||
equal( op.calcLength("calc(766px - 1em)"), 750, "calc(766px - 1em) returned `750px`. If `calc` is unsupported, the value was discarded and defaulted to `100vw`."); | ||
equal( op.calcLength("calc(160px / 1em * 1vw)"), 20, "calc(160px / 1em * 1vw)"); | ||
equal( op.calcLength("calc(160px + 1em)"), 176, "calc(160px + 1em)"); | ||
equal( op.calcLength("calc(160px + 1de)"), false, "calc(160px + 1de)"); | ||
}); | ||
var srcset5 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg 1x, bar.png 2x"; | ||
var expectedresult5 = [ | ||
{ | ||
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg", | ||
resolution: 1 | ||
},{ | ||
url: "bar.png", | ||
resolution: 2 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset5), expectedresult5, "`" + srcset5 + "` is parsed correctly" ); | ||
test("calcListLength", function() { | ||
var width; | ||
var invalidSizes = "(min-width: 1px) 1002pysa, (min-width: 2px) -20px"; | ||
var sizes = " (max-width: 30em) 1000px, (max-width: 50em) 750px, 500px "; | ||
var srcset6 = "2.png 1x,1.png 2x"; | ||
var expectedresult6 = [ | ||
{ | ||
url: "2.png", | ||
resolution: 1 | ||
},{ | ||
url: "1.png", | ||
resolution: 2 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset6), expectedresult6, "`" + srcset6 + "` is parsed correctly" ); | ||
op.matchesMedia = function(media) { | ||
return true; | ||
}; | ||
var srcset7 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg 2x, 1x.gif 1x, data:image/png;base64,iVBORw0KGgoAAAANSUhEUg"; | ||
var expectedresult7 = [ | ||
{ | ||
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg", | ||
resolution: 2 | ||
},{ | ||
url: "1x.gif", | ||
resolution: 1 | ||
},{ | ||
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg", | ||
resolution: 1 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset7), expectedresult7, "`" + srcset7 + "` is parsed correctly" ); | ||
width = op.calcListLength(sizes); | ||
var srcset8 = "400.gif 400w, 6000.gif 6000w"; | ||
var expectedresult8 = [ | ||
{ | ||
url: "400.gif", | ||
resolution: 0.625 | ||
},{ | ||
url: "6000.gif", | ||
resolution: 9.375 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset8), expectedresult8, "`" + srcset8 + "` is parsed correctly" ); | ||
equal(width, 1000, "returns 1000 when match media returns true"); | ||
var srcset9 = "800.gif 2x, 1600.gif 1600w"; | ||
var expectedresult9 = [ | ||
{ | ||
url: "800.gif", | ||
resolution: 2 | ||
},{ | ||
url: "1600.gif", | ||
resolution: 2.5 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset9), expectedresult9, "`" + srcset9 + "` is parsed correctly" ); | ||
var srcset10 = "1x,, , x ,2x , 1x.gif, , 3x, 4x.gif 4x 100h,,, 5x.gif 5, dx.gif dx, 2x.gif 2x,"; | ||
var expectedresult10 = [ | ||
{ | ||
url: "1x", | ||
resolution: 1 | ||
},{ | ||
url: "x", | ||
resolution: 1 | ||
},{ | ||
url: "2x", | ||
resolution: 1 | ||
},{ | ||
url: "1x.gif", | ||
resolution: 1 | ||
},{ | ||
url: "3x", | ||
resolution: 1 | ||
},{ | ||
url: "4x.gif", | ||
resolution: 4 | ||
},{ | ||
url: "5x.gif", | ||
resolution: 1 | ||
},{ | ||
url: "dx.gif", | ||
resolution: 1 | ||
},{ | ||
url: "2x.gif", | ||
resolution: 2 | ||
} | ||
]; | ||
deepEqual(pf.getCandidatesFromSourceSet(srcset10), expectedresult10, "`" + srcset10 + "` is parsed correctly" ); | ||
}); | ||
width = op.calcListLength(invalidSizes + ", (min-width: 2px) 10px"); | ||
equal(width, 10, "iterates through until finds valid value"); | ||
test("verifyTypeSupport", function() { | ||
expect( 6 ); | ||
width = op.calcListLength(invalidSizes); | ||
equal(width, op.u.width, "if no valid size is given defaults to viewport width"); | ||
// Test widely supported mime types. | ||
ok(pf.verifyTypeSupport({ | ||
getAttribute: function() { | ||
return "image/jpeg"; | ||
} | ||
})); | ||
op.matchesMedia = function(media) { | ||
return !media || false; | ||
}; | ||
ok(pf.verifyTypeSupport({ | ||
getAttribute: function() { | ||
return "image/png"; | ||
} | ||
})); | ||
width = op.calcListLength(sizes); | ||
equal(width, 500, "returns 500 when match media returns false"); | ||
ok(pf.verifyTypeSupport({ | ||
getAttribute: function() { | ||
return "image/gif"; | ||
} | ||
})); | ||
op.matchesMedia = function(media) { | ||
return !media || media == "(max-width: 50em)"; | ||
}; | ||
width = op.calcListLength(sizes); | ||
equal(width, 750, "returns 750px when match media returns true on (max-width: 50em)"); | ||
}); | ||
// if the type attribute is supported it should return true | ||
ok(pf.verifyTypeSupport({ | ||
getAttribute: function() { | ||
return ""; | ||
} | ||
})); | ||
test("parseSize", function() { | ||
var size1 = ""; | ||
var expected1 = { | ||
length: null, | ||
media: null | ||
}; | ||
deepEqual(op.parseSize(size1), expected1, "Length and Media are empty"); | ||
// if the type attribute is supported it should return true | ||
ok(pf.verifyTypeSupport({ | ||
getAttribute: function() { | ||
return null; | ||
} | ||
})); | ||
var size2 = "( max-width: 50em ) 50%"; | ||
var expected2 = { | ||
length: "50%", | ||
media: "( max-width: 50em )" | ||
}; | ||
deepEqual(op.parseSize(size2), expected2, "Length and Media are properly parsed"); | ||
pf.verifyTypeSupport({ | ||
getAttribute: function() { | ||
return "foo"; | ||
} | ||
var size3 = "(min-width:30em) calc(30% - 15px)"; | ||
var expected3 = { | ||
length: "calc(30% - 15px)", | ||
media: "(min-width:30em)" | ||
}; | ||
deepEqual(op.parseSize(size3), expected3, "Length and Media are properly parsed"); | ||
}); | ||
pf.types[ "bar" ] = "baz"; | ||
test("setRes", function() { | ||
var srcset, expected, sizes; | ||
// Basic test | ||
var runGetCandidate = function(candidate, sizes) { | ||
return $.map(op.setRes( { srcset: candidate, sizes: sizes || null } ), function( can ) { | ||
return { | ||
res: can.res, | ||
url: can.url | ||
}; | ||
}); | ||
}; | ||
equal( "pending", pf.verifyTypeSupport({ | ||
getAttribute: function() { | ||
return "bar"; | ||
} | ||
})); | ||
}); | ||
srcset = "images/pic-medium.png"; | ||
expected = [ | ||
{ | ||
res: 1, | ||
url: "images/pic-medium.png" | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly"); | ||
test("applyBestCandidate", function() { | ||
var image, candidates; | ||
srcset = "images/pic-medium.png 1x"; | ||
expected = [ | ||
{ | ||
res: 1, | ||
url: "images/pic-medium.png" | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
candidates = [ | ||
{ resolution: 100, url: "data:100" }, | ||
{ resolution: 200, url: "data:200" }, | ||
{ resolution: 300, url: "data:300" } | ||
]; | ||
srcset = "images/pic-medium.png, images/pic-medium-2x.png 2x"; | ||
expected = [ | ||
{ | ||
res: 1, | ||
url: "images/pic-medium.png" | ||
}, | ||
{ | ||
res: 2, | ||
url: "images/pic-medium-2x.png" | ||
} | ||
]; | ||
image = { | ||
src: "not one of the urls" | ||
}; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
pf.getDpr = function() { | ||
return 300; | ||
}; | ||
srcset = "images/pic-medium.png 1x, images/pic-medium-2x.png 2x"; | ||
expected = [ | ||
{ | ||
res: 1, | ||
url: "images/pic-medium.png" | ||
}, | ||
{ | ||
res: 2, | ||
url: "images/pic-medium-2x.png" | ||
} | ||
]; | ||
pf.applyBestCandidate( candidates, image ); | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly"); | ||
deepEqual(image.src, candidates[2].url, "uses the url from the best px fit" ); | ||
if ( !pf.curSrcSupported ) { | ||
deepEqual(image.currentSrc, candidates[2].url, "uses the url from the best px fit" ); | ||
} | ||
// Test with multiple spaces | ||
srcset = " images/pic-medium.png 1x , images/pic-medium-2x.png 2x "; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
image.src = "data:300"; | ||
image.currentSrc = "data:300"; | ||
// Test with decimals | ||
srcset = " images/pic-smallest.png 0.25x , images/pic-small.png 0.5x , images/pic-medium.png 1x"; | ||
expected = [ | ||
{ | ||
res: 0.25, | ||
url: "images/pic-smallest.png" | ||
}, | ||
{ | ||
res: 0.5, | ||
url: "images/pic-small.png" | ||
}, | ||
{ | ||
res: 1, | ||
url: "images/pic-medium.png" | ||
} | ||
]; | ||
//deepEqual(runGetCandidate(srcset), expectedFormattedCandidates4, "`" + srcset + "` is parsed correctly" ); | ||
pf.applyBestCandidate( candidates, image ); | ||
// Test with "sizes" passed with a px length specified | ||
srcset = " images/pic-smallest.png 250w , images/pic-small.png 500w , images/pic-medium.png 1000w"; | ||
sizes = "1000px"; | ||
deepEqual(runGetCandidate(srcset, sizes), expected, "`" + srcset + "` is parsed correctly"); | ||
deepEqual(image.src, "data:300", "src left alone when matched" ); | ||
if ( !pf.curSrcSupported ) { | ||
deepEqual(image.currentSrc, "data:300", "currentSrc left alone when matched" ); | ||
} | ||
}); | ||
// Test with "sizes" passed with % lengths specified | ||
srcset = "\npic320.png 320w , pic640.png 640w, pic768.png 768w, \ | ||
\npic1536.png 1536w, pic2048.png 2048w "; | ||
sizes = " (max-width: 30em) 100%, (max-width: 50em) 50%, 33%"; | ||
expected = [ | ||
{ | ||
res: 0.5, | ||
url: "pic320.png" | ||
}, | ||
{ | ||
res: 1, | ||
url: "pic640.png" | ||
}, | ||
{ | ||
res: 1.2, | ||
url: "pic768.png" | ||
}, | ||
{ | ||
res: 2.4, | ||
url: "pic1536.png" | ||
}, | ||
{ | ||
res: 3.2, | ||
url: "pic2048.png" | ||
} | ||
]; | ||
test( "removeVideoShim", function() { | ||
var $videoShim = $( ".video-shim" ); | ||
op.calcLength = function() { | ||
return 640; | ||
}; | ||
equal( $videoShim.find( "video" ).length, 1 ); | ||
equal( $videoShim.find( "source" ).length, 2 ); | ||
op.matchesMedia = function() { | ||
return true; | ||
}; | ||
pf.removeVideoShim( $videoShim[0] ); | ||
deepEqual(runGetCandidate(srcset, sizes), expected, "`" + srcset + "` is parsed correctly" ); | ||
equal( $videoShim.find( "video" ).length, 0 ); | ||
equal( $videoShim.find( "source" ).length, 2 ); | ||
}); | ||
srcset = "foo,bar.png 320w, bar,baz.png 320w, "; | ||
expected = [ | ||
{ | ||
url: "foo,bar.png", | ||
res: 0.5 | ||
},{ | ||
url: "bar,baz.png", | ||
res: 0.5 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
test("getMatch returns the first matching `source`", function() { | ||
var firstsource = $( ".first-match" )[ 0 ].parentNode.getElementsByTagName( "source" )[ 0 ]; | ||
srcset = "foo,bar.png 320w,bar,baz.png 320w"; | ||
expected = [ | ||
{ | ||
url: "foo,bar.png", | ||
res: 0.5 | ||
},{ | ||
url: "bar,baz.png", | ||
res: 0.5 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
equal( pf.getMatch( $( ".first-match" )[ 0 ], $( ".first-match" )[ 0 ].parentNode ), firstsource ); | ||
}); | ||
srcset = "foo.png 1x, bar.png -2x"; | ||
expected = [ | ||
{ | ||
url: "foo.png", | ||
res: 1 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
test("Each `img` should then check if its parent is `picture`, then loop through `source` elements until finding the `img` that triggered the loop.", function() { | ||
var match = $( ".match" )[ 0 ], | ||
match2 = $( ".match-second" )[ 0 ], | ||
firstSource = match.parentNode.getElementsByTagName( "source" )[ 0 ]; | ||
srcset = "foo.png 1x, bar.png 2q"; | ||
expected = [ | ||
{ | ||
url: "foo.png", | ||
res: 1 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
ok( pf.getMatch( match, match.parentNode ) === undefined && pf.getMatch( match2, match2.parentNode ) === firstSource ); | ||
}); | ||
srcset = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg 1x, bar.png 2x"; | ||
expected = [ | ||
{ | ||
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg", | ||
res: 1 | ||
},{ | ||
url: "bar.png", | ||
res: 2 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
test( "getMatch returns false when a source type is pending", function() { | ||
pf.types["foo"] = function() {}; | ||
srcset = "2.png 1x,1.png 2x"; | ||
expected = [ | ||
{ | ||
url: "2.png", | ||
res: 1 | ||
},{ | ||
url: "1.png", | ||
res: 2 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
equal( pf.getMatch($(".pending-check")[0], $(".pending-check")[0].parentNode ), false, "pending type should be false" ); | ||
}); | ||
srcset = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg 2x, 1x.gif 1x, data:image/png;base64,iVBORw0KGgoAAAANSUhEUg"; | ||
expected = [ | ||
{ | ||
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg", | ||
res: 2 | ||
},{ | ||
url: "1x.gif", | ||
res: 1 | ||
},{ | ||
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg", | ||
res: 1 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
test( "getMatch returns source when it matches the media", function() { | ||
var $match = $( ".match-check "); | ||
pf.matchesMedia = function() { | ||
return true; | ||
}; | ||
srcset = "400.gif 400w, 6000.gif 6000w"; | ||
expected = [ | ||
{ | ||
url: "400.gif", | ||
res: 0.625 | ||
},{ | ||
url: "6000.gif", | ||
res: 9.375 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
equal( pf.getMatch( $match[0], $match[0].parentNode ), $match[0].parentNode.getElementsByTagName( "source" )[0] ); | ||
}); | ||
srcset = "1200.gif 2x, 1600.gif 1600w, 800.jpg 800w 400h,800a.jpg 400h 800w"; | ||
expected = [ | ||
{ | ||
url: "1200.gif", | ||
res: 2 | ||
},{ | ||
url: "1600.gif", | ||
res: 2.5 | ||
},{ | ||
url: "800.jpg", | ||
res: 1.25 | ||
},{ | ||
url: "800a.jpg", | ||
res: 1.25 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
test( "getMatch returns undefined when no match is found", function() { | ||
pf.matchesMedia = function() { | ||
return false; | ||
}; | ||
srcset = "1x,, , x ,2x , 1x.gif, , 3x, 4x.gif 4x 100w,,, 5x.gif 5, dx.gif dx, 2x.gif 2x,"; | ||
expected = [ | ||
{ | ||
url: "1x", | ||
res: 1 | ||
},{ | ||
url: "x", | ||
res: 1 | ||
},{ | ||
url: "2x", | ||
res: 1 | ||
},{ | ||
url: "1x.gif", | ||
res: 1 | ||
},{ | ||
url: "3x", | ||
res: 1 | ||
},{ | ||
url: "2x.gif", | ||
res: 2 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
var $noMatch = $( ".no-match-check "); | ||
srcset = ",,,,foo.png 1x, ,,,,,bar 2x, , ,bar2 3x"; | ||
expected = [ | ||
{ | ||
url: "foo.png", | ||
res: 1 | ||
}, | ||
{ | ||
url: "bar", | ||
res: 2 | ||
}, | ||
{ | ||
url: "bar2", //why not ,bar2? | ||
res: 3 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
equal( pf.getMatch( $noMatch[0], $noMatch[0].parentNode ), undefined ); | ||
}); | ||
op.calcLength = function() { | ||
return 100; | ||
}; | ||
test( "getMatch returns undefined when no srcset is found", function() { | ||
var $noSrcset = $( ".no-srcset-check "); | ||
/* | ||
srcset = "foo.png 2e2w, bar.jpg 1e2w"; | ||
expected = [ | ||
{ | ||
url: "foo.png", | ||
res: 2 | ||
}, | ||
{ | ||
url: "bar.jpg", | ||
res: 1 | ||
} | ||
]; | ||
deepEqual(runGetCandidate(srcset), expected, "`" + srcset + "` is parsed correctly" ); | ||
*/ | ||
}); | ||
equal( pf.getMatch( $noSrcset[0], $noSrcset[0].parentNode ), undefined ); | ||
}); | ||
test( "op.mMQ", function() { | ||
op.u.width = 480; | ||
op.u.em = 2; | ||
op.getEmValue = function() { | ||
return 2; | ||
}; | ||
test( "getMatch returns only sources preceding fallback img", function() { | ||
var $ignoredSource = $( ".ignored-source-check" ); | ||
ok( op.mMQ( "(min-width: 480px)" ) ); | ||
ok( !op.mMQ( "(min-width: 481px)" ) ); | ||
ok( op.mMQ( "(min-width: 479px)" ) ); | ||
// ensure the construction of the fixture | ||
equal($ignoredSource[0].nodeName, "IMG" ); | ||
equal($ignoredSource.next()[0].nodeName, "SOURCE" ); | ||
ok( op.mMQ( "(max-width: 480px)" ) ); | ||
ok( op.mMQ( "(max-width: 481px)" ) ); | ||
ok( !op.mMQ( "(max-width: 479px)" ) ); | ||
// ensure that the result is undefined so the picture is grabbed later | ||
equal( pf.getMatch( $ignoredSource[0], $ignoredSource[0].parentNode ), undefined, "no source found" ); | ||
}); | ||
ok( !op.mMQ( "(orientation: landscape)" ) ); | ||
test( "picturefill ignores elements when they are marked with a property", function() { | ||
expect( 0 ); | ||
ok( op.mMQ( "(min-width: 240em)" ) ); | ||
ok( !op.mMQ( "(min-width: 241em)" ) ); | ||
ok( op.mMQ( "(min-width: 239em)" ) ); | ||
var mockPicture = { | ||
nodeName: "PICTURE" | ||
}; | ||
ok( op.mMQ( "(max-width: 240em)" ) ); | ||
ok( op.mMQ( "(max-width: 241em)" ) ); | ||
ok( !op.mMQ( "(max-width: 239em)" ) ); | ||
mockPicture[ pf.ns ] = { | ||
evaluated: true | ||
}; | ||
ok( !op.mMQ( "(min-width: 240ups)" ) ); | ||
pf.removeVideoShim = function() { | ||
ok( false ); | ||
}; | ||
} ); | ||
picturefill({ reevaluate: false, elements: [ mockPicture ] }); | ||
}); | ||
test("supportsType", function() { | ||
expect( 5 ); | ||
test( "picturefill marks elements with a property", function() { | ||
// NOTE requires at least one child image for the propery to be set | ||
var mockPicture = $( ".prop-check" )[0]; | ||
// Test widely supported mime types. | ||
ok( op.supportsType( "image/jpeg" ) ); | ||
// make sure there are candidates to consider | ||
pf.processSourceSet = function() { | ||
return [ { url: "foo" } ]; | ||
}; | ||
ok( op.supportsType( "image/png" ) ); | ||
picturefill({ reevaluate: false, elements: [ mockPicture ] }); | ||
ok( op.supportsType( "image/gif" ) ); | ||
ok( mockPicture[ pf.ns ].evaluated ); | ||
}); | ||
// if the type attribute is supported it should return true | ||
ok( op.supportsType( "" ) ); | ||
test( "`img` with `sizes` but no `srcset` shouldn’t fail silently", function() { | ||
expect( 0 ); | ||
var el = document.createElement( "img" ); | ||
// if the type attribute is supported it should return true | ||
ok( op.supportsType( null ) ); | ||
}); | ||
el.setAttribute( "sizes", "100vw" ); | ||
el.setAttribute( "class", "no-src" ); | ||
(document.body || document.documentElement).appendChild( el ); | ||
test("applySetCandidate", function() { | ||
var image, candidates; | ||
try { picturefill({ reevaluate: false, elements: document.querySelector( ".no-src" ) }); } catch (e) { console.log( e ); ok( false ); } | ||
var fullPath = op.makeUrl("foo300"); | ||
el.parentNode.removeChild( el ); | ||
}); | ||
candidates = [ | ||
{ res: 100, url: "foo100", set: {}, desc: {} }, | ||
{ res: 200, url: "foo200", set: {}, desc: {} }, | ||
{ res: 300, url: "foo300", set: {}, desc: {} } | ||
]; | ||
test( "Mixed content should be blocked", function() { | ||
pf.restrictsMixedContent = function() { | ||
return true; | ||
}; | ||
var image, candidates; | ||
image = document.createElement("img"); | ||
candidates = [ | ||
{ resolution: 1, url: "http://example.org/bar" }, | ||
]; | ||
image [op.ns ] = {}; | ||
image = { | ||
src: "foo" | ||
}; | ||
op.DPR = 300; | ||
pf.applyBestCandidate( candidates, image ); | ||
op.applySetCandidate( candidates, image ); | ||
equal( image.src, "foo" ); | ||
equal(op.makeUrl( image.src ), op.makeUrl( candidates[2].url ), "uses the url from the best px fit" ); | ||
}); | ||
image.src = fullPath; | ||
image [op.ns ].curSrc = fullPath; | ||
test( "`img` can be added outside the DOM without errors", function() { | ||
var img = document.createElement( "img" ); | ||
op.applySetCandidate( candidates, image ); | ||
img.setAttribute( "srcset", "data:img 500w" ); | ||
deepEqual(image.src, fullPath, "src left alone when matched" ); | ||
picturefill( { elements: [ img ] } ); | ||
}); | ||
assert.equal( img.src || img.currentSrc, "data:img" ); | ||
}); | ||
test("getSet returns the first matching `source`", function() { | ||
var img = $( ".first-match" )[ 0 ]; | ||
var firstsource = img.parentNode.getElementsByTagName( "source" )[ 0 ]; | ||
forceElementParsing( img ); | ||
equal( op.getSet( img ).srcset, firstsource.getAttribute( "srcset" ) ); | ||
}); | ||
test( "getSet returns 'pending' when a source type is pending", function() { | ||
var img = $(".pending-check")[0]; | ||
op.types["foo"] = "pending"; | ||
forceElementParsing( img ); | ||
equal( op.getSet( img ), "pending", "pending type should be false" ); | ||
}); | ||
test( "getSet returns source when it matches the media", function() { | ||
var img = $( ".match-check ")[ 0 ]; | ||
var $source = $(img).closest( "picture").find('source'); | ||
op.matchesMedia = function() { | ||
return true; | ||
}; | ||
forceElementParsing( img ); | ||
//IE11 in IE9 mode | ||
if($source.length){ | ||
equal( op.getSet( img ).srcset, $source.attr( "srcset" ) ); | ||
} else { | ||
ok(true); | ||
} | ||
}); | ||
test( "getMatch returns false when no match is found", function() { | ||
op.matchesMedia = function( media ) { | ||
return !media || false; | ||
}; | ||
var img = $( ".no-match-check ")[0]; | ||
forceElementParsing( img ); | ||
equal( op.getSet( img ), false ); | ||
}); | ||
test( "getSet returns false when no srcset is found", function() { | ||
var img = $( ".no-srcset-check ")[0]; | ||
forceElementParsing( img ); | ||
equal( op.getSet( img ), false ); | ||
}); | ||
test( "picturefill marks elements with a property", function() { | ||
// NOTE requires at least one child image for the propery to be set | ||
var mockPicture = $( ".prop-check" )[0]; | ||
// make sure there are candidates to consider | ||
op.processSourceSet = function() { | ||
return [ { url: "foo" } ]; | ||
}; | ||
picturefill({ reevaluate: false, elements: [ mockPicture ] }); | ||
if ( !window.HTMLPictureElement ) { | ||
ok( mockPicture[ op.ns ].evaled ); | ||
} else { | ||
ok( !mockPicture[ op.ns ] ); | ||
} | ||
}); | ||
test( "`img` with `sizes` but no `srcset` shouldn’t fail silently", function() { | ||
expect( 0 ); | ||
var el = document.createElement( "img" ); | ||
el.setAttribute( "sizes", "100vw" ); | ||
el.setAttribute( "class", "no-src" ); | ||
jQuery( "#qunit-fixture" ).append( el ); | ||
try { picturefill({ reevaluate: false, elements: jQuery( ".no-src" ) }); } catch (e) { console.log( e ); ok( false ); } | ||
}); | ||
asyncTest( "`img` can be added outside the DOM without errors", function() { | ||
var runTest, timer; | ||
var img = document.createElement( "img" ); | ||
var runTest = function(){ | ||
equal( img.currentSrc || img.src, "data:img" ); | ||
start(); | ||
img.onload = null; | ||
img.onerror = null; | ||
clearTimeout(timer); | ||
}; | ||
timer = setTimeout(runTest, 99); | ||
img.onload = runTest; | ||
img.onerror = runTest; | ||
img.setAttribute( "srcset", "data:img 500w" ); | ||
picturefill( { elements: [ img ] } ); | ||
}); | ||
}; | ||
if( window.blanket ) { | ||
blanket.beforeStartTestRunner({ | ||
callback: function() { | ||
setTimeout(startTests, QUnit.urlParams.coverage ? 500 : 0); //if blanketjs fails set a higher timeout | ||
} | ||
}); | ||
} else { | ||
startTests(); | ||
} | ||
})( window, jQuery ); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
High entropy strings
Supply chain riskContains high entropy strings. This could be a sign of encrypted data, leaked secrets or obfuscated code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
11599399
4.64%83
66%17328
49.35%13
30%2
Infinity%4
Infinity%2
100%