summaryrefslogtreecommitdiff
path: root/resources/src/jquery/jquery.hidpi.js
blob: aa6590bf50e3d1b0bcc536558595b33763e4be58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/**
 * 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 ) );