TypeScript BSD-3-Clause

react-medium-image-zoom

๐Ÿ”Ž ๐Ÿž The original medium.com-inspired image zooming library for React (since 2016)

R

rpearce

Derniรจre activitรฉ 23 sept. 2026
rpearce/react-medium-image-zoom

2,1ย k

รฉtoiles

104

forks

0

issues ouvertes

image-zoommediumpropsreactreact-imagesreactjstypescriptzoomzoom-imageszoomable

Ce README est souvent en anglais.

react-medium-image-zoom

npm version react-medium-image-zoom bundlejs badge npm downloads All Contributors

The original medium.com-inspired image zooming library for React.

View the storybook examples to see various usages.

Features:

Requirements to know about:

  • <dialog> element (caniuse dialog)
  • ResizeObserver (caniuse ResizeObserver)
  • Package build target is ES2021. If you need to support older environments, run this package through your build system.

Media and Tutorials

Quickstart

npm install --save react-medium-image-zoom
import React from 'react'
import Zoom from 'react-medium-image-zoom'
import 'react-medium-image-zoom/dist/styles.css'

export const MyImg = () => (
  <Zoom>
    <img
      alt="That Wanaka Tree, New Zealand by Laura Smetsers"
      src="/path/to/thatwanakatree.jpg"
      width="500"
    />
  </Zoom>
)

API

You can pass these options to either the Uncontrolled (default) or Controlled components.

export interface UncontrolledProps {
  // Accessible label text for when you want to unzoom.
  // Default: 'Minimize image'
  a11yNameButtonUnzoom?: string

  // Accessible label text for when you want to zoom.
  // Default: 'Expand image'
  a11yNameButtonZoom?: string

  // Allow swipe gesture to unzoom.
  // Default: true
  canSwipeToUnzoom?: boolean

  // Your image (required).
  children: ReactNode

  // Custom CSS className to add to the zoomed <dialog>.
  classDialog?: string

  // Provide your own unzoom button icon.
  // Default: ICompress
  IconUnzoom?: ElementType

  // Provide your own zoom button icon.
  // Default: IEnlarge
  IconZoom?: ElementType

  // Disables the zoom/unzoom behavior.
  // Default: false
  isDisabled?: boolean

  // First argument: boolean value of a new zoomed state (Uncontrolled
  // component) or a suggested new state (Controlled component).
  // Second argument: object containing the event that triggered the change.
  // Default: undefined
  onZoomChange?: (
    value: boolean,
    data: { event: React.SyntheticEvent | Event },
  ) => void

  // Swipe gesture threshold after which to unzoom.
  // Default: 10
  swipeToUnzoomThreshold?: number

  // Specify what type of element should be used for
  // internal component usage. This is useful if the
  // image is inside a <p> or <button>, for example.
  // Default: 'div'
  wrapElement?: 'div' | 'span'

  // Provide your own custom modal content component.
  ZoomContent?: (props: {
    img: ReactElement | null
    buttonUnzoom: ReactElement<HTMLButtonElement>
    onUnzoom: () => void
  }) => ReactElement

  // Higher quality image attributes to use on zoom.
  zoomImg?: ImgHTMLAttributes<HTMLImageElement>

  // Offset in pixels the zoomed image should
  // be from the window's boundaries.
  // Default: 0
  zoomMargin?: number
}

You can pass these options to only the Controlled component.

export interface ControlledProps {
  // ...same as UncontrolledProps

  // Tell the component whether or not it should be zoomed
  // Default: false
  isZoomed: boolean
}

Basic Usage

Uncontrolled component (default)

Import the component and the CSS, wrap your image with the component, and the component will handle it's own state.

import React from 'react'
import Zoom from 'react-medium-image-zoom'
import 'react-medium-image-zoom/dist/styles.css'

// <img />
export const MyImg = () => (
  <Zoom>
    <img
      alt="That Wanaka Tree, New Zealand by Laura Smetsers"
      src="/path/to/thatwanakatree.jpg"
      width="500"
    />
  </Zoom>
)

// <div>
export const MyDiv = () => (
  <Zoom>
    <div
      aria-label="That Wanaka Tree, New Zealand by Laura Smetsers"
      role="img"
      style={{
        backgroundColor: '#fff',
        backgroundImage: `url("/path/to/thatwanakatree.jpg")`,
        backgroundPosition: '50%',
        backgroundRepeat: 'no-repeat',
        backgroundSize: 'cover',
        height: '0',
        paddingBottom: '56%',
        width: '100%',
      }}
    />
  </Zoom>
)

