Skip to content

A draggable and zoomable grid system for placing items.

License

elfatairy/dynamic-gridline

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Contributors Forks Stargazers Issues project_license LinkedIn


Logo

Dynamic Gridline

A draggable and zoomable grid system for placing items.

Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Roadmap
  5. Contributing
  6. License
  7. Contact
  8. Acknowledgments

About The Project

Demo Dynamic Gridline

Built With

  • React
  • Motion

Getting Started

Prerequisites

# npm
npm install motion @radix-ui/react-slider

# yarn
yarn add motion @radix-ui/react-slider

# pnpm
pnpm add motion @radix-ui/react-slider

# bun
bun install motion @radix-ui/react-slider

Installation

# npm
npm install dynamic-gridline

# yarn
yarn add dynamic-gridline

# pnpm
pnpm add dynamic-gridline

# bun
bun install dynamic-gridline

Usage

Basic Setup

Import the components and start building your interactive grid:

import { Grid, GridItem } from 'dynamic-gridline'

function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Grid>
        <GridItem x={100} y={100}>
          <div>Your content here</div>
        </GridItem>
      </Grid>
    </div>
  )
}
Screenshot 2025-09-21 011243

Core Components

Grid Component

The Grid component is the main container that provides the interactive canvas with pan, zoom, and grid functionality.

import { Grid } from 'dynamic-gridline'

function InteractiveCanvas() {
  return (
    <Grid
      config={{
        width: 10000,
        height: 8000,
        gridCellSize: 50,
        minZoom: 0.1,
        maxZoom: 2,
        gridColor: '#e0e0e0',
      }}
    >
      {/* Your grid items go here */}
    </Grid>
  )
}
Screenshot 2025-09-21 011342

GridItem Component

The GridItem component positions content within the grid coordinate system.

import { Grid, GridItem } from 'dynamic-gridline'

function ItemPlacement() {
  return (
    <Grid>
      <GridItem x={0} y={0}>
        <div style={{ background: 'red', text: 'white', padding: '10px' }}>(0,0)</div>
      </GridItem>
      <GridItem x={200} y={150}>
        <div style={{ background: 'blue', text: 'white', padding: '10px' }}>(200,150)</div>
      </GridItem>
      <GridItem x={-150} y={-50}>
        <div style={{ background: 'green', text: 'white', padding: '10px' }}>(-150, -50)</div>
      </GridItem>
    </Grid>
  )
}
Screenshot 2025-09-21 011534

Advanced Features

Disable Scale for Grid Items

Control whether items scale with zoom using the disableScale property:

function ScalingDemo() {
  return (
    <Grid config={{ minZoom: 0.5, maxZoom: 2 }}>
      {/* This circle will scale with zoom */}
      <GridItem x={-100} y={0} disableScale={false}>
        <div
          style={{
            width: 120,
            height: 120,
            color: 'white',
            borderRadius: '50%',
            background: 'blue',
          }}
        ></div>
      </GridItem>

      {/* This circle maintains constant size */}
      <GridItem x={100} y={0} disableScale={true}>
        <div
          style={{
            width: 120,
            height: 120,
            color: 'white',
            borderRadius: '50%',
            background: 'red',
          }}
        ></div>
      </GridItem>
    </Grid>
  )
}
Screenshot 2025-09-21 011732 Screenshot 2025-09-21 013624

Fixed Z-Index Control

Control layering of grid items with fixedZIndex:

function LayeringDemo() {
  return (
    <Grid>
      <GridItem x={-50} y={-50} fixedZIndex={1}>
        <div
          style={{
            width: 100,
            height: 100,
            background: 'rgba(255,0,0,0.7)',
            color: 'white',
          }}
        >
          Behind (z-index: 1)
        </div>
      </GridItem>

      <GridItem x={0} y={0} fixedZIndex={10}>
        <div
          style={{
            width: 100,
            height: 100,
            background: 'rgba(0,0,255,0.7)',
            color: 'white',
          }}
        >
          In Front (z-index: 10)
        </div>
      </GridItem>
    </Grid>
  )
}
Screenshot 2025-09-21 011845

