/** * Responsive images based on `srcset` and `window.devicePixelRatio` emulation where needed. * * Call `.hidpi()` on a document or part of a document to proces image srcsets within that section. * * `$.devicePixelRatio()` can be used as a substitute for `window.devicePixelRatio`. * It provides a familiar interface to retrieve the pixel ratio for browsers that don't * implement `window.devicePixelRatio` but do have a different way of getting it. * * @class jQuery.plugin.hidpi */ ( function ( $ ) { /** * Get reported or approximate device pixel ratio. * * - 1.0 means 1 CSS pixel is 1 hardware pixel * - 2.0 means 1 CSS pixel is 2 hardware pixels * - etc. * * Uses `window.devicePixelRatio` if available, or CSS media queries on IE. * * @static * @inheritable * @return {number} Device pixel ratio */ $.devicePixelRatio = function () { if ( window.devicePixelRatio !== undefined ) { // Most web browsers: // * WebKit/Blink (Safari, Chrome, Android browser, etc) // * Opera // * Firefox 18+ // * Microsoft Edge (Windows 10) return window.devicePixelRatio; } else if ( window.msMatchMedia !== undefined ) { // Windows 8 desktops / tablets, probably Windows Phone 8 // // IE 10/11 doesn't report pixel ratio directly, but we can get the // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for // simplicity, but you may get different values depending on zoom // factor, size of screen and orientation in Metro IE. if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) { return 2; } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) { return 1.5; } else { return 1; } } else { // Legacy browsers... // Assume 1 if unknown. return 1; } }; /** * Bracket a given device pixel ratio to one of [1, 1.5, 2]. * * This is useful for grabbing images on the fly with sizes based on the display * density, without causing slowdown and extra thumbnail renderings on devices * that are slightly different from the most common sizes. * * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails, * so will be consistent with default renderings. * * @static * @inheritable * @return {number} Device pixel ratio */ $.bracketDevicePixelRatio = function ( baseRatio ) { if ( baseRatio > 1.5 ) { return 2; } else if ( baseRatio > 1 ) { return 1.5; } else { return 1; } }; /** * Get reported or approximate device pixel ratio, bracketed to [1, 1.5, 2]. * * This is useful for grabbing images on the fly with sizes based on the display * density, without causing slowdown and extra thumbnail renderings on devices * that are slightly different from the most common sizes. * * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails, * so will be consistent with default renderings. * * - 1.0 means 1 CSS pixel is 1 hardware pixel * - 1.5 means 1 CSS pixel is 1.5 hardware pixels * - 2.0 means 1 CSS pixel is 2 hardware pixels * * @static * @inheritable * @return {number} Device pixel ratio */ $.bracketedDevicePixelRatio = function () { return $.bracketDevicePixelRatio( $.devicePixelRatio() ); }; /** * Implement responsive images based on srcset attributes, if browser has no * native srcset support. * * @return {jQuery} This selection * @chainable */ $.fn.hidpi = function () { var $target = this, // @todo add support for dpi media query checks on Firefox, IE devicePixelRatio = $.devicePixelRatio(), testImage = new Image(); if ( devicePixelRatio > 1 && testImage.srcset === undefined ) { // No native srcset support. $target.find( 'img' ).each( function () { var $img = $( this ), srcset = $img.attr( 'srcset' ), match; if ( typeof srcset === 'string' && srcset !== '' ) { match = $.matchSrcSet( devicePixelRatio, srcset ); if ( match !== null ) { $img.attr( 'src', match ); } } } ); } return $target; }; /** * Match a srcset entry for the given device pixel ratio * * Exposed for testing. * * @private * @static * @param {number} devicePixelRatio * @param {string} srcset * @return {Mixed} null or the matching src string */ $.matchSrcSet = function ( devicePixelRatio, srcset ) { var candidates, candidate, bits, src, i, ratioStr, ratio, selectedRatio = 1, selectedSrc = null; candidates = srcset.split( / *, */ ); for ( i = 0; i < candidates.length; i++ ) { candidate = candidates[ i ]; bits = candidate.split( / +/ ); src = bits[ 0 ]; if ( bits.length > 1 && bits[ 1 ].charAt( bits[ 1 ].length - 1 ) === 'x' ) { ratioStr = bits[ 1 ].slice( 0, -1 ); ratio = parseFloat( ratioStr ); if ( ratio <= devicePixelRatio && ratio > selectedRatio ) { selectedRatio = ratio; selectedSrc = src; } } } return selectedSrc; }; /** * @class jQuery * @mixins jQuery.plugin.hidpi */ }( jQuery ) );