// <picture>
export const MyPicture = () => (
  <Zoom>
    <picture>
      <source media="(max-width: 800px)" srcSet="/path/to/teAraiPoint.jpg" />
      <img
        alt="A beautiful, serene setting in nature"
        src="/path/to/thatwanakatree.jpg"
        width="500"
      />
    </picture>
  </Zoom>
)

// <figure>
export const MyFigure = () => (
  <figure>
    <Zoom>
      <img
        alt="That Wanaka Tree, New Zealand by Laura Smetsers"
        src="/path/to/thatwanakatree.jpg"
        width="500"
      />
    </Zoom>
    <figcaption>Photo by Laura Smetsers</figcaption>
  </figure>
)

Controlled component

Import the Controlled component and the CSS, wrap your image with the component, and then dictate the isZoomed state to the component.

import React, { useCallback, useState } from 'react'
import { Controlled as ControlledZoom } from 'react-medium-image-zoom'
import 'react-medium-image-zoom/dist/styles.css'

const MyComponent = () => {
  const [isZoomed, setIsZoomed] = useState(false)

  const handleZoomChange = useCallback(shouldZoom => {
    setIsZoomed(shouldZoom)
  }, [])

  return (
    <ControlledZoom isZoomed={isZoomed} onZoomChange={handleZoomChange}>
      <img
        alt="That wanaka tree, alone in the water near mountains"
        src="/path/to/thatwanakatree.jpg"
        width="500"
      />
    </ControlledZoom>
  )
}

export default MyComponent

The onZoomChange prop accepts a callback that will receive true or false based on events that occur (like click or scroll events) to assist you in determining when to zoom and unzoom the component.

Styles

You can import the default styles from react-medium-image-zoom/dist/styles.css and override the values from your code, or you can copy the styles.css file and alter it to your liking. The latter is the best option, given rems should be used instead of px to account for different default browser font sizes, and it's hard for a library to guess at what these values should be.

An example of customizing the transition duration, timing function, overlay background color, and unzoom button styles with :focus-visible can be found in this story: https://rpearce.github.io/react-medium-image-zoom/?path=/story/img--custom-modal-styles

Custom zoom modal content

If you want to customize the zoomed modal experience with a caption, form, or other set of components, you can do so by providing a custom component to the ZoomContent prop.

View the live example of custom zoom modal content.

Below is some example code that demonstrates how to use this feature.

export const MyImg = () => (
  <Zoom ZoomContent={CustomZoomContent}>
    <img
      alt="That Wanaka Tree, New Zealand by Laura Smetsers"
      src="/path/to/thatwanakatree.jpg"
      width="500"
    />
  </Zoom>
)

const CustomZoomContent = ({
  /** Default unzoom button */
  buttonUnzoom,

  /** Current state of the zoom modal: UNLOADED, LOADING, LOADED, UNLOADING */
  modalState,

  /** Your image, prepped for zooming */
  img,

  /** A state to check if the zoom img is loaded (useful for loading state) */
  // isZoomImgLoaded,

  /**
   * A callback to manually unzoom the image and close the modal if you want to
   * use your own buttons or listeners in your custom experience.
   */
  //onUnzoom,
}) => {
  const [isLoaded, setIsLoaded] = useState(false)

  useLayoutEffect(() => {
    if (modalState === 'LOADED') {
      setIsLoaded(true)
    } else if (modalState === 'UNLOADING') {
      setIsLoaded(false)
    }
  }, [modalState])

  const classCaption = isLoaded
    ? 'zoom-caption zoom-caption--loaded'
    : 'zoom-caption'

  return (
    <>
      {buttonUnzoom}

      <figure>
        {img}
        <figcaption className={classCaption}>
          That Wanaka Tree, also known as the Wanaka Willow, is a willow tree
          located at the southern end of Lake Wฤnaka in the Otago region of New
          Zealand.
          <cite className="zoom-caption-cite">
            Wikipedia,{' '}
            <a
              className="zoom-caption-link"
              href="https://en.wikipedia.org/wiki/That_Wanaka_Tree"
            >
              That Wanaka Tree
            </a>
          </cite>
        </figcaption>
      </figure>
    </>
  )
}

Migrating From v4 to v5

Here are the prop changes from v4 to be aware of:

  • closeText was renamed to a11yNameButtonUnzoom
  • openText was renamed to a11yNameButtonZoom
  • overlayBgColorStart was removed and is now controlled via the CSS selector [data-rmiz-modal-overlay="hidden"]
  • overlayBgColorEnd was removed and is now controlled via the CSS selector [data-rmiz-modal-overlay="visible"]
  • portalEl was removed, for we are using the <dialog> element now
  • transitionDuration was removed and is now controlled via the CSS selectors [data-rmiz-modal-overlay] and [data-rmiz-modal-img]
  • wrapElement was removed then added back in v5.1.0
  • wrapStyle was removed
  • zoomZindex was removed, for we are using the <dialog> element now

