Merge "resourceloader: Simplify StringSet fallback"
[lhc/web/wiklou.git] / resources / src / jquery / jquery.hidpi.js
1 /**
2 * Responsive images based on `srcset` and `window.devicePixelRatio` emulation where needed.
3 *
4 * Call `.hidpi()` on a document or part of a document to proces image srcsets within that section.
5 *
6 * `$.devicePixelRatio()` can be used as a substitute for `window.devicePixelRatio`.
7 * It provides a familiar interface to retrieve the pixel ratio for browsers that don't
8 * implement `window.devicePixelRatio` but do have a different way of getting it.
9 *
10 * @class jQuery.plugin.hidpi
11 */
12 ( function () {
13
14 /**
15 * Get reported or approximate device pixel ratio.
16 *
17 * - 1.0 means 1 CSS pixel is 1 hardware pixel
18 * - 2.0 means 1 CSS pixel is 2 hardware pixels
19 * - etc.
20 *
21 * Uses `window.devicePixelRatio` if available, or CSS media queries on IE.
22 *
23 * @static
24 * @inheritable
25 * @return {number} Device pixel ratio
26 */
27 $.devicePixelRatio = function () {
28 if ( window.devicePixelRatio !== undefined ) {
29 // Most web browsers:
30 // * WebKit/Blink (Safari, Chrome, Android browser, etc)
31 // * Opera
32 // * Firefox 18+
33 // * Microsoft Edge (Windows 10)
34 return window.devicePixelRatio;
35 } else if ( window.msMatchMedia !== undefined ) {
36 // Windows 8 desktops / tablets, probably Windows Phone 8
37 //
38 // IE 10/11 doesn't report pixel ratio directly, but we can get the
39 // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for
40 // simplicity, but you may get different values depending on zoom
41 // factor, size of screen and orientation in Metro IE.
42 if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) {
43 return 2;
44 } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) {
45 return 1.5;
46 } else {
47 return 1;
48 }
49 } else {
50 // Legacy browsers...
51 // Assume 1 if unknown.
52 return 1;
53 }
54 };
55
56 /**
57 * Bracket a given device pixel ratio to one of [1, 1.5, 2].
58 *
59 * This is useful for grabbing images on the fly with sizes based on the display
60 * density, without causing slowdown and extra thumbnail renderings on devices
61 * that are slightly different from the most common sizes.
62 *
63 * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails,
64 * so will be consistent with default renderings.
65 *
66 * @static
67 * @inheritable
68 * @param {number} baseRatio Base ratio
69 * @return {number} Device pixel ratio
70 */
71 $.bracketDevicePixelRatio = function ( baseRatio ) {
72 if ( baseRatio > 1.5 ) {
73 return 2;
74 } else if ( baseRatio > 1 ) {
75 return 1.5;
76 } else {
77 return 1;
78 }
79 };
80
81 /**
82 * Get reported or approximate device pixel ratio, bracketed to [1, 1.5, 2].
83 *
84 * This is useful for grabbing images on the fly with sizes based on the display
85 * density, without causing slowdown and extra thumbnail renderings on devices
86 * that are slightly different from the most common sizes.
87 *
88 * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails,
89 * so will be consistent with default renderings.
90 *
91 * - 1.0 means 1 CSS pixel is 1 hardware pixel
92 * - 1.5 means 1 CSS pixel is 1.5 hardware pixels
93 * - 2.0 means 1 CSS pixel is 2 hardware pixels
94 *
95 * @static
96 * @inheritable
97 * @return {number} Device pixel ratio
98 */
99 $.bracketedDevicePixelRatio = function () {
100 return $.bracketDevicePixelRatio( $.devicePixelRatio() );
101 };
102
103 /**
104 * Implement responsive images based on srcset attributes, if browser has no
105 * native srcset support.
106 *
107 * @return {jQuery} This selection
108 * @chainable
109 */
110 $.fn.hidpi = function () {
111 var $target = this,
112 // TODO add support for dpi media query checks on Firefox, IE
113 devicePixelRatio = $.devicePixelRatio(),
114 testImage = new Image();
115
116 if ( devicePixelRatio > 1 && testImage.srcset === undefined ) {
117 // No native srcset support.
118 $target.find( 'img' ).each( function () {
119 var $img = $( this ),
120 srcset = $img.attr( 'srcset' ),
121 match;
122 if ( typeof srcset === 'string' && srcset !== '' ) {
123 match = $.matchSrcSet( devicePixelRatio, srcset );
124 if ( match !== null ) {
125 $img.attr( 'src', match );
126 }
127 }
128 } );
129 }
130
131 return $target;
132 };
133
134 /**
135 * Match a srcset entry for the given device pixel ratio
136 *
137 * Exposed for testing.
138 *
139 * @private
140 * @static
141 * @param {number} devicePixelRatio
142 * @param {string} srcset
143 * @return {Mixed} null or the matching src string
144 */
145 $.matchSrcSet = function ( devicePixelRatio, srcset ) {
146 var candidates,
147 candidate,
148 bits,
149 src,
150 i,
151 ratioStr,
152 ratio,
153 selectedRatio = 1,
154 selectedSrc = null;
155 candidates = srcset.split( / *, */ );
156 for ( i = 0; i < candidates.length; i++ ) {
157 candidate = candidates[ i ];
158 bits = candidate.split( / +/ );
159 src = bits[ 0 ];
160 if ( bits.length > 1 && bits[ 1 ].charAt( bits[ 1 ].length - 1 ) === 'x' ) {
161 ratioStr = bits[ 1 ].slice( 0, -1 );
162 ratio = parseFloat( ratioStr );
163 if ( ratio <= devicePixelRatio && ratio > selectedRatio ) {
164 selectedRatio = ratio;
165 selectedSrc = src;
166 }
167 }
168 }
169 return selectedSrc;
170 };
171
172 /**
173 * @class jQuery
174 * @mixins jQuery.plugin.hidpi
175 */
176
177 }() );