react-zoom-pan-pinch

repository·master·Indexed 23 days ago

https://github.com/bettertyped/react-zoom-pan-pinch

A fast, lightweight React package for enabling zooming, panning, and pinching on HTML elements. It supports mobile gestures, touchpad gestures, and desktop mouse events. The library provides components like TransformWrapper, TransformComponent, and MiniMap, as well as the useControls hook for programmatic zoom and reset functionality.

Tokens
16K
Snippets
30
Records
98
Agent score
83%

What's inside react-zoom-pan-pinch

  1. Keep content centered when zoomed out

    master

    You can force the content to remain centered in the viewport whenever the scale is less than 1× (zoomed out). This is useful for UIs like cinema or ticket-booking apps where the layout should stay framed until the user zooms in. When this mode is active, panning is restricted while zoomed out, and the content will snap back to the center. Once the user zooms in past 1×, free panning is enabled.

    To achieve this, use the following props on <TransformWrapper>:

    • centerZoomedOut: (boolean) Keeps content centered whenever scale < 1.
    • centerOnInit: (boolean) Centers content when the component mounts.
    • initialScale: (number) Sets the starting zoom level (e.g., 0.5 for 50%).
    • minScale: (number) Sets the minimum allowed zoom level.
    <TransformWrapper
      initialScale={0.5}
      minScale={0.2}
      centerOnInit
      centerZoomedOut
    >
      {(utils) => (
        <TransformComponent>
          <img src="your-image.jpg" alt="" />
        </TransformComponent>
      )}
    </TransformWrapper>
  2. When to use useTransformInit vs useTransformEffect

    master

    Choosing between useTransformInit and useTransformEffect depends on whether you need to react to continuous changes or perform one-time setup.

    FeatureuseTransformInituseTransformEffect
    FiresOnce (on mount)On every transform change
    PurposeSetup / measurementsOngoing side-effects
    Deps[] (captured once)[callback, context]

    Important Caveat

    useTransformInit uses an empty dependency array ([]) internally. This means the callback is captured once at mount time. If your callback relies on props or state that change over time, those values will be stale inside the hook. For logic that needs to react to changing props or continuous transform updates, use useTransformEffect instead.

  3. Configure zoom boundary behavior with disablePadding

    master

    The disablePadding prop controls the behavior of the zoom effect when a user zooms past the content boundaries.

    • Default (disablePadding: false): Enables a 'padding' effect. The content allows a small elastic overscroll, stretching slightly beyond the boundary before snapping back with a smooth animation (similar to iOS scroll bounce).
    • Strict Clamping (disablePadding: true): Disables the padding effect. The zoom is strictly clamped, meaning the content stops exactly at the boundary with no overscroll or snap-back animation.
  4. Maintain visual size with the KeepScale component

    master
    The KeepScale component is used to wrap child elements (such as map markers or pins) so they maintain their constant visual size on the screen, regardless of the current zoom level. This is useful when you want certain UI elements to remain legible and crisp even as the background terrain or map scales up or down (e.g., preventing a pin from becoming massive when zooming in or tiny when zooming out).
  5. How Virtualize determines visibility

    master

    Each Virtualize component declares its position and size in the content space using x, y, width, and height props.

    On every transform change (pan, zoom, or pinch), the component calculates whether the scaled and translated bounding box intersects the viewport.

    • Mounting: When the bounding box intersects the viewport, children are mounted.
    • Unmounting: When the bounding box no longer intersects the viewport, children are removed from the DOM.
  6. Use the Virtualize component for large canvases

    master

    The Virtualize component optimizes performance by mounting and unmounting children based on their visibility within the viewport. It is designed for large canvases containing many elements (such as seat maps, tile grids, or node graphs) where rendering every item in the DOM would degrade performance.

    Virtualize wraps content positioned inside the transformed canvas and only renders its children when its declared bounding box overlaps the viewport.

    <TransformWrapper>
      <TransformComponent>
        <div style={{ position: "relative", width: 4000, height: 4000 }}>
          <Virtualize x={100} y={200} width={300} height={150} margin={50}>
            <MyExpensiveWidget />
          </Virtualize>
        </div>
      </TransformComponent>
    </TransformWrapper>
  7. Use limitToBounds to keep content inside the viewport

    master

    For the common use case where you want to ensure the scaled content never moves outside the visible viewport (the wrapper edges), use the limitToBounds prop.

    Unlike manual position bounds which use fixed pixel values, limitToBounds allows the library to automatically derive limits based on the wrapper dimensions, the content size, and the current zoom scale. This is the preferred method when you want a 'contained' feel without manually calculating pixel offsets.

  8. Use exclusion zones to block gestures on specific elements

    master

    When your zoomable content contains interactive elements like scrollable lists, buttons, or text inputs, you may want to prevent the library's gestures (panning, zooming, etc.) from triggering when a user interacts with those specific elements.

    To do this, use the excluded array within the panning, wheel, pinch, or doubleClick configuration objects in TransformWrapper. You provide an array of CSS class names. Any event originating from an element (or its children) that carries one of these classes will be ignored by the corresponding gesture handler.

    Key details:

    • No JS wiring required: The library checks the event target's classList at interaction time.
    • Nesting support: Because it uses classList.contains(), the exclusion works regardless of how deep the element is nested.
    • Per-gesture isolation: Exclusions are specific to the gesture type. An element with panningDisabled will still allow wheel zooming or pinching unless those are also explicitly excluded.
    <TransformWrapper
      panning={{ excluded: ["panningDisabled"] }}
      wheel={{ excluded: ["wheelDisabled"] }}
      pinch={{ excluded: ["pinchDisabled"] }}
    >
      <TransformComponent>
        <div className="panningDisabled">
          {/* Drag here does NOT pan the canvas */}
          <ScrollableList />
        </div>
        <div className="wheelDisabled">
          {/* Mouse wheel here does NOT zoom */}
          <SomeWidget />
        </div>
        <div className="pinchDisabled">
          {/* Touch pinch here does NOT zoom */}
          <EmbeddedMap />
        </div>
        <p>Normal content — all gestures work here</p>
      </TransformComponent>
    </TransformWrapper>
  9. Performance behavior during large DOM tree interactions

    master
    The library is designed to handle large DOM trees (e.g., a 1,000-image grid) without performance degradation. Pan and zoom operations remain smooth because the library applies a single CSS transform to the container element. Individual child elements are never touched during interactions, preventing expensive layout recalculations for every child during a pan or zoom event.
  10. Resilience to frequent React re-renders

    master
    The TransformComponent is designed to be resilient to frequent React re-renders occurring within its children. Even when the content inside TransformComponent undergoes significant layout restructuring or height changes (e.g., switching between different grid layouts or dashboard views), the current zoom and pan state remains undisturbed. This makes the library suitable for dynamic UIs like live dashboards or data-heavy applications where content structure changes frequently.