And you can now provide zoomImg props to specify a different image to load when zooming.

Contributors โœจ

Thanks goes to these wonderful people (emoji key):

Robert Pearce
Robert Pearce

๐Ÿ’ป ๐Ÿ’ฌ โš ๏ธ ๐Ÿ› ๐Ÿ’ก ๐ŸŽจ ๐Ÿ‘€ ๐Ÿค” ๐Ÿ“–
Cameron Bothner
Cameron Bothner

๐Ÿ’ป ๐Ÿ“– ๐Ÿ› ๐Ÿ’ก ๐Ÿค” ๐Ÿ‘€ โš ๏ธ
Jeremy Bini
Jeremy Bini

๐Ÿ’ป ๐Ÿ›
ismay
ismay

๐Ÿ› ๐Ÿค”
Rajit Singh
Rajit Singh

๐Ÿ›
Roberto Saccon
Roberto Saccon

๐Ÿ›
wtfdaemon
wtfdaemon

๐Ÿ›
Josh Sloat
Josh Sloat

๐Ÿ› ๐Ÿ’ป ๐Ÿ’ก ๐Ÿ‘€ ๐Ÿค” ๐Ÿ“– ๐ŸŽจ ๐Ÿ’ฌ
Aswin
Aswin

๐Ÿ’ฌ
Alex Shelkovskiy
Alex Shelkovskiy

๐Ÿ›
Adrian Bindiu
Adrian Bindiu

๐Ÿ›
Kendall Buchanan
Kendall Buchanan

๐Ÿ›
Kaycee
Kaycee

๐Ÿ’ป
Anuj
Anuj

๐Ÿ› ๐Ÿ’ฌ
Ludwig Frank
Ludwig Frank

๐Ÿ› ๐Ÿ’ป
LX
LX

๐Ÿ› ๐Ÿค”
Rosen Tomov
Rosen Tomov

๐Ÿ›
Tom Moor
Tom Moor

๐Ÿ’ป ๐Ÿ›
Johan Preynat
Johan Preynat

๐Ÿ’ป ๐Ÿ›
Rahul Gaba
Rahul Gaba

๐Ÿ’ป ๐Ÿ›
Spencer Davis
Spencer Davis

๐Ÿ’ป ๐Ÿค” ๐Ÿ‘€ ๐ŸŽจ
dnlnvl
dnlnvl

๐Ÿ’ป
Madi
Madi

๐Ÿค”
Ben Hood
Ben Hood

๐Ÿค” ๐Ÿ› ๐Ÿ’ก ๐Ÿ‘€
Navilan
Navilan

๐Ÿค”
13806
13806

๐Ÿ›
Akshay Kadam (A2K)
Akshay Kadam (A2K)

๐Ÿ› ๐Ÿค”
Jake Stewart
Jake Stewart

๐Ÿ› ๐Ÿค”
hhh
hhh

๐Ÿ›
@davalapar
@davalapar

๐Ÿ›
Sun Knudsen
Sun Knudsen

๐Ÿ’ป ๐Ÿ› ๐Ÿค” ๐Ÿ’ก ๐Ÿ’ฌ ๐Ÿ‘€ โš ๏ธ ๐Ÿ“–
Douglas Galdino
Douglas Galdino

๐Ÿ’ป ๐Ÿ“– ๐Ÿ› ๐Ÿค” ๐Ÿ’ก ๐Ÿ‘€ โš ๏ธ
Mohammed Faragallah
Mohammed Faragallah

๐Ÿ› ๐Ÿค” ๐Ÿ’ก
Youngrok Kim
Youngrok Kim

๐Ÿ’ป ๐Ÿ›
Nandhagopal Ezhilmaran
Nandhagopal Ezhilmaran

๐Ÿ›
Mattia Astorino
Mattia Astorino

๐Ÿ›
Dan Wood
Dan Wood

๐Ÿ“–
Zachery C Gentry
Zachery C Gentry

๐Ÿ›
xmflsct
xmflsct

๐Ÿ›
Will.iam
Will.iam

๐Ÿ’ป โš ๏ธ
Gourav Goyal
Gourav Goyal

๐Ÿ“–
Joshua Chen
Joshua Chen

๐Ÿ› ๐Ÿ’ป
David Edler
David Edler

๐Ÿ› ๐Ÿ’ป
rikusen0335
rikusen0335

๐Ÿค”
Surjith S M
Surjith S M

