medium-zoom
A JavaScript library for zooming images like Medium
Contents
Features
๐ฑ Responsive โ scale on mobile and desktop๐ Performant and lightweight โ should be able to reach 60 fpsโก๏ฟฝ? High definition support โ load the HD version of your image on zoom๐ Flexibility โ apply the zoom to a selection of images๐ฑ Mouse, keyboard and gesture friendly โ click anywhere, press a key or scroll away to close the zoom๐ Event handling โ trigger events when the zoom enters a new state๐ฆ Customization โ set your own margin, background and scroll offset๐ง Pluggable โ add your own features to the zoom๐ Custom templates โ extend the default look to match the UI of your app
Installation
The module is available on the npm registry.
npm install medium-zoom
# or
yarn add medium-zoomDownload
CDN
Usage
Import the library as a module:
import mediumZoom from 'medium-zoom'Or import the library with a script tag:
<script src="node_modules/medium-zoom/dist/medium-zoom.min.js"></script>That's it! You don't need to import any CSS styles.
Assuming you add the data-zoomable attribute to your images:
mediumZoom('[data-zoomable]')API
mediumZoom(selector?: string | HTMLElement | HTMLElement[] | NodeList, options?: object): ZoomSelectors
The selector allows attaching images to the zoom. It can be of the following types:
// CSS selector
mediumZoom('[data-zoomable]')
// HTMLElement
mediumZoom(document.querySelector('#cover'))
// NodeList
mediumZoom(document.querySelectorAll('[data-zoomable]'))
// Array
const images = [
document.querySelector('#cover'),
...document.querySelectorAll('[data-zoomable]'),
]
mediumZoom(images)Options
The options enable the customization of the zoom. They are defined as an object with the following properties:
| Property | Type | Default | Description |
|---|---|---|---|
margin |
number |
0 |
The space outside the zoomed image |
background |
string |
"#fff" |
The background of the overlay |
scrollOffset |
number |
40 |
The number of pixels to scroll to close the zoom |
container |
string | HTMLElement | object |
null |
The viewport to render the zoom in Read more โ |
template |
string | HTMLTemplateElement |
null |
The template element to display on zoom Read more โ |
mediumZoom('[data-zoomable]', {
margin: 24,
background: '#BADA55',
scrollOffset: 0,
container: '#zoom-container',
template: '#zoom-template',
})Methods
open({ target?: HTMLElement }): Promise<Zoom>
Opens the zoom and returns a promise resolving with the zoom.
const zoom = mediumZoom('[data-zoomable]')
zoom.open()Emits an event open on animation start and opened when completed.
close(): Promise<Zoom>
Closes the zoom and returns a promise resolving with the zoom.
const zoom = mediumZoom('[data-zoomable]')
zoom.close()Emits an event close on animation start and closed when completed.
toggle({ target?: HTMLElement }): Promise<Zoom>
Opens the zoom when closed / dismisses the zoom when opened, and returns a promise resolving with the zoom.
const zoom = mediumZoom('[data-zoomable]')
zoom.toggle()attach(...selectors: string[] | HTMLElement[] | NodeList[] | Array[]): Zoom
Attaches the images to the zoom and returns the zoom.
const zoom = mediumZoom()
zoom.attach('#image-1', '#image-2')
zoom.attach(
document.querySelector('#image-3'),
document.querySelectorAll('[data-zoomable]')
)detach(...selectors: string[] | HTMLElement[] | NodeList[] | Array[]): Zoom
Releases the images from the zoom and returns the zoom.
const zoom = mediumZoom('[data-zoomable]')
zoom.detach('#image-1', document.querySelector('#image-2')) // detach two images
zoom.detach() // detach all imagesEmits an event detach on the image.
update(options: object): Zoom
Updates the options and returns the zoom.
const zoom = mediumZoom('[data-zoomable]')
zoom.update({ background: '#BADA55' })Emits an event update on each image of the zoom.
clone(options?: object): Zoom
Clones the zoom with provided options merged with the current ones and returns the zoom.
const zoom = mediumZoom('[data-zoomable]', { background: '#BADA55' })
const clonedZoom = zoom.clone({ margin: 48 })
clonedZoom.getOptions() // => { background: '#BADA55', margin: 48, ... }on(type: string, listener: () => void, options?: boolean | AddEventListenerOptions): Zoom
Registers the listener on each target of the zoom.
The same options as addEventListener are used.
const zoom = mediumZoom('[data-zoomable]')
zoom.on('closed', event => {
// the image has been closed
})
zoom.on(
'open',
event => {
// the image has been opened (tracked only once)
},
{ once: true }
)The zoom object is accessible in event.detail.zoom.
off(type: string, listener: () => void, options?: boolean | AddEventListenerOptions): Zoom
Removes the previously registered listener on each target of the zoom.
The same options as removeEventListener are used.
const zoom = mediumZoom('[data-zoomable]')
function listener(event) {
// ...
}
zoom.on('open', listener)
// ...
zoom.off('open', listener)The zoom object is accessible in event.detail.zoom.
getOptions(): object
Returns the zoom options as an object.
const zoom = mediumZoom({ background: '#BADA55' })
zoom.getOptions() // => { background: '#BADA55', ... }getImages(): HTMLElement[]
Returns the images attached to the zoom as an array of HTMLElements.
const zoom = mediumZoom('[data-zoomable]')
zoom.getImages() // => [HTMLElement, HTMLElement]getZoomedImage(): HTMLElement
Returns the current zoomed image as an HTMLElement or null if none.
const zoom = mediumZoom('[data-zoomable]')
zoom.getZoomedImage() // => null
zoom.open().then(() => {
zoom.getZoomedImage() // => HTMLElement
})Attributes
data-zoom-src
Specifies the high definition image to open on zoom. This image loads when the user clicks on the source image.
<img src="image-thumbnail.jpg" data-zoom-src="image-hd.jpg" alt="My image" />Events
| Event | Description |
|---|---|
| open | Fired immediately when the open method is called |
| opened | Fired when the zoom has finished being animated |
| close | Fired immediately when the close method is called |
| closed | Fired when the zoom out has finished being animated |
| detach | Fired when the detach method is called |
| update | Fired when the update method is called |
const zoom = mediumZoom('[data-zoomable]')
zoom.on('open', event => {
// track when the image is zoomed
})The zoom object is accessible in event.detail.zoom.
Examples
Trigger a zoom from another element
const button = document.querySelector('[data-action="zoom"]')
const zoom = mediumZoom('#image')
button.addEventListener('click', () => zoom.open())Track an event (for analytics)
You 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.
let counter = 0
const zoom = mediumZoom('#image-tracked')
zoom.on('open', event => {
console.log(`"${event.target.alt}" has been zoomed ${++counter} times`)
})Detach a zoom once closed
const zoom = mediumZoom('[data-zoomable]')
zoom.on('closed', () => zoom.detach(), { once: true })Attach jQuery elements
jQuery elements are compatible with medium-zoom once converted to an array.
mediumZoom($('[data-zoomable]').toArray())Create a zoomable React component
Using React hooks
import React from 'react'
import mediumZoom from 'medium-zoom'
function ImageZoom({ zoom, src, alt, background }) {
const zoomRef = React.useRef(zoom.clone({ background }))
function attachZoom(image) {
zoomRef.current.attach(image)
}
return <img src={src} alt={alt} ref={attachZoom} />
}
function App() {
const zoom = React.useRef(mediumZoom({ background: '#000', margin: 48 }))
render() {
return (
<ImageZoom src="image.jpg" alt="Image" zoom={zoom.current} color="#BADA55" />
)
}
}Using React classes
import React, { Component } from 'react'
import mediumZoom from 'medium-zoom'
class ImageZoom extends Component {
zoom = this.props.zoom.clone({
background: this.props.color,
})
attachZoom = image => {
this.zoom.attach(image)
}
render() {
return (
<img src={this.props.src} alt={this.props.alt} ref={this.attachZoom} />
)
}
}
class App extends Component {
zoom = mediumZoom({ background: '#000', margin: 48 })
render() {
return (
<ImageZoom src="image.jpg" alt="Image" zoom={this.zoom} color="#BADA55" />
)
}
}You can see more examples including React and Vue, or check out the storybook.
Browser support
| IE | Edge | Chrome | Firefox | Safari |
|---|---|---|---|---|
| 10* | 12* | 36 | 34 | 9 |
* These browsers require a template polyfill when using custom templates.
Cross-browser testing is sponsored by
Contributing
- Run
yarnto install Node dev dependencies - Run
yarn startto build the library in watch mode - Run
yarn run storybookto see your changes at http://localhost:9001
Please read the contributing guidelines for more detailed explanations.
You can also use npm.
License
MIT ยฉ Franรงois Chalifour

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.
