Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

medium-zoom

francoischalifour625.5kMIT1.1.0TypeScript support: included

A JavaScript library for zooming images like Medium

medium, image, zoom, picture, lightbox, click, scroll, pure, plain, vanilla, javascript, ux, performance, event, medium-zoom, zoomable

readme

Demo

medium-zoom

A JavaScript library for zooming images like Medium

version MIT license downloads
gzip size no dependencies travis

Medium Zoom Demo

🔬 Playground🔎 Demo📚 Storybook

<summary>Contents</summary>

Features

  • 📱 Responsive — scale on mobile and desktop
  • 🚀 Performant and lightweight — optimized 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
  • 🔌 Framework agnostic — works with React, Vue, Angular, Svelte, Solid, etc.

Installation

The module is available on the npm registry.

npm install medium-zoom
# or
yarn add medium-zoom
Download
CDN

Usage

Try it out in the browser

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]')

[!TIP] If you want to control when to inject the Medium Zoom CSS styles, you can use the pure JavaScript bundle:

import mediumZoom from 'medium-zoom/dist/pure'
import 'medium-zoom/dist/style.css'

API

mediumZoom(selector?: string | HTMLElement | HTMLElement[] | NodeList, options?: object): Zoom

Selectors

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 images

Emits 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.

Framework integrations

Medium Zoom is a JavaScript library that can be used with any framework. Here are some integrations that you can use to get started quickly:

Examples

<summary>Trigger a zoom from another element</summary>
const button = document.querySelector('[data-action="zoom"]')
const zoom = mediumZoom('#image')

