DEV Community

Cover image for Let's write a useImageZoom React hook

Let's write a useImageZoom React hook

bgord profile image Bartosz Gordon Updated on ・3 min read

In this article, I am going to present a basic React hook responsible for handling image "zoom" functionality.

What we are going to build

The goal is to create a useImageZoom hook that provides three handlers:

  • zoom in
  • zoom out
  • reset zoom

Bearing that in mind, we can already plan the API that hook may expose.

const [zoomStyles, handlers] = useImageZoom();

Following prevailing convention, it returns a two-element array.

A first element is a style object applied to an image, and the second element contains the aforementioned handlers.

Hook implementation

// App.jsx

import React from "react":

function useImageZoom(maxZoomLevel = 5) {
  const minZoomLevel = 1;

  const [zoomLevel, setZoomLevel] = React.useState(minZoomLevel);

  function zoomIn() {
    setZoomLevel(zoomLevel =>
      zoomLevel < maxZoomLevel ? zoomLevel + 1 : zoomLevel

  function zoomOut() {
    setZoomLevel(zoomLevel =>
      zoomLevel > minZoomLevel ? zoomLevel - 1 : minZoomLevel

  function resetZoom() {

  const zoomStyles = {
    transform: `scale(${zoomLevel})`,

  const handlers = {

  return [zoomStyles, handlers];

A few words about the implementation.

If we want CSS to handle the image scaling, we need to use transform property and passscale(x) as a value. By default, x equals 1, so we assign 1 to the minZoomLevel variable.

maxZoomLevel is the only parameter that hook accepts, defaults to 5.

In zoomIn and zoomOut functions we respectively increase and decrease the zoom level, but keeping it in maxZoomLevel and minZoomLevel boundary.

resetZoom simply sets the minZoomLevel value.


// App.jsx

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

// function useImageZoom(maxZoomLevel = 5) {
//   ...
// }

function App() {
  const [zoomStyles, handlers] = useImageZoom();

  return (
    <div className="app">
      <div className="buttons">
        <button onClick={handlers.zoomIn}>Zoom in</button>
        <button onClick={handlers.zoomOut}>Zoom out</button>
      <div className="preview">
          style={{ ...zoomStyles }}
          alt="preview box"

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

/* styles.css */

.app {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: sans-serif;
  text-align: center;

.buttons {
  margin-top: 50px;

.preview {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 300px;
  width: 300px;
  margin-top: 50px;
  background: #eee;
  overflow: auto;
  position: relative;

Let's see it in action.

As you may have noticed, we have a problem. Both sides of the image get incrementally cropped as we zoom in.


The issue is that transform-origin's initial value equals "50% 50%", which is equivalent to the center of a given image. To get a sense of what this property does, please refer to this page.

To apply the solution, change zoomStyles object to the following:

const zoomStyles = {
  transform: `scale(${zoomLevel})`,
  transformOrigin: "top left"

Let's see what happens.

Ok, the image does not get cropped, but there is another problem. Have you noticed that there is a blank space on the left and at the top part of the preview box? We can force the image to fill the whole available space but still preserving the initial centered position.

We can do it by positioning the image absolutely in the top left corner of the preview box but only if zoom level greater than 1 is applied.

Change zoomStyles object to the following:

const zoomStyles = {
  transform: `scale(${zoomLevel})`,
  transformOrigin: "top left",
  position: zoomLevel > 1 ? "absolute" : undefined,
  top: zoomLevel > 1 ? 0 : undefined,
  left: zoomLevel > 1 ? 0 : undefined

Voilà, it works!

The final version is available on codesandbox.

In the next article, we are going to make useImageZoom hook more flexible and easier to use. Stay tuned!


Editor guide