Interaction Features

Click Event Handlers

Handle fast clicks and hold clicks on the grid:

function ClickHandling() {
  const handleFastClick = ({ x, y }: { x: number; y: number }) => {
    console.log('Fast click at:', x, y)
    // Add logic for quick interactions
  }

  const handleHoldClick = ({ x, y }: { x: number; y: number }) => {
    console.log('Hold click at:', x, y)
    // Add logic for context menus or long-press actions
  }

  return (
    <Grid
      config={{
        onFastClick: handleFastClick,
        onHoldClick: handleHoldClick,
      }}
    >
      <GridItem x={0} y={0}>
        <div>Try clicking and holding on the grid!</div>
      </GridItem>
    </Grid>
  )
}

Mouse Movement Tracking

Track mouse position within the grid coordinate system:

function MouseTracking() {
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 })

  const handleMouseMove = ({ x, y }: { x: number; y: number }) => {
    setMousePos({ x, y })
  }

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <p style={{ position: 'absolute', top: 0, left: 0 }}>
        Mouse position: ({mousePos.x.toFixed(2)}, {mousePos.y.toFixed(2)})
      </p>
      <Grid config={{ onMouseMove: handleMouseMove }}>
        <GridItem x={mousePos.x} y={mousePos.y}>
          <div
            style={{
              width: 10,
              height: 10,
              background: 'red',
              borderRadius: '50%',
            }}
          />
        </GridItem>
      </Grid>
    </div>
  )
}

Untitled video - Made with Clipchamp

Navigation Controls

Keyboard Navigation

Navigate the grid using arrow keys:

function KeyboardNavigation() {
  return (
    <Grid
      config={{
        panStep: 50, // Pixels to move per arrow key press
        keyDisabled: false, // Enable keyboard controls
      }}
    >
      <GridItem x={0} y={0}>
        <div>Use arrow keys to pan around!</div>
      </GridItem>
    </Grid>
  )
}

Zoom Controls

Built-in zoom slider and wheel zoom functionality:

function ZoomDemo() {
  return (
    <Grid
      config={{
        minZoom: 0.25,
        maxZoom: 4,
        zoomSteps: 50,
        wheelDisabled: false, // Enable mouse wheel zoom
      }}
    >
      <GridItem x={0} y={0}>
        <div>Use mouse wheel or slider to zoom!</div>
      </GridItem>
    </Grid>
  )
}

Customization Options

Custom Grid Appearance

Customize the grid's visual appearance:

function CustomStyling() {
  return (
    <Grid
      config={{
        gridBackground: '#1b2845',
        gridColor: '#4f88cb', // Custom grid line color
        gridCellSize: 30, // Smaller grid cells
      }}
    >
      <GridItem x={0} y={0}>
        <div style={{ color: 'white' }}>Custom styled grid!</div>
      </GridItem>
    </Grid>
  )
}
Screenshot 2025-09-21 012631

Custom Zoom Slider

Replace the default zoom slider with your own component:

function CustomZoomSlider() {
  const customSlider = (sliderProps: SliderProps) => (
    <div
      style={{
        position: 'absolute',
        top: 20,
        right: 20,
        background: 'white',
        padding: '10px',
        borderRadius: '8px',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
      }}
    >
      <label>Zoom: {sliderProps.zoomValue.toFixed(2)}x</label>
      <input
        type="range"
        min={Math.log10(sliderProps.minZoom)}
        max={Math.log10(sliderProps.maxZoom)}
        step={
          (Math.log10(sliderProps.maxZoom) - Math.log10(sliderProps.minZoom)) /
          (sliderProps.zoomSteps - 1)
        }
        value={Math.log10(sliderProps.zoomValue)}
        onChange={(e) => sliderProps.handleZoom(10 ** parseFloat(e.target.value))}
      />
    </div>
  )

  return (
    <Grid config={{ customZoomSlider: customSlider }}>
      <GridItem x={0} y={0}>
        <div>Custom zoom control!</div>
      </GridItem>
    </Grid>
  )
}
Screenshot 2025-09-21 012823

