1 | # ViewportJS #
|
2 |
|
3 | [![NPM version](https://img.shields.io/npm/v/viewportjs.svg)](https://www.npmjs.com/package/viewportjs) [![Build Status](https://img.shields.io/travis/ryanfitzer/ViewportJS.svg)](https://travis-ci.org/ryanfitzer/ViewportJS?branch=master) ![Total downloads](https://img.shields.io/npm/dt/viewportjs.svg) [![Maintainability](https://img.shields.io/codeclimate/maintainability/ryanfitzer/ViewportJS.svg)](https://codeclimate.com/github/ryanfitzer/ViewportJS/maintainability)
|
4 |
|
5 | ViewportJS is built on top of `window.matchMedia` and provides valuable features that enable more structure when querying and subscribing to media queries.
|
6 |
|
7 | - 1.16 kB minified & gzipped.
|
8 |
|
9 | - Supports all browsers that [support `window.matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia#Browser_compatibility).
|
10 |
|
11 | - Compatible with CommonJS, AMD, and browser globals (via [UMD](https://github.com/umdjs/umd)).
|
12 |
|
13 | - Supports SSR (server-side rendering) by providing a shallow API when `window.matchMedia` is unavailable.
|
14 |
|
15 | Give the [demo](http://ryanfitzer.github.io/ViewportJS/demo) a try by changing the size of your browser window and watch the UI update.
|
16 |
|
17 | If you are upgrading from [v3](../../tree/v3.0.2), please see the [v4 migration guide](docs/migrating-to-4.0.0.md).
|
18 |
|
19 |
|
20 | ------
|
21 |
|
22 |
|
23 |
|
24 | - [Installation](#installation)
|
25 | - [CommonJS](#commonjs)
|
26 | - [AMD](#amd)
|
27 | - [Browser Global](#browser-global)
|
28 | - [Usage](#usage)
|
29 | - [Configuration](#configuration)
|
30 | - [Subscribing to Viewport Changes](#subscribing-to-viewport-changes)
|
31 | - [Subscribing Directly to a `mediaQueryString`](#subscribing-directly-to-a-mediaquerystring)
|
32 | - [Instance Methods](#instance-methods)
|
33 | - [`current( [name] )`](#current-name-)
|
34 | - [`matches( [name] )`](#matches-name-)
|
35 | - [`previous( [name] )`](#previous-name-)
|
36 | - [`remove()`](#remove)
|
37 | - [`state( [name] )`](#state-name-)
|
38 | - [Server-Side Rendering](#server-side-rendering)
|
39 | - [Examples](#examples)
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | ## Installation ##
|
45 |
|
46 |
|
47 | ### CommonJS ###
|
48 |
|
49 |
|
50 | Install the latest version from [npm](https://www.npmjs.com/package/viewportjs):
|
51 |
|
52 | ```bash
|
53 | npm install viewportjs
|
54 | ```
|
55 |
|
56 | Add the `viewportjs` package to your app:
|
57 |
|
58 | ```js
|
59 | const viewport = require( 'viewportjs' );
|
60 |
|
61 | const myViewports = viewport( /* configuration */ );
|
62 | ```
|
63 |
|
64 | ### AMD ###
|
65 |
|
66 | The API is exported as an anonymous module. If you're not familiar with AMD, [RequireJS](https://requirejs.org/docs/start.html) is a great place to start.
|
67 |
|
68 |
|
69 | ### Browser Global ###
|
70 |
|
71 | Download the latest [development](https://unpkg.com/viewportjs/dist/viewport.js) and [production](https://unpkg.com/viewportjs/dist/viewport.min.js) versions from [UNPKG](https://unpkg.com/viewportjs/dist/). Once the script is loaded, the `viewport` function can be accessed globally.
|
72 |
|
73 | ```html
|
74 | <!-- When deploying, replace "viewport.js" with "viewport.min.js". -->
|
75 | <script src="viewport.js"></script>
|
76 | <script>
|
77 | const myViewports = viewport( /* configuration */ );
|
78 | </script>
|
79 | ```
|
80 |
|
81 |
|
82 |
|
83 | ## Usage ##
|
84 |
|
85 | Configure viewports by name:
|
86 |
|
87 | ```js
|
88 | const myViewports = viewport( [
|
89 | {
|
90 | name: 'small',
|
91 | query: '( min-width:0px ) and ( max-width:480px )'
|
92 | },
|
93 | {
|
94 | name: 'medium',
|
95 | query: '( min-width:480px ) and ( max-width:767px )'
|
96 | },
|
97 | {
|
98 | name: 'large',
|
99 | query: '( min-width:769px )'
|
100 | }
|
101 | ] );
|
102 | ```
|
103 |
|
104 | Once configured, you can query their state:
|
105 |
|
106 | ```js
|
107 | // Check if `small` is the current viewport
|
108 | myViewports.current( 'small' ); // boolean
|
109 |
|
110 | // Get the current viewport object
|
111 | myViewports.current(); // { name: 'small', matches: boolean, current: boolean }
|
112 |
|
113 | // Check if the `small` viewport's media query matches
|
114 | myViewports.matches( 'small' ); // boolean
|
115 |
|
116 | // Retrieve all matches
|
117 | myViewports.matches(); // [ /* matches viewport state objects */ ]
|
118 | ```
|
119 |
|
120 | You can also subscribe to state changes:
|
121 |
|
122 | ```js
|
123 | // Subscribe to changes in 'small' viewport
|
124 | myViewports( 'small', state => {
|
125 |
|
126 | // Do something based on `state`
|
127 | // {
|
128 | // name: 'small',
|
129 | // matches: boolean,
|
130 | // current: boolean
|
131 | // }
|
132 |
|
133 | } );
|
134 | ```
|
135 |
|
136 |
|
137 |
|
138 | ## Configuration ##
|
139 |
|
140 | The initialization method takes an array of viewport configuration objects that are composed of two properties:
|
141 |
|
142 | - `name` *(string)* The viewport's nickname. Must be unique.
|
143 |
|
144 | - `query` *(string)* A valid [`mediaQueryString`](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Syntax).
|
145 |
|
146 | The order of these objects in the array is important, as it determines how to calculate the `current` viewport, which is defined as the last matching viewport based on the order of the configuration array. So if you prefer a "mobile-first" approach, your viewport configuration objects should be ordered from smallest to largest.
|
147 |
|
148 | The initialization method returns a configured instance that can act as:
|
149 |
|
150 | - A function used to subscribe to viewport changes.
|
151 | - An object that contains methods for querying viewport state.
|
152 |
|
153 |
|
154 |
|
155 | ## Subscribing to Viewport Changes ##
|
156 |
|
157 | The initialization method returns an instance that can be used to subscribe to state changes on the configured viewports.
|
158 |
|
159 | Arguments:
|
160 |
|
161 | - `name`: *(string)* (optional) The name of a configured viewport.
|
162 | - `handler`: *(Function)* The function to execute whenever state changes occur.
|
163 |
|
164 | Returns:
|
165 |
|
166 | - *(Function)*: A function that unsubscribes `handler`.
|
167 |
|
168 | To subscribe to the state of an individual viewport, both `name` and `handler` are required. Providing only a `handler` will set up a subscription to the states of all configured viewports.
|
169 |
|
170 | A subscriber's `handler` is executed whenever there's a change in either the viewport's `matched` or `current` state. When a subscriber is added, its `handler` will be immediately executed.
|
171 |
|
172 | The `handler` receives the following arguments when executed:
|
173 |
|
174 | - `state`: *(Object)* The changed viewport's state object.
|
175 | - `instance`: *(Object)* The configured instance.
|
176 |
|
177 | A viewport state object has three properties:
|
178 |
|
179 | - `name`: *(string)* The name of a configured viewport.
|
180 | - `matches`: *(boolean)* If the viewport's media query matches.
|
181 | - `current`: *(boolean)* If the viewport is current.
|
182 |
|
183 | Example:
|
184 |
|
185 | ```js
|
186 | const myViewports = viewport( /* configuration */ );
|
187 |
|
188 | // Subscribe to an individual configured viewport
|
189 | myViewports( 'name', state => {} );
|
190 |
|
191 | // Subscribe to all configured viewport
|
192 | myViewports( state => {} )
|
193 | ```
|
194 |
|
195 | ### Subscribing Directly to a `mediaQueryString` ###
|
196 |
|
197 | For scenarios where you're only interested in matching a single media query, you can provide the initialization method with a valid [`mediaQueryString`](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Syntax) and an optional `handler`, instead of a configuration array. This will return an instance with a limited API.
|
198 |
|
199 | Arguments:
|
200 |
|
201 | - `query`: *(string)* A valid `mediaQueryString`.
|
202 | - `handler`: *(Function)* (optional) The function to execute whenever state changes occur.
|
203 |
|
204 | Returns:
|
205 |
|
206 | - *(Object)*: A limited API composed of the `matches()` and `remove()` method.
|
207 |
|
208 | If provided, `handler` is executed whenever there's a change in the media query's `matched` state, including on initial subscription.
|
209 |
|
210 | The `handler` receives the following arguments when executed:
|
211 |
|
212 | - `matches`: *(boolean)* If the media query matches.
|
213 | - `instance`: *(Object)* The configured instance.
|
214 |
|
215 | Example:
|
216 |
|
217 | ```js
|
218 | const smallvp = viewport( '( max-width: 500px )', ( matches, instance ) => {} );
|
219 |
|
220 | smallvp.matches(); // true/false
|
221 | smallvp.remove(); // remove the handler, if provided.
|
222 | ```
|
223 |
|
224 |
|
225 |
|
226 | ## Instance Methods ##
|
227 |
|
228 |
|
229 | ### `current( [name] )` ###
|
230 |
|
231 | When called with the `name` argument, checks if `name` is the current viewport and returns a boolean. Otherwise, it returns the current viewport's state object.
|
232 |
|
233 | The current viewport is defined as the last matching viewport based on the order of the configuration array. If there is no current viewport, the state object for the `undefined` viewport is returned: `{ name: undefined, matches: false, current: false }`.
|
234 |
|
235 |
|
236 | Arguments:
|
237 |
|
238 | - `name`: *(string)* (optional) The name of the configured viewport to check.
|
239 |
|
240 | Returns:
|
241 |
|
242 | - *(boolean)*: If `name` is the current viewport.
|
243 | - *(Object)*: The state object of the current viewport.
|
244 |
|
245 |
|
246 | ```js
|
247 | myViewports.current(); // { name: string, matches: boolean, current: boolean }
|
248 |
|
249 | myViewports.current( 'name' ); // true/false
|
250 | ```
|
251 |
|
252 | ### `matches( [name] )` ###
|
253 |
|
254 | When called with the `name` argument, checks if the `name` viewport's media query matches. Otherwise, it returns an array of all matching viewports.
|
255 |
|
256 | If there are no matching viewports, an empty array is returned.
|
257 |
|
258 |
|
259 | Arguments:
|
260 |
|
261 | - `name`: *(string)* (optional) The name of the configured viewport to check.
|
262 |
|
263 | Returns:
|
264 |
|
265 | - *(boolean)*: If the `name` viewport matches.
|
266 | - *(Array)*: An array of state objects for all matching viewports.
|
267 |
|
268 |
|
269 | ```js
|
270 | myViewports.matches(); // [ { name: string, matches: boolean, current: boolean }, ... ]
|
271 |
|
272 | myViewports.matches( 'name' ); // true/false
|
273 | ```
|
274 |
|
275 | ### `previous( [name] )` ###
|
276 |
|
277 | When called with the `name` argument, checks if the `name` viewport was the previously current viewport. Otherwise, it returns the previously current viewport's state object.
|
278 |
|
279 | If there was no previously current viewport, the state object for the `undefined` viewport is returned: `{ name: undefined, matches: false, current: false }`.
|
280 |
|
281 |
|
282 | Arguments:
|
283 |
|
284 | - `name`: *(string)* (optional) The name of the configured viewport to check.
|
285 |
|
286 | Returns:
|
287 |
|
288 | - *(boolean)*: If `name` was the previously current viewport.
|
289 | - *(Object)*: The state object of the previously current viewport.
|
290 |
|
291 |
|
292 | ```js
|
293 | myViewports.previous(); // { name: string, matches: boolean, current: boolean }
|
294 |
|
295 | myViewports.previous( 'name' ); // true/false
|
296 | ```
|
297 |
|
298 |
|
299 | ### `remove()` ###
|
300 |
|
301 | Removes all the instance's configured viewports and subscribers at once.
|
302 |
|
303 | Returns:
|
304 |
|
305 | - *(null)*: Subscribers are removed and values set to `null`.
|
306 |
|
307 | ```js
|
308 | const myViewports = viewport( /* viewport config array */ );
|
309 |
|
310 | myvps( 'small', state => {} );
|
311 | myvps( 'medium', state => {} );
|
312 |
|
313 | myvps.remove();
|
314 | ```
|
315 |
|
316 | ### `state( [name] )` ###
|
317 |
|
318 | When called with the `name` argument, returns the named viewport's state object. Otherwise, it returns an array of state objects for all viewports.
|
319 |
|
320 | Arguments:
|
321 |
|
322 | - `name`: *(string)* (optional) The name of the configured viewport to check.
|
323 |
|
324 | Returns:
|
325 |
|
326 | - *(Object)*: The state object of the named viewport.
|
327 | - *(Array)*: An array of state objects for all viewports.
|
328 |
|
329 |
|
330 | ```js
|
331 | myViewports.state(); // [ { name: string, matches: boolean, current: boolean }, ... ]
|
332 |
|
333 | myViewports.previous( 'name' ); // { name: string, matches: boolean, current: boolean }
|
334 | ```
|
335 |
|
336 |
|
337 |
|
338 | ## Server-Side Rendering ##
|
339 |
|
340 | ViewportJS supports SSR (or "Universal JavaScript") through a shallow API that enables the use of all methods in an environment where `window.matchMedia` is unavailable.
|
341 |
|
342 | Due to potential memory leaks, calls that subscribe to viewports should only be made when their respective unsubscribe functions (or the instance's `remove()` method) can be called in the same environment. Initialization and query methods can be used in any environment, but it's best if subscriptions are made in code that only executes in the browser. The `development` build of ViewportJS will log a warning whenever a subscription is made in an environment where `window.matchMedia` is unavailable. All logging is removed in the `production` build.
|
343 |
|
344 | See the relevant framework examples below for SSR-compatible demonstrations.
|
345 |
|
346 |
|
347 |
|
348 | ## Examples ##
|
349 |
|
350 | - [Vanilla](https://codesandbox.io/s/q3no20volw?module=%2Fsrc%2Findex.js)
|
351 | - [React](https://codesandbox.io/s/00l82nl6pv?module=%2Fsrc%2Fvpjs-component.js)
|
352 | - [Vue](https://codesandbox.io/s/zw8283vyol?module=%2Fsrc%2Fcomponents%2Fvpjs-component.vue)
|
353 | - Angular (todo)
|
354 | - Riot (todo)
|
355 | - [Next.js](https://codesandbox.io/s/q3r0xympjq?module=%2Fcomponents%2Fvpjs-component.js)
|
356 | - Nuxt.js (todo)
|
357 | - Ember (todo)
|