mirror of
https://github.com/danbee/danbarber.me
synced 2025-03-04 08:59:10 +00:00
Add responsive images.
This commit is contained in:
parent
aca1e6b632
commit
8202524ed1
2
Gemfile
2
Gemfile
@ -5,6 +5,8 @@ gem 'rake'
|
||||
gem 'jekyll'
|
||||
gem 'jekyll-assets'
|
||||
|
||||
gem 'mini_magick'
|
||||
|
||||
gem 'octopress', '~> 3.0.0.rc.14'
|
||||
gem 'octopress-deploy', '~> 1.0.0.rc.11'
|
||||
|
||||
|
||||
@ -57,6 +57,8 @@ GEM
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
mercenary (0.3.4)
|
||||
mini_magick (3.8.1)
|
||||
subexec (~> 0.2.1)
|
||||
multi_json (1.10.1)
|
||||
octopress (3.0.0.rc.14)
|
||||
jekyll (~> 2.0)
|
||||
@ -89,6 +91,7 @@ GEM
|
||||
sprockets-sass (1.2.0)
|
||||
sprockets (~> 2.0)
|
||||
tilt (~> 1.1)
|
||||
subexec (0.2.3)
|
||||
thor (0.19.1)
|
||||
tilt (1.4.1)
|
||||
timers (4.0.1)
|
||||
@ -110,6 +113,7 @@ DEPENDENCIES
|
||||
jekyll
|
||||
jekyll-assets
|
||||
kramdown (~> 1.3.3)
|
||||
mini_magick
|
||||
octopress (~> 3.0.0.rc.14)
|
||||
octopress-deploy (~> 1.0.0.rc.11)
|
||||
rake
|
||||
|
||||
BIN
_assets/images/pages/sale/DSCF1694.jpg
Normal file
BIN
_assets/images/pages/sale/DSCF1694.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
633
_assets/javascripts/lib/picturefill.js
Normal file
633
_assets/javascripts/lib/picturefill.js
Normal file
@ -0,0 +1,633 @@
|
||||
/*! Picturefill - v2.1.0 - 2014-08-20
|
||||
* http://scottjehl.github.io/picturefill
|
||||
* Copyright (c) 2014 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 */
|
||||
|
||||
window.matchMedia || (window.matchMedia = function() {
|
||||
"use strict";
|
||||
|
||||
// For browsers that support matchMedium api such as IE 9 and webkit
|
||||
var styleMedia = (window.styleMedia || window.media);
|
||||
|
||||
// For those that don't support matchMedium
|
||||
if (!styleMedia) {
|
||||
var style = document.createElement('style'),
|
||||
script = document.getElementsByTagName('script')[0],
|
||||
info = null;
|
||||
|
||||
style.type = 'text/css';
|
||||
style.id = 'matchmediajs-test';
|
||||
|
||||
script.parentNode.insertBefore(style, script);
|
||||
|
||||
// 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers
|
||||
info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Test if media query is true or false
|
||||
return info.width === '1px';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 ) {
|
||||
// Enable strict mode
|
||||
"use strict";
|
||||
|
||||
// If picture is supported, well, that's awesome. Let's get outta here...
|
||||
if ( w.HTMLPictureElement ) {
|
||||
w.picturefill = function() { };
|
||||
return;
|
||||
}
|
||||
|
||||
// HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround)
|
||||
doc.createElement( "picture" );
|
||||
|
||||
// local object for method references and testing exposure
|
||||
var pf = {};
|
||||
|
||||
// namespace
|
||||
pf.ns = "picturefill";
|
||||
|
||||
// srcset support test
|
||||
pf.srcsetSupported = "srcset" in doc.createElement( "img" );
|
||||
pf.sizesSupported = w.HTMLImageElement.sizes;
|
||||
|
||||
// just a string trim workaround
|
||||
pf.trim = function( str ) {
|
||||
return str.trim ? str.trim() : str.replace( /^\s+|\s+$/g, "" );
|
||||
};
|
||||
|
||||
// just a string endsWith workaround
|
||||
pf.endsWith = function( str, suffix ) {
|
||||
return str.endsWith ? str.endsWith( suffix ) : str.indexOf( suffix, str.length - suffix.length ) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shortcut method for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests )
|
||||
*/
|
||||
pf.restrictsMixedContent = function() {
|
||||
return w.location.protocol === "https:";
|
||||
};
|
||||
/**
|
||||
* Shortcut method for matchMedia ( for easy overriding in tests )
|
||||
*/
|
||||
pf.matchesMedia = function( media ) {
|
||||
return w.matchMedia && w.matchMedia( media ).matches;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shortcut method for `devicePixelRatio` ( for easy overriding in tests )
|
||||
*/
|
||||
pf.getDpr = function() {
|
||||
return ( w.devicePixelRatio || 1 );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get width in css pixel value from a "length" value
|
||||
* http://dev.w3.org/csswg/css-values-3/#length-value
|
||||
*/
|
||||
pf.getWidthFromLength = function( length ) {
|
||||
// If a length is specified and doesn’t contain a percentage, and it is greater than 0 or using `calc`, use it. Else, use the `100vw` default.
|
||||
length = length && length.indexOf( "%" ) > -1 === false && ( parseFloat( length ) > 0 || length.indexOf( "calc(" ) > -1 ) ? length : "100vw";
|
||||
/**
|
||||
* If length is specified in `vw` units, use `%` instead since the div we’re measuring
|
||||
* is injected at the top of the document.
|
||||
*
|
||||
* TODO: maybe we should put this behind a feature test for `vw`?
|
||||
*/
|
||||
length = length.replace( "vw", "%" );
|
||||
|
||||
// Create a cached element for getting length value widths
|
||||
if ( !pf.lengthEl ) {
|
||||
pf.lengthEl = doc.createElement( "div" );
|
||||
doc.documentElement.insertBefore( pf.lengthEl, doc.documentElement.firstChild );
|
||||
}
|
||||
|
||||
// Positioning styles help prevent padding/margin/width on `html` from throwing calculations off.
|
||||
pf.lengthEl.style.cssText = "position: absolute; left: 0; width: " + length + ";";
|
||||
|
||||
if ( pf.lengthEl.offsetWidth <= 0 ) {
|
||||
// Something has gone wrong. `calc()` is in use and unsupported, most likely. Default to `100vw` (`100%`, for broader support.):
|
||||
pf.lengthEl.style.cssText = "width: 100%;";
|
||||
}
|
||||
|
||||
return pf.lengthEl.offsetWidth;
|
||||
};
|
||||
|
||||
// container of supported mime types that one might need to qualify before using
|
||||
pf.types = {};
|
||||
|
||||
// Add support for standard mime types.
|
||||
pf.types["image/jpeg"] = true;
|
||||
pf.types["image/gif"] = true;
|
||||
pf.types["image/png"] = true;
|
||||
|
||||
// test svg support
|
||||
pf.types[ "image/svg+xml" ] = doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1");
|
||||
|
||||
// test webp support, only when the markup calls for it
|
||||
pf.types[ "image/webp" ] = function() {
|
||||
// based on Modernizr's lossless img-webp test
|
||||
// note: asynchronous
|
||||
var img = new w.Image(),
|
||||
type = "image/webp";
|
||||
|
||||
img.onerror = function() {
|
||||
pf.types[ type ] = false;
|
||||
picturefill();
|
||||
};
|
||||
img.onload = function() {
|
||||
pf.types[ type ] = img.width === 1;
|
||||
picturefill();
|
||||
};
|
||||
img.src = "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=";
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a source element and checks if its type attribute is present and if so, supported
|
||||
* Note: for type tests that require a async logic,
|
||||
* you can define them as a function that'll run only if that type needs to be tested. Just make the test function call picturefill again when it is complete.
|
||||
* see the async webp test above for example
|
||||
*/
|
||||
pf.verifyTypeSupport = function( source ) {
|
||||
var type = source.getAttribute( "type" );
|
||||
// if type attribute exists, return test result, otherwise return true
|
||||
if ( type === null || type === "" ) {
|
||||
return true;
|
||||
} else {
|
||||
// if the type test is a function, run it and return "pending" status. The function will rerun picturefill on pending elements once finished.
|
||||
if ( typeof( pf.types[ type ] ) === "function" ) {
|
||||
pf.types[ type ]();
|
||||
return "pending";
|
||||
} else {
|
||||
return pf.types[ type ];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses an individual `size` and returns the length, and optional media query
|
||||
*/
|
||||
pf.parseSize = function( sourceSizeStr ) {
|
||||
var match = /(\([^)]+\))?\s*(.+)/g.exec( sourceSizeStr );
|
||||
return {
|
||||
media: match && match[1],
|
||||
length: match && match[2]
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a string of sizes and returns the width in pixels as a number
|
||||
*/
|
||||
pf.findWidthFromSourceSize = function( sourceSizeListStr ) {
|
||||
// Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33%
|
||||
// or (min-width:30em) calc(30% - 15px)
|
||||
var sourceSizeList = pf.trim( sourceSizeListStr ).split( /\s*,\s*/ ),
|
||||
winningLength;
|
||||
|
||||
for ( var i = 0, len = sourceSizeList.length; i < len; i++ ) {
|
||||
// Match <media-condition>? length, ie ( min-width: 50em ) 100%
|
||||
var sourceSize = sourceSizeList[ i ],
|
||||
// Split "( min-width: 50em ) 100%" into separate strings
|
||||
parsedSize = pf.parseSize( sourceSize ),
|
||||
length = parsedSize.length,
|
||||
media = parsedSize.media;
|
||||
|
||||
if ( !length ) {
|
||||
continue;
|
||||
}
|
||||
if ( !media || pf.matchesMedia( media ) ) {
|
||||
// if there is no media query or it matches, choose this as our winning length
|
||||
// and end algorithm
|
||||
winningLength = length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// pass the length to a method that can properly determine length
|
||||
// in pixels based on these formats: http://dev.w3.org/csswg/css-values-3/#length-value
|
||||
return pf.getWidthFromLength( winningLength );
|
||||
};
|
||||
|
||||
pf.parseSrcset = function( srcset ) {
|
||||
/**
|
||||
* 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 = [];
|
||||
|
||||
while ( srcset !== "" ) {
|
||||
srcset = srcset.replace(/^\s+/g,"");
|
||||
|
||||
// 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;
|
||||
|
||||
if ( pos !== -1 ) {
|
||||
url = srcset.slice( 0, pos );
|
||||
|
||||
var last = url[ url.length - 1 ];
|
||||
|
||||
// 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 ( last === "," || url === "" ) {
|
||||
url = url.replace(/,+$/, "");
|
||||
descriptor = "";
|
||||
}
|
||||
srcset = srcset.slice( pos + 1 );
|
||||
|
||||
// 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 );
|
||||
} else {
|
||||
descriptor = srcset;
|
||||
srcset = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
url = srcset;
|
||||
srcset = "";
|
||||
}
|
||||
|
||||
// 7. Add url to raw candidates, associated with descriptors.
|
||||
if ( url || descriptor ) {
|
||||
candidates.push({
|
||||
url: url,
|
||||
descriptor: descriptor
|
||||
});
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
if ( sizeDescriptor ) {
|
||||
var splitDescriptor = sizeDescriptor.split(" ");
|
||||
|
||||
for (var i = splitDescriptor.length + 1; i >= 0; i--) {
|
||||
if ( splitDescriptor[ i ] !== undefined ) {
|
||||
var curr = splitDescriptor[ i ],
|
||||
lastchar = curr && curr.slice( curr.length - 1 );
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resCandidate || 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a srcset in the form of url/
|
||||
* ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or
|
||||
* "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or
|
||||
* "images/pic-small.png"
|
||||
* Get an array of image candidates in the form of
|
||||
* {url: "/foo/bar.png", resolution: 1}
|
||||
* where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value
|
||||
* If sizes is specified, resolution is calculated
|
||||
*/
|
||||
pf.getCandidatesFromSourceSet = function( srcset, sizes ) {
|
||||
var candidates = pf.parseSrcset( srcset ),
|
||||
formattedCandidates = [];
|
||||
|
||||
for ( var i = 0, len = candidates.length; i < len; i++ ) {
|
||||
var candidate = candidates[ i ];
|
||||
|
||||
formattedCandidates.push({
|
||||
url: candidate.url,
|
||||
resolution: pf.parseDescriptor( candidate.descriptor, sizes )
|
||||
});
|
||||
}
|
||||
return formattedCandidates;
|
||||
};
|
||||
|
||||
/*
|
||||
* if it's an img element and it has a srcset property,
|
||||
* we need to remove the attribute so we can manipulate src
|
||||
* (the property's existence infers native srcset support, and a srcset-supporting browser will prioritize srcset's value over our winning picture candidate)
|
||||
* this moves srcset's value to memory for later use and removes the attr
|
||||
*/
|
||||
pf.dodgeSrcset = function( img ) {
|
||||
if ( img.srcset ) {
|
||||
img[ pf.ns ].srcset = img.srcset;
|
||||
img.removeAttribute( "srcset" );
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Accept a source or img element and process its srcset and sizes attrs
|
||||
*/
|
||||
pf.processSourceSet = function( el ) {
|
||||
var srcset = el.getAttribute( "srcset" ),
|
||||
sizes = el.getAttribute( "sizes" ),
|
||||
candidates = [];
|
||||
|
||||
// if it's an img element, use the cached srcset property (defined or not)
|
||||
if ( el.nodeName.toUpperCase() === "IMG" && el[ pf.ns ] && el[ pf.ns ].srcset ) {
|
||||
srcset = el[ pf.ns ].srcset;
|
||||
}
|
||||
|
||||
if ( srcset ) {
|
||||
candidates = pf.getCandidatesFromSourceSet( srcset, sizes );
|
||||
}
|
||||
return candidates;
|
||||
};
|
||||
|
||||
pf.applyBestCandidate = function( candidates, picImg ) {
|
||||
var candidate,
|
||||
length,
|
||||
bestCandidate;
|
||||
|
||||
candidates.sort( pf.ascendingSort );
|
||||
|
||||
length = candidates.length;
|
||||
bestCandidate = candidates[ length - 1 ];
|
||||
|
||||
for ( var i = 0; i < length; i++ ) {
|
||||
candidate = candidates[ i ];
|
||||
if ( candidate.resolution >= pf.getDpr() ) {
|
||||
bestCandidate = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( bestCandidate && !pf.endsWith( picImg.src, bestCandidate.url ) ) {
|
||||
if ( pf.restrictsMixedContent() && bestCandidate.url.substr(0, "http:".length).toLowerCase() === "http:" ) {
|
||||
if ( typeof console !== undefined ) {
|
||||
console.warn( "Blocked mixed content image " + bestCandidate.url );
|
||||
}
|
||||
} else {
|
||||
picImg.src = bestCandidate.url;
|
||||
// currentSrc attribute and property to match
|
||||
// http://picture.responsiveimages.org/#the-img-element
|
||||
picImg.currentSrc = picImg.src;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pf.ascendingSort = function( a, b ) {
|
||||
return a.resolution - b.resolution;
|
||||
};
|
||||
|
||||
/*
|
||||
* In IE9, <source> elements get removed if they aren't children of
|
||||
* video elements. Thus, we conditionally wrap source elements
|
||||
* using <!--[if IE 9]><video style="display: none;"><![endif]-->
|
||||
* and must account for that here by moving those source elements
|
||||
* back into the picture element.
|
||||
*/
|
||||
pf.removeVideoShim = function( picture ) {
|
||||
var videos = picture.getElementsByTagName( "video" );
|
||||
if ( videos.length ) {
|
||||
var video = videos[ 0 ],
|
||||
vsources = video.getElementsByTagName( "source" );
|
||||
while ( vsources.length ) {
|
||||
picture.insertBefore( vsources[ 0 ], video );
|
||||
}
|
||||
// Remove the video element once we're finished removing its children
|
||||
video.parentNode.removeChild( video );
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Find all `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" );
|
||||
|
||||
for ( var h = 0, len = imgs.length; h < len; h++ ) {
|
||||
var currImg = imgs[ h ];
|
||||
|
||||
if ( currImg.parentNode.nodeName.toUpperCase() === "PICTURE" ||
|
||||
( currImg.getAttribute( "srcset" ) !== null ) || currImg[ pf.ns ] && currImg[ pf.ns ].srcset !== null ) {
|
||||
elems.push( currImg );
|
||||
}
|
||||
}
|
||||
return elems;
|
||||
};
|
||||
|
||||
pf.getMatch = function( img, picture ) {
|
||||
var sources = picture.childNodes,
|
||||
match;
|
||||
|
||||
// Go through each child, and if they have media queries, evaluate them
|
||||
for ( var j = 0, slen = sources.length; j < slen; j++ ) {
|
||||
var source = sources[ j ];
|
||||
|
||||
// ignore non-element nodes
|
||||
if ( source.nodeType !== 1 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Hitting 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;
|
||||
}
|
||||
|
||||
// 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`.");
|
||||
}
|
||||
|
||||
var media = source.getAttribute( "media" );
|
||||
|
||||
// if source does not have a srcset attribute, skip
|
||||
if ( !source.getAttribute( "srcset" ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if there's no media specified, OR w.matchMedia is supported
|
||||
if ( ( !media || pf.matchesMedia( media ) ) ) {
|
||||
var typeSupported = pf.verifyTypeSupport( source );
|
||||
|
||||
if ( typeSupported === true ) {
|
||||
match = source;
|
||||
break;
|
||||
} else if ( typeSupported === "pending" ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
};
|
||||
|
||||
function picturefill( opt ) {
|
||||
var elements,
|
||||
element,
|
||||
parent,
|
||||
firstMatch,
|
||||
candidates,
|
||||
|
||||
options = opt || {};
|
||||
elements = options.elements || pf.getAllElements();
|
||||
|
||||
// Loop through all elements
|
||||
for ( var i = 0, plen = elements.length; i < plen; i++ ) {
|
||||
element = elements[ i ];
|
||||
parent = element.parentNode;
|
||||
firstMatch = undefined;
|
||||
candidates = undefined;
|
||||
|
||||
// expando for caching data on the img
|
||||
if ( !element[ pf.ns ] ) {
|
||||
element[ pf.ns ] = {};
|
||||
}
|
||||
|
||||
// if the element has already been evaluated, skip it
|
||||
// unless `options.force` is set to true ( this, for example,
|
||||
// is set to true when running `picturefill` on `resize` ).
|
||||
if ( !options.reevaluate && element[ pf.ns ].evaluated ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if `img` is in a `picture` element
|
||||
if ( parent.nodeName.toUpperCase() === "PICTURE" ) {
|
||||
|
||||
// IE9 video workaround
|
||||
pf.removeVideoShim( parent );
|
||||
|
||||
// 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 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;
|
||||
}
|
||||
} else {
|
||||
firstMatch = undefined;
|
||||
}
|
||||
|
||||
// Cache and remove `srcset` if present and we’re going to be doing `picture`/`srcset`/`sizes` polyfilling to it.
|
||||
if ( parent.nodeName.toUpperCase() === "PICTURE" ||
|
||||
( element.srcset && !pf.srcsetSupported ) ||
|
||||
( !pf.sizesSupported && ( element.srcset && element.srcset.indexOf("w") > -1 ) ) ) {
|
||||
pf.dodgeSrcset( element );
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
// set evaluated to true to avoid unnecessary reparsing
|
||||
element[ pf.ns ].evaluated = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up picture polyfill by polling the document and running
|
||||
* the polyfill every 250ms until the document is ready.
|
||||
* Also attaches picturefill on resize
|
||||
*/
|
||||
function runPicturefill() {
|
||||
picturefill();
|
||||
var intervalId = setInterval( function() {
|
||||
// When the document has finished loading, stop checking for new images
|
||||
// https://github.com/ded/domready/blob/master/ready.js#L15
|
||||
picturefill();
|
||||
if ( /^loaded|^i|^c/.test( doc.readyState ) ) {
|
||||
clearInterval( intervalId );
|
||||
return;
|
||||
}
|
||||
}, 250 );
|
||||
if ( w.addEventListener ) {
|
||||
var resizeThrottle;
|
||||
w.addEventListener( "resize", function() {
|
||||
if (!w._picturefillWorking) {
|
||||
w._picturefillWorking = true;
|
||||
w.clearTimeout( resizeThrottle );
|
||||
resizeThrottle = w.setTimeout( function() {
|
||||
picturefill({ reevaluate: true });
|
||||
w._picturefillWorking = false;
|
||||
}, 60 );
|
||||
}
|
||||
}, false );
|
||||
}
|
||||
}
|
||||
|
||||
runPicturefill();
|
||||
|
||||
/* expose methods for testing */
|
||||
picturefill._ = pf;
|
||||
|
||||
/* expose picturefill */
|
||||
if ( typeof module === "object" && typeof module.exports === "object" ) {
|
||||
// CommonJS, just export
|
||||
module.exports = picturefill;
|
||||
} else if ( typeof define === "function" && define.amd ){
|
||||
// AMD support
|
||||
define( function() { return picturefill; } );
|
||||
} else if ( typeof w === "object" ) {
|
||||
// If no AMD and we are in the browser, attach to window
|
||||
w.picturefill = picturefill;
|
||||
}
|
||||
|
||||
} )( this, this.document );
|
||||
1
_assets/javascripts/main.js
Normal file
1
_assets/javascripts/main.js
Normal file
@ -0,0 +1 @@
|
||||
//= require lib/picturefill
|
||||
21
_config.yml
21
_config.yml
@ -26,15 +26,20 @@ assets:
|
||||
- _assets/images
|
||||
- _assets/fonts
|
||||
- _assets/icons
|
||||
js_compressor: uglifier
|
||||
css_compressor: sass
|
||||
|
||||
redcarpet:
|
||||
extensions:
|
||||
- hard_wrap
|
||||
- no_intra_emphasis
|
||||
- autolink
|
||||
- strikethrough
|
||||
- fenced_code_blocks
|
||||
- smart
|
||||
picture:
|
||||
source: "_assets/images"
|
||||
output: "pictures"
|
||||
markup: "picture"
|
||||
presets:
|
||||
default:
|
||||
ppi: [1, 2]
|
||||
attr:
|
||||
itemprop: "image"
|
||||
source_default:
|
||||
width: "800"
|
||||
|
||||
kramdown:
|
||||
input: GFM
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
<!-- iOS favicons -->
|
||||
<link href="{{ 'favicon.ico' | asset_path }}" rel="shortcut icon">
|
||||
{{ 'styles' | stylesheet }}
|
||||
{{ 'main' | javascript }}
|
||||
<!-- 144x144 for iPad 3rd and 4th generation -->
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="{{ 'apple-touch-icon-144x144.png' | asset_path }}">
|
||||
<!-- 114x114 for iPhone 4, 4S, 5 and 2012 iPod Touch -->
|
||||
|
||||
238
_plugins/picture_tag.rb
Normal file
238
_plugins/picture_tag.rb
Normal file
@ -0,0 +1,238 @@
|
||||
# Title: Jekyll Picture Tag
|
||||
# Authors: Rob Wierzbowski : @robwierzbowski
|
||||
# Justin Reese : @justinxreese
|
||||
# Welch Canavan : @xiwcx
|
||||
#
|
||||
# Description: Easy responsive images for Jekyll.
|
||||
#
|
||||
# Download: https://github.com/robwierzbowski/jekyll-picture-tag
|
||||
# Documentation: https://github.com/robwierzbowski/jekyll-picture-tag/readme.md
|
||||
# Issues: https://github.com/robwierzbowski/jekyll-picture-tag/issues
|
||||
#
|
||||
# Syntax: {% picture [preset] path/to/img.jpg [source_key: path/to/alt-img.jpg] [attr="value"] %}
|
||||
# Example: {% picture poster.jpg alt="The strange case of responsive images" %}
|
||||
# {% picture gallery poster.jpg source_small: poster_closeup.jpg
|
||||
# alt="The strange case of responsive images" class="gal-img" data-selected %}
|
||||
#
|
||||
# See the documentation for full configuration and usage instructions.
|
||||
|
||||
require 'fileutils'
|
||||
require 'pathname'
|
||||
require 'digest/md5'
|
||||
require 'mini_magick'
|
||||
|
||||
module Jekyll
|
||||
|
||||
class Picture < Liquid::Tag
|
||||
|
||||
def initialize(tag_name, markup, tokens)
|
||||
@markup = markup
|
||||
super
|
||||
end
|
||||
|
||||
def render(context)
|
||||
|
||||
# Render any liquid variables in tag arguments and unescape template code
|
||||
render_markup = Liquid::Template.parse(@markup).render(context).gsub(/\\\{\\\{|\\\{\\%/, '\{\{' => '{{', '\{\%' => '{%')
|
||||
|
||||
# Gather settings
|
||||
site = context.registers[:site]
|
||||
settings = site.config['picture']
|
||||
markup = /^(?:(?<preset>[^\s.:\/]+)\s+)?(?<image_src>[^\s]+\.[a-zA-Z0-9]{3,4})\s*(?<source_src>(?:(source_[^\s.:\/]+:\s+[^\s]+\.[a-zA-Z0-9]{3,4})\s*)+)?(?<html_attr>[\s\S]+)?$/.match(render_markup)
|
||||
preset = settings['presets'][ markup[:preset] ] || settings['presets']['default']
|
||||
|
||||
raise "Picture Tag can't read this tag. Try {% picture [preset] path/to/img.jpg [source_key: path/to/alt-img.jpg] [attr=\"value\"] %}." unless markup
|
||||
|
||||
# Assign defaults
|
||||
settings['source'] ||= '.'
|
||||
settings['output'] ||= 'generated'
|
||||
settings['markup'] ||= 'picturefill'
|
||||
|
||||
# Prevent Jekyll from erasing our generated files
|
||||
site.config['keep_files'] << settings['output'] unless site.config['keep_files'].include?(settings['output'])
|
||||
|
||||
# Deep copy preset for single instance manipulation
|
||||
instance = Marshal.load(Marshal.dump(preset))
|
||||
|
||||
# Process alternate source images
|
||||
source_src = if markup[:source_src]
|
||||
Hash[ *markup[:source_src].gsub(/:/, '').split ]
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
||||
# Process html attributes
|
||||
html_attr = if markup[:html_attr]
|
||||
Hash[ *markup[:html_attr].scan(/(?<attr>[^\s="]+)(?:="(?<value>[^"]+)")?\s?/).flatten ]
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
||||
if instance['attr']
|
||||
html_attr = instance.delete('attr').merge(html_attr)
|
||||
end
|
||||
|
||||
if settings['markup'] == 'picturefill'
|
||||
html_attr['data-picture'] = nil
|
||||
html_attr['data-alt'] = html_attr.delete('alt')
|
||||
end
|
||||
|
||||
html_attr_string = html_attr.inject('') { |string, attrs|
|
||||
if attrs[1]
|
||||
string << "#{attrs[0]}=\"#{attrs[1]}\" "
|
||||
else
|
||||
string << "#{attrs[0]} "
|
||||
end
|
||||
}
|
||||
|
||||
# Prepare ppi variables
|
||||
ppi = if instance['ppi'] then instance.delete('ppi').sort.reverse else nil end
|
||||
# this might work??? ppi = instance.delete('ppi'){ |ppi| [nil] }.sort.reverse
|
||||
ppi_sources = {}
|
||||
|
||||
# Switch width and height keys to the symbols that generate_image() expects
|
||||
instance.each { |key, source|
|
||||
raise "Preset #{key} is missing a width or a height" if !source['width'] and !source['height']
|
||||
instance[key][:width] = instance[key].delete('width') if source['width']
|
||||
instance[key][:height] = instance[key].delete('height') if source['height']
|
||||
}
|
||||
|
||||
# Store keys in an array for ordering the instance sources
|
||||
source_keys = instance.keys
|
||||
# used to escape markdown parsing rendering below
|
||||
markdown_escape = "\ "
|
||||
|
||||
# Raise some exceptions before we start expensive processing
|
||||
raise "Picture Tag can't find the \"#{markup[:preset]}\" preset. Check picture: presets in _config.yml for a list of presets." unless preset
|
||||
raise "Picture Tag can't find this preset source. Check picture: presets: #{markup[:preset]} in _config.yml for a list of sources." unless (source_src.keys - source_keys).empty?
|
||||
|
||||
# Process instance
|
||||
# Add image paths for each source
|
||||
instance.each_key { |key|
|
||||
instance[key][:src] = source_src[key] || markup[:image_src]
|
||||
}
|
||||
|
||||
# Construct ppi sources
|
||||
# Generates -webkit-device-ratio and resolution: dpi media value for cross browser support
|
||||
# Reference: http://www.brettjankord.com/2012/11/28/cross-browser-retinahigh-resolution-media-queries/
|
||||
if ppi
|
||||
instance.each { |key, source|
|
||||
ppi.each { |p|
|
||||
if p != 1
|
||||
ppi_key = "#{key}-x#{p}"
|
||||
|
||||
ppi_sources[ppi_key] = {
|
||||
:width => if source[:width] then (source[:width].to_f * p).round else nil end,
|
||||
:height => if source[:height] then (source[:height].to_f * p).round else nil end,
|
||||
'media' => if source['media']
|
||||
"#{source['media']} and (-webkit-min-device-pixel-ratio: #{p}), #{source['media']} and (min-resolution: #{(p * 96).round}dpi)"
|
||||
else
|
||||
"(-webkit-min-device-pixel-ratio: #{p}), (min-resolution: #{(p * 96).to_i}dpi)"
|
||||
end,
|
||||
:src => source[:src]
|
||||
}
|
||||
|
||||
# Add ppi_key to the source keys order
|
||||
source_keys.insert(source_keys.index(key), ppi_key)
|
||||
end
|
||||
}
|
||||
}
|
||||
instance.merge!(ppi_sources)
|
||||
end
|
||||
|
||||
# Generate resized images
|
||||
instance.each { |key, source|
|
||||
instance[key][:generated_src] = generate_image(source, site.source, site.dest, settings['source'], settings['output'], site.config["baseurl"])
|
||||
}
|
||||
|
||||
# Construct and return tag
|
||||
if settings['markup'] == 'picture'
|
||||
|
||||
source_tags = ''
|
||||
source_keys.each { |source|
|
||||
media = " media=\"#{instance[source]['media']}\"" unless source == 'source_default'
|
||||
source_tags += "#{markdown_escape * 4}<source srcset=\"#{instance[source][:generated_src]}\"#{media}>\n"
|
||||
}
|
||||
|
||||
# Note: we can't indent html output because markdown parsers will turn 4 spaces into code blocks
|
||||
# Note: Added backslash+space escapes to bypass markdown parsing of indented code below -WD
|
||||
picture_tag = "<picture>\n"\
|
||||
"#{source_tags}"\
|
||||
"#{markdown_escape * 4}<img srcset=\"#{instance['source_default'][:generated_src]}\" #{html_attr_string}>\n"\
|
||||
"#{markdown_escape * 2}</picture>\n"
|
||||
|
||||
elsif settings['markup'] == 'img'
|
||||
# TODO implement <img srcset/sizes>
|
||||
end
|
||||
|
||||
# Return the markup!
|
||||
picture_tag
|
||||
end
|
||||
|
||||
def generate_image(instance, site_source, site_dest, image_source, image_dest, baseurl)
|
||||
image = MiniMagick::Image.open(File.join(site_source, image_source, instance[:src]))
|
||||
digest = Digest::MD5.hexdigest(image.to_blob).slice!(0..5)
|
||||
|
||||
image_dir = File.dirname(instance[:src])
|
||||
ext = File.extname(instance[:src])
|
||||
basename = File.basename(instance[:src], ext)
|
||||
|
||||
orig_width = image[:width].to_f
|
||||
orig_height = image[:height].to_f
|
||||
orig_ratio = orig_width/orig_height
|
||||
|
||||
gen_width = if instance[:width]
|
||||
instance[:width].to_f
|
||||
elsif instance[:height]
|
||||
orig_ratio * instance[:height].to_f
|
||||
else
|
||||
orig_width
|
||||
end
|
||||
gen_height = if instance[:height]
|
||||
instance[:height].to_f
|
||||
elsif instance[:width]
|
||||
instance[:width].to_f / orig_ratio
|
||||
else
|
||||
orig_height
|
||||
end
|
||||
gen_ratio = gen_width/gen_height
|
||||
|
||||
# Don't allow upscaling. If the image is smaller than the requested dimensions, recalculate.
|
||||
if orig_width < gen_width || orig_height < gen_height
|
||||
undersize = true
|
||||
gen_width = if orig_ratio < gen_ratio then orig_width else orig_height * gen_ratio end
|
||||
gen_height = if orig_ratio > gen_ratio then orig_height else orig_width/gen_ratio end
|
||||
end
|
||||
|
||||
gen_name = "#{basename}-#{gen_width.round}by#{gen_height.round}-#{digest}#{ext}"
|
||||
gen_dest_dir = File.join(site_dest, image_dest, image_dir)
|
||||
gen_dest_file = File.join(gen_dest_dir, gen_name)
|
||||
|
||||
# Generate resized files
|
||||
unless File.exists?(gen_dest_file)
|
||||
|
||||
warn "Warning:".yellow + " #{instance[:src]} is smaller than the requested output file. It will be resized without upscaling." if undersize
|
||||
|
||||
# If the destination directory doesn't exist, create it
|
||||
FileUtils.mkdir_p(gen_dest_dir) unless File.exist?(gen_dest_dir)
|
||||
|
||||
# Let people know their images are being generated
|
||||
puts "Generating #{gen_name}"
|
||||
|
||||
# Scale and crop
|
||||
image.combine_options do |i|
|
||||
i.resize "#{gen_width}x#{gen_height}^"
|
||||
i.gravity "center"
|
||||
i.crop "#{gen_width}x#{gen_height}+0+0"
|
||||
end
|
||||
|
||||
image.write gen_dest_file
|
||||
end
|
||||
|
||||
# Return path relative to the site root for html
|
||||
Pathname.new(File.join(baseurl, image_dest, image_dir, gen_name)).cleanpath
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Liquid::Template.register_tag('picture', Jekyll::Picture)
|
||||
Loading…
Reference in New Issue
Block a user