๐Ÿค”
developergunny
developergunny

๐Ÿ›
Khan Mohsin
Khan Mohsin

๐Ÿ’ฌ
Robin Goudeketting
Robin Goudeketting

๐Ÿ›
Botros Toro
Botros Toro

๐Ÿค”
Christian Guevara
Christian Guevara

๐Ÿ’ฌ
Johan Book
Johan Book

๐Ÿ›
Paolo Di Bello
Paolo Di Bello

๐Ÿค”
Tommaso De Rossi
Tommaso De Rossi

๐Ÿ“– ๐Ÿ›
Lezan
Lezan

๐Ÿ› ๐Ÿค”
Ibrahim H. Sluma
Ibrahim H. Sluma

๐Ÿ›
Ben Gotow
Ben Gotow

๐Ÿ›
Rubon72
Rubon72

๐Ÿ›
wanderingme
wanderingme

๐Ÿ›
Thomas Strobl
Thomas Strobl

๐Ÿ› ๐Ÿค” ๐Ÿ’ก ๐Ÿ’ฌ ๐Ÿ‘€
Songkeys
Songkeys

๐Ÿ› ๐Ÿค” ๐Ÿ’ก ๐Ÿ’ฌ ๐Ÿ‘€
AntoineS92
AntoineS92

๐Ÿ›
Sindre Aubert
Sindre Aubert

๐Ÿ›
mx
mx

๐Ÿ›
Sander Heling
Sander Heling

๐Ÿ›
Yida Zhang
Yida Zhang

๐Ÿ› ๐Ÿ’ป
Nir
Nir

๐Ÿ›
hhatakeyama
hhatakeyama

๐Ÿ›
Paco
Paco

๐Ÿ› ๐Ÿค”
LichLord91
LichLord91

๐Ÿ›
just-small-potato
just-small-potato

๐Ÿค”
walmsles
walmsles

๐Ÿ›
tenshin
tenshin

๐Ÿ’ฌ
Steven Tey
Steven Tey

๐Ÿ›
Sergey
Sergey

๐Ÿ› ๐Ÿ’ป ๐Ÿ“–
Diego Azevedo
Diego Azevedo

๐Ÿ“–
Faizan Ahmad
Faizan Ahmad

๐Ÿ›
Kunal L.
Kunal L.

๐Ÿ›
Kevin Wang
Kevin Wang

๐Ÿค”
u3u
u3u

๐Ÿค” ๐Ÿ‘€
Hong
Hong

๐Ÿ’ป
Wojciech Rok
Wojciech Rok

๐Ÿ’ป ๐Ÿค”
Matija
Matija

๐Ÿ›
Jiayi Hu
Jiayi Hu

๐Ÿ›
Zeit der Forschung
Zeit der Forschung

๐Ÿ›
Andrei Barabas
Andrei Barabas

๐Ÿ› ๐Ÿค”
Nรฉmeth Benedek
Nรฉmeth Benedek

๐Ÿ›
iMalFect
iMalFect

๐Ÿ›
Karl Horky
Karl Horky

๐Ÿ› ๐Ÿค” ๐Ÿ‘€
้™ˆๆจๆ–‡
้™ˆๆจๆ–‡

๐Ÿ›
Zohaib Khan
Zohaib Khan

๐Ÿ’ป ๐Ÿ’ก
Ryan DeStefano
Ryan DeStefano

๐Ÿค” ๐Ÿ‘€
Akhileswar Kamale
Akhileswar Kamale

๐Ÿ›
MwSpace llc
MwSpace llc

๐Ÿ›
Sukka
Sukka

๐Ÿ’ป
Michaล‚ Kowalczyk
Michaล‚ Kowalczyk

๐Ÿ›
Julius Tarng
Julius Tarng

๐Ÿ›
Joey Van Lierop
Joey Van Lierop

๐Ÿ› ๐Ÿ‘€
Fabio Lazzaroni
Fabio Lazzaroni

๐Ÿ›

This project follows the all-contributors specification. Contributions of any kind welcome!

Projets similaires

๐Ÿ”Ž๐Ÿ–ผ A JavaScript library for zooming images like Medium

JavaScripteventimageimage-zoom
Ffrancoischalifour
3,9ย k รฉtoiles168

A performant and customizable image zoom component built with Reanimated v2+ and TypeScript. ๐ŸŒƒ ๐Ÿš€

TypeScriptgesturegesture-handlerimage
Llikashefqet
516 รฉtoiles59

A little yet powerful framework agnostic headless library to zoom images on the web

TypeScriptframeworksheadlessnextjs
Wwillnguyen1312
357 รฉtoiles13