Disabling Features

Selective Feature Disabling

Disable specific interactions while keeping others active:

function SelectiveDisabling() {
  return (
    <div
      style={{
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        width: '100vw',
        height: '100vh',
        gap: '20px',
      }}
    >
      <div style={{ width: '30vw', aspectRatio: '1/1' }}>
        <Grid config={{ panDisabled: true, wheelDisabled: false }}>
          <GridItem x={0} y={0}>
            <div>Zoom only</div>
          </GridItem>
        </Grid>
      </div>
      {/* Wheel zoom disabled, pan still works */}
      <div style={{ width: '30vw', aspectRatio: '1/1' }}>
        <Grid config={{ panDisabled: false, wheelDisabled: true }}>
          <GridItem x={0} y={0}>
            <div>Pan only</div>
          </GridItem>
        </Grid>
      </div>
      {/* Everything disabled */}
      <div style={{ width: '30vw', aspectRatio: '1/1' }}>
        <Grid config={{ disabled: true }}>
          <GridItem x={0} y={0}>
            <div>Static view</div>
          </GridItem>
        </Grid>
      </div>
    </div>
  )
}
Screenshot 2025-09-21 013157

Grid Reference and Imperative Controls

Access grid methods using a ref:

import { useRef } from 'react'
import { Grid, GridRef } from 'dynamic-gridline'

function GridWithRef() {
  const gridRef = useRef<GridRef>(null)

  const focusGrid = () => {
    gridRef.current?.focusGrid()
  }

  return (
    <div>
      <button onClick={focusGrid}>Focus Grid (for keyboard controls)</button>
      <Grid ref={gridRef}>
        <GridItem x={0} y={0}>
          <div>Grid with ref access</div>
        </GridItem>
      </Grid>
    </div>
  )
}

Configuration Reference

The Grid component accepts a config prop with the following options:

Property Type Default Description
width number window.innerWidth * 10 Total width of the grid canvas in pixels
height number window.innerHeight * 10 Total height of the grid canvas in pixels
gridCellSize number 20 Size of each grid cell in pixels
gridBackground string 'transparent' Background color of the grid container
gridColor string 'oklch(70.7% 0.022 261.325)' Color of the grid lines
minZoom number 0.1 Minimum zoom level (0.1 = 10%)
maxZoom number 1 Maximum zoom level (1 = 100%)
zoomSteps number 100 Number of steps in the zoom slider
panStep number 10 Pixels to move when using keyboard navigation
disabled boolean false Disable all interactions (pan, zoom, keyboard)
panDisabled boolean false Disable panning (mouse drag and touch)
wheelDisabled boolean false Disable mouse wheel zooming
keyDisabled boolean false Disable keyboard navigation
customZoomSlider ((props: SliderProps) => ReactNode) | null null Custom zoom slider component
onFastClick ({ x, y }: { x: number; y: number }) => void undefined Callback for quick clicks on the grid
onHoldClick ({ x, y }: { x: number; y: number }) => void undefined Callback for long presses on the grid
onMouseMove ({ x, y }: { x: number; y: number }) => void undefined Callback for mouse movement over the grid

GridItem Properties

Property Type Default Description
x number Required X coordinate in grid space
y number Required Y coordinate in grid space
disableScale boolean false Prevent item from scaling with zoom
fixedZIndex number undefined Fixed z-index for layering control
children ReactNode Required Content to render in the grid item

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Top contributors:

contrib.rocks image

License

Distributed under the MIT License. See LICENSE for more information.

Contact

Omar Hassan - @omar_elfat76510 - [email protected]

Project Link: https://github.com/elfatairy/dynamic-gridline

Portfolio: https://omarhassan.net

(back to top)

About

A draggable and zoomable grid system for placing items.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published