UNPKG

15 kBMarkdownView Raw
1<p align="center">
2 <a href="https://medium-zoom.francoischalifour.com"><img src="logo.svg" alt="Demo" width="64"></a>
3 <h3 align="center">medium-zoom</h3>
4 <p align="center">A JavaScript library for zooming images like Medium</p>
5</p>
6
7<p align="center">
8 <a href="https://www.npmjs.com/package/medium-zoom">
9 <img src="https://img.shields.io/npm/v/medium-zoom.svg?style=flat-square" alt="version">
10 </a>
11 <a href="https://github.com/francoischalifour/medium-zoom/blob/master/LICENSE">
12 <img src="https://img.shields.io/npm/l/medium-zoom.svg?style=flat-square" alt="MIT license">
13 </a>
14 <a href="http://npmcharts.com/compare/medium-zoom">
15 <img src="https://img.shields.io/npm/dm/medium-zoom.svg?style=flat-square" alt="downloads">
16 </a>
17 <br>
18 <a href="https://unpkg.com/medium-zoom/dist/">
19 <img src="http://img.badgesize.io/https://unpkg.com/medium-zoom/dist/medium-zoom.min.js?compression=gzip&label=gzip%20size&style=flat-square" alt="gzip size">
20 </a>
21 <a href="https://github.com/francoischalifour/medium-zoom/blob/master/package.json">
22 <img src="https://img.shields.io/badge/dependencies-none-lightgrey.svg?style=flat-square" alt="no dependencies">
23 </a>
24 <a href="https://travis-ci.org/francoischalifour/medium-zoom">
25 <img src="https://img.shields.io/travis/francoischalifour/medium-zoom.svg?style=flat-square" alt="travis">
26 </a>
27</p>
28
29<p align="center">
30 <a href="https://medium-zoom.francoischalifour.com">
31 <img src="https://user-images.githubusercontent.com/6137112/43369906-7623239a-9376-11e8-978b-6e089be499fb.gif" alt="Medium Zoom Demo">
32 </a>
33 <br>
34 <br>
35 <strong>
36 <a href="https://codesandbox.io/s/github/francoischalifour/medium-zoom/tree/master/website">🔬 Playground</a>
37 <a href="https://medium-zoom.francoischalifour.com">🔎 Demo</a>
38 <a href="https://medium-zoom.francoischalifour.com/storybook">📚 Storybook</a>
39 </strong>
40</p>
41
42<details>
43 <summary><strong>Contents</strong></summary>
44
45<!--
46Generate the table of contents using:
47
48```
49npx doctoc README.md --maxlevel 3
50```
51-->
52
53<!-- START doctoc generated TOC please keep comment here to allow auto update -->
54<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
55
56- [Features](#features)
57- [Installation](#installation)
58- [Usage](#usage)
59- [API](#api)
60 - [Selectors](#selectors)
61 - [Options](#options)
62 - [Methods](#methods)
63 - [Attributes](#attributes)
64 - [Events](#events)
65- [Examples](#examples)
66- [Browser support](#browser-support)
67- [Contributing](#contributing)
68- [License](#license)
69
70<!-- END doctoc generated TOC please keep comment here to allow auto update -->
71
72</details>
73
74## Features
75
76- 📱 **Responsive**_scale on mobile and desktop_
77- 🚀 **Performant and lightweight**_should be able to reach 60 [fps](https://en.wikipedia.org/wiki/Frame_rate)_
78- ⚡️ **High definition support**_load the HD version of your image on zoom_
79- 🔎 **Flexibility**_apply the zoom to a selection of images_
80- 🖱 **Mouse, keyboard and gesture friendly**_click anywhere, press a key or scroll away to close the zoom_
81- 🎂 **Event handling**_trigger events when the zoom enters a new state_
82- 📦 **Customization**_set your own margin, background and scroll offset_
83- 🔧 **Pluggable**_add your own features to the zoom_
84- 💎 **Custom templates**_extend the default look to match the UI of your app_
85
86## Installation
87
88The module is available on the [npm](https://www.npmjs.com) registry.
89
90```sh
91npm install medium-zoom
92# or
93yarn add medium-zoom
94```
95
96###### Download
97
98- [Normal](https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.js)
99- [Minified](https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.min.js)
100
101###### CDN
102
103- [jsDelivr](https://www.jsdelivr.com/package/npm/medium-zoom)
104- [unpkg](https://unpkg.com/medium-zoom/)
105
106## Usage
107
108> [Try it out in the browser](https://codesandbox.io/s/github/francoischalifour/medium-zoom/tree/master/website)
109
110Import the library as a module:
111
112```js
113import mediumZoom from 'medium-zoom'
114```
115
116Or import the library with a script tag:
117
118```html
119<script src="node_modules/medium-zoom/dist/medium-zoom.min.js"></script>
120```
121
122That's it! You don't need to import any CSS styles.
123
124Assuming you add the `data-zoomable` attribute to your images:
125
126```js
127mediumZoom('[data-zoomable]')
128```
129
130## API
131
132```ts
133mediumZoom(selector?: string | HTMLElement | HTMLElement[] | NodeList, options?: object): Zoom
134```
135
136### Selectors
137
138The selector allows attaching images to the zoom. It can be of the following types:
139
140- [CSS selectors](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors)
141- [`HTMLElement`](https://developer.mozilla.org/docs/Web/API/HTMLElement)
142- [`NodeList`](https://developer.mozilla.org/docs/Web/API/NodeList)
143- [`Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)
144
145```js
146// CSS selector
147mediumZoom('[data-zoomable]')
148
149// HTMLElement
150mediumZoom(document.querySelector('#cover'))
151
152// NodeList
153mediumZoom(document.querySelectorAll('[data-zoomable]'))
154
155// Array
156const images = [
157 document.querySelector('#cover'),
158 ...document.querySelectorAll('[data-zoomable]'),
159]
160
161mediumZoom(images)
162```
163
164### Options
165
166The options enable the customization of the zoom. They are defined as an object with the following properties:
167
168| Property | Type | Default | Description |
169| -------------- | ------------------------------------- | -------- | --------------------------------------------------------------------------- |
170| `margin` | `number` | `0` | The space outside the zoomed image |
171| `background` | `string` | `"#fff"` | The background of the overlay |
172| `scrollOffset` | `number` | `40` | The number of pixels to scroll to close the zoom |
173| `container` | `string` \| `HTMLElement` \| `object` | `null` | The viewport to render the zoom in<br> [Read more →](docs/container.md) |
174| `template` | `string` \| `HTMLTemplateElement` | `null` | The template element to display on zoom<br> [Read more →](docs/template.md) |
175
176```js
177mediumZoom('[data-zoomable]', {
178 margin: 24,
179 background: '#BADA55',
180 scrollOffset: 0,
181 container: '#zoom-container',
182 template: '#zoom-template',
183})
184```
185
186### Methods
187
188#### `open({ target?: HTMLElement }): Promise<Zoom>`
189
190Opens the zoom and returns a promise resolving with the zoom.
191
192```js
193const zoom = mediumZoom('[data-zoomable]')
194
195zoom.open()
196```
197
198_Emits an event [`open`](#events) on animation start and [`opened`](#events) when completed._
199
200#### `close(): Promise<Zoom>`
201
202Closes the zoom and returns a promise resolving with the zoom.
203
204```js
205const zoom = mediumZoom('[data-zoomable]')
206
207zoom.close()
208```
209
210_Emits an event [`close`](#events) on animation start and [`closed`](#events) when completed._
211
212#### `toggle({ target?: HTMLElement }): Promise<Zoom>`
213
214Opens the zoom when closed / dismisses the zoom when opened, and returns a promise resolving with the zoom.
215
216```js
217const zoom = mediumZoom('[data-zoomable]')
218
219zoom.toggle()
220```
221
222#### `attach(...selectors: string[] | HTMLElement[] | NodeList[] | Array[]): Zoom`
223
224Attaches the images to the zoom and returns the zoom.
225
226```js
227const zoom = mediumZoom()
228
229zoom.attach('#image-1', '#image-2')
230zoom.attach(
231 document.querySelector('#image-3'),
232 document.querySelectorAll('[data-zoomable]')
233)
234```
235
236#### `detach(...selectors: string[] | HTMLElement[] | NodeList[] | Array[]): Zoom`
237
238Releases the images from the zoom and returns the zoom.
239
240```js
241const zoom = mediumZoom('[data-zoomable]')
242
243zoom.detach('#image-1', document.querySelector('#image-2')) // detach two images
244zoom.detach() // detach all images
245```
246
247_Emits an event [`detach`](#events) on the image._
248
249#### `update(options: object): Zoom`
250
251Updates the options and returns the zoom.
252
253```js
254const zoom = mediumZoom('[data-zoomable]')
255
256zoom.update({ background: '#BADA55' })
257```
258
259_Emits an event [`update`](#events) on each image of the zoom._
260
261#### `clone(options?: object): Zoom`
262
263Clones the zoom with provided options merged with the current ones and returns the zoom.
264
265```js
266const zoom = mediumZoom('[data-zoomable]', { background: '#BADA55' })
267
268const clonedZoom = zoom.clone({ margin: 48 })
269
270clonedZoom.getOptions() // => { background: '#BADA55', margin: 48, ... }
271```
272
273#### `on(type: string, listener: () => void, options?: boolean | AddEventListenerOptions): Zoom`
274
275Registers the listener on each target of the zoom.
276
277The same `options` as [`addEventListener`](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener#Parameters) are used.
278
279```js
280const zoom = mediumZoom('[data-zoomable]')
281
282zoom.on('closed', event => {
283 // the image has been closed
284})
285
286zoom.on(
287 'open',
288 event => {
289 // the image has been opened (tracked only once)
290 },
291 { once: true }
292)
293```
294
295The zoom object is accessible in `event.detail.zoom`.
296
297#### `off(type: string, listener: () => void, options?: boolean | AddEventListenerOptions): Zoom`
298
299Removes the previously registered listener on each target of the zoom.
300
301The same `options` as [`removeEventListener`](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener#Parameters) are used.
302
303```js
304const zoom = mediumZoom('[data-zoomable]')
305
306function listener(event) {
307 // ...
308}
309
310zoom.on('open', listener)
311// ...
312zoom.off('open', listener)
313```
314
315The zoom object is accessible in `event.detail.zoom`.
316
317#### `getOptions(): object`
318
319Returns the zoom options as an object.
320
321```js
322const zoom = mediumZoom({ background: '#BADA55' })
323
324zoom.getOptions() // => { background: '#BADA55', ... }
325```
326
327#### `getImages(): HTMLElement[]`
328
329Returns the images attached to the zoom as an array of [`HTMLElement`s](https://developer.mozilla.org/docs/Web/API/HTMLElement).
330
331```js
332const zoom = mediumZoom('[data-zoomable]')
333
334zoom.getImages() // => [HTMLElement, HTMLElement]
335```
336
337#### `getZoomedImage(): HTMLElement`
338
339Returns the current zoomed image as an [`HTMLElement`](https://developer.mozilla.org/docs/Web/API/HTMLElement) or `null` if none.
340
341```js
342const zoom = mediumZoom('[data-zoomable]')
343
344zoom.getZoomedImage() // => null
345zoom.open().then(() => {
346 zoom.getZoomedImage() // => HTMLElement
347})
348```
349
350### Attributes
351
352#### `data-zoom-src`
353
354Specifies the high definition image to open on zoom. This image loads when the user clicks on the source image.
355
356```html
357<img src="image-thumbnail.jpg" data-zoom-src="image-hd.jpg" alt="My image" />
358```
359
360### Events
361
362| Event | Description |
363| ------ | --------------------------------------------------- |
364| open | Fired immediately when the `open` method is called |
365| opened | Fired when the zoom has finished being animated |
366| close | Fired immediately when the `close` method is called |
367| closed | Fired when the zoom out has finished being animated |
368| detach | Fired when the `detach` method is called |
369| update | Fired when the `update` method is called |
370
371```js
372const zoom = mediumZoom('[data-zoomable]')
373
374zoom.on('open', event => {
375 // track when the image is zoomed
376})
377```
378
379The zoom object is accessible in `event.detail.zoom`.
380
381## Examples
382
383<details>
384 <summary>Trigger a zoom from another element</summary>
385
386```js
387const button = document.querySelector('[data-action="zoom"]')
388const zoom = mediumZoom('#image')
389
390button.addEventListener('click', () => zoom.open())
391```
392
393</details>
394
395<details>
396 <summary>Track an event (for analytics)</summary>
397
398You can use the `open` event to keep track of how many times a user interacts with your image. This can be useful if you want to gather some analytics on user engagement.
399
400```js
401let counter = 0
402const zoom = mediumZoom('#image-tracked')
403
404zoom.on('open', event => {
405 console.log(`"${event.target.alt}" has been zoomed ${++counter} times`)
406})
407```
408
409</details>
410
411<details>
412 <summary>Detach a zoom once closed</summary>
413
414```js
415const zoom = mediumZoom('[data-zoomable]')
416
417zoom.on('closed', () => zoom.detach(), { once: true })
418```
419
420</details>
421
422<details>
423 <summary>Attach jQuery elements</summary>
424
425jQuery elements are compatible with `medium-zoom` once converted to an array.
426
427```js
428mediumZoom($('[data-zoomable]').toArray())
429```
430
431</details>
432
433<details>
434 <summary>Create a zoomable React component</summary>
435
436**Using React hooks**
437
438```js
439import React from 'react'
440import mediumZoom from 'medium-zoom'
441
442function ImageZoom({ zoom, src, alt, background }) {
443 const zoomRef = React.useRef(zoom.clone({ background }))
444
445 function attachZoom(image) {
446 zoomRef.current.attach(image)
447 }
448
449 return <img src={src} alt={alt} ref={attachZoom} />
450}
451
452function App() {
453 const zoom = React.useRef(mediumZoom({ background: '#000', margin: 48 }))
454
455 render() {
456 return (
457 <ImageZoom src="image.jpg" alt="Image" zoom={zoom.current} color="#BADA55" />
458 )
459 }
460}
461```
462
463**Using React classes**
464
465```js
466import React, { Component } from 'react'
467import mediumZoom from 'medium-zoom'
468
469class ImageZoom extends Component {
470 zoom = this.props.zoom.clone({
471 background: this.props.color,
472 })
473
474 attachZoom = image => {
475 this.zoom.attach(image)
476 }
477
478 render() {
479 return (
480 <img src={this.props.src} alt={this.props.alt} ref={this.attachZoom} />
481 )
482 }
483}
484
485class App extends Component {
486 zoom = mediumZoom({ background: '#000', margin: 48 })
487
488 render() {
489 return (
490 <ImageZoom src="image.jpg" alt="Image" zoom={this.zoom} color="#BADA55" />
491 )
492 }
493}
494```
495
496</details>
497<br>
498
499You can see [more examples](examples/) including [React](examples/react) and [Vue](examples/vue), or check out the [storybook](https://medium-zoom.francoischalifour.com/storybook).
500
501## Browser support
502
503| IE | Edge | Chrome | Firefox | Safari |
504| --------------- | --------------- | ------ | ------- | ------ |
505| 10<sup>\*</sup> | 12<sup>\*</sup> | 36 | 34 | 9 |
506
507<sup>\*</sup> _These browsers require a [`template` polyfill](https://github.com/webcomponents/template) when using [custom templates](docs/template.md)_.
508
509<blockquote>
510 <p align="center">
511 Cross-browser testing is sponsored by
512 </p>
513 <p align="center">
514 <a href="https://www.browserstack.com">
515 <img src="https://user-images.githubusercontent.com/6137112/44587083-35987000-a7b2-11e8-8e0d-8ba15de83802.png" alt="BrowserStack" height="35">
516 </a>
517 </p>
518</blockquote>
519
520## Contributing
521
522- Run `yarn` to install Node dev dependencies
523- Run `yarn start` to build the library in watch mode
524- Run `yarn run storybook` to see your changes at http://localhost:9001
525
526Please read the [contributing guidelines](CONTRIBUTING.md) for more detailed explanations.
527
528_You can also use [npm](https://www.npmjs.com)._
529
530## License
531
532MIT © [François Chalifour](https://francoischalifour.com)