button.addEventListener('click', () => zoom.open())
<summary>Track an event (for analytics)</summary>

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`)
})
<summary>Detach a zoom once closed</summary>
const zoom = mediumZoom('[data-zoomable]')

zoom.on('closed', () => zoom.detach(), { once: true })
<summary>Attach jQuery elements</summary>

jQuery elements are compatible with medium-zoom once converted to an array.

mediumZoom($('[data-zoomable]').toArray())
<summary>Create a zoomable React component</summary>
import React, { useRef } from 'react'
import mediumZoom from 'medium-zoom'

export function ImageZoom({ options, ...props }) {
  const zoomRef = useRef(null)

  function getZoom() {
    if (zoomRef.current === null) {
      zoomRef.current = mediumZoom(options)
    }

    return zoomRef.current
  }

  function attachZoom(image) {
    const zoom = getZoom()

    if (image) {
      zoom.attach(image)
    } else {
      zoom.detach()
    }
  }

  return <img {...props} ref={attachZoom} />
}

You can see more examples including React and Vue, or check out the storybook.

Debugging

The zoomed image is not visible

The library doesn't provide a z-index value on the zoomed image to avoid conflicts with other frameworks. Some frameworks might specify a z-index for their elements, which makes the zoomed image not visible.

If that's the case, you can provide a z-index value in your CSS:

.medium-zoom-overlay,
.medium-zoom-image--opened {
  z-index: 999;
}

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

BrowserStack

Contributing

  • Run yarn to install Node dev dependencies
  • Run yarn start to build the library in watch mode
  • Run yarn run storybook to 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

changelog

1.1.0 (2023-11-16)

Features

1.0.8 (2022-11-15)

Bug Fixes

  • support images in <picture> elements with currentSrc (#194) (32ee39f)

1.0.7 (2022-11-14)

Bug Fixes

1.0.6 (2020-07-08)

Bug Fixes

  • support lazy loading images on Firefox (#158) (aebb316)

1.0.5 (2019-12-07)

Fixed

1.0.4 (2019-04-06)

Fixed

  • Bring SSR compatibility by not using window outside of main function (#95) (541e8f0), closes #94

1.0.3 (2019-01-20)

Changed

  • Export default export type as ESM module (#82) (ed45fcb)

Fixed

1.0.2 (2018-09-05)

Added

  • Improve TypeScript definition (#73)

1.0.1 (2018-08-29)

Fixed

  • Don't prevent behavior of all clicks (#72) (71eebf9), closes #71

1.0.0 (2018-08-28)

Added

  • Methods become chainable. All methods except getters and animation methods (open(), close() and toggle()) return the zoom object to allow method calls to be chained.
const zoom = mediumZoom()

zoom
  .attach('#image-1', '#image-2')
  .on('open', () => zoom.update({ background: 'yellow' }))
  .open()
  • Animation methods return promises. open(), close() and toggle() return promises resolving with the zoom for acting accordingly when the animation is completed. To remain compatible with IE10, promises are converted to no-operation functions if unavailable.
const zoom = mediumZoom('[data-zoom]')

zoom.open().then(() => zoom.update({ background: 'yellow' }))
  • Options

  • Methods

    • attach(...selectors: string[]|Element[]|NodeList[]|Array[]) => Zoom
    • clone(options?: object) => Zoom
    • getOptions() => object
    • getImages() => Element[]
    • getZoomedImage() => Element
  • Events

    • "update" is fired when the update method is called
  • Add TypeScript definitions

  • Improve documentation

Fixed

  • HD image scales the first time on Firefox (264c81f9d54b7272fa260616f117c3149be89123)
  • Support srcset attribute (#51)
  • Support SVG sources (#56)

Breaking changes

  • Imports. If you're using CommonJS, you'll need to change the require statement:
- const mediumZoom = require('medium-zoom')
+ const mediumZoom = require('medium-zoom').default
  • No images selected by default. Prior to version 1, all scaled images (via HTML or CSS properties) were added to the zoom when calling mediumZoom(). Now, calling mediumZoom() without selector does not attach any images to the zoom. This change is necessary for having a more predictable behavior and a more composable API.

  • Options

    • metaClick was removed
  • Methods

    • show() => voidopen({ target?: Element }) => Promise<Zoom>
    • hide() => voidclose() => Promise<Zoom>
    • toggle() => voidtoggle({ target?: Element }) => Promise<Zoom>
    • detach() => voiddetach(...selectors: string[]|Element[]|NodeList[]|Array[]) => Zoom
    • update(options: object) => voidupdate(options: object) => Zoom
    • addEventListeners(type: string, listener: Function) => voidon(type: string, listener: Function, options?: object) => Zoom
    • removeEventListeners(type: string, listener: Function) => voidoff(type: string, listener: Function, options?: object) => Zoom
  • Attributes

    • data-zoom-targetdata-zoom-src
  • Events

    • "show" → "open"
    • "shown" → "opened"
    • "hide" → "close"
    • "hidden" → "closed"

0.4.0 (2018-03-09)

Added

Fixed

  • Calling .detach() when having a zoomed image: 9fa798d3fe96ae7060f316995b84eaacf3ce8a11

0.3.0 (2017-12-07)

Added

  • Add support for templates and containers: d0d1ec141ffe744d059dddcfc08b6e830b7c17c9

0.2.0 (2017-10-01)

Added

  • Add HD support: 1db9607dce2aa348d9be465208395d125b16e728

Changed

  • Rewrite core implementation: 5158cace958acee0e89a4c9358704ed504756254
  • Update library description: a6f424bae2da534563154c26b49046367d7db215
  • Use less restrictive CSS rules: acaeba4bf96576b65867c3effc2710bd9d029dc0
  • Reduce latency on click to unzoom: 3c4c2fef2c2fca2ce542e57e13ce3198fe7ba2bb

Fixed

  • Fix hide event being thrown multiple times on scroll: 3c4c2fef2c2fca2ce542e57e13ce3198fe7ba2bb

0.1.8 (2017-09-17)

Fixed

  • Do not trigger the zoom when target is null (can happen on fast double click): 3f795b44877af341ed65ae7c1bc764cc5122cdfb

0.1.7 (2017-07-21)

Fixed

  • Don't ignore margin on floating images: 384f0b1576e66f16844e0482eb1653a0c9479ad2

0.1.6 (2017-07-19)

Fixed

  • Support scrollTop position on Firefox: ee13718c38a7a4ba486cacfb50f2861dd1adb8ad

0.1.5 (2017-06-10)

Changed

  • Wrong selector trows a TypeError instead of a SyntaxError: 38e6292ce83d1b54f4fd80cce03d737c3872a58f
  • Library now fully exported by webpack: 5c7944b5e2de19828c8f9298fdc7a03a9146e42b
  • Production version is used when importing the library: 3a7d8ebc0ddd2cb142ccb8519de6fc57e8e8ba3e

0.1.4 (2017-05-31)

Added

  • Support NodeLists, HTMLCollections and Nodes (#8): aa8ff0ff6743e0bc011ea162ff068a2ddbb0f9ab

Changed

  • Apply the default selector only when the plugin is given none (#7): aa8ff0ff6743e0bc011ea162ff068a2ddbb0f9ab

0.1.3 (2017-04-01)

Fixed

  • Center image when the doctype is not declared (#4): 95be45a63837bcd282433728f8db8759bd5777cb
  • Ignore zero-pixel scroll offset: 3cd34640ab26b04b802ce39ce74f092180e3cb00

0.1.2 (2017-03-04)

Added

  • .detach() method that unsubscribe images from the zoom (#2): 2ceb5120ebc35bb457a08474e0d39430e253ab08

0.1.1 (2016-10-06)

Fixed

  • Export the plugin for npm usage (#1): affca211ac6999f1d2c554205d5b216c37fc7c1c

0.1.0 (2016-07-28)

Initial release.