← back to articles

Resizing a div using CSS resize, but the resizing is done at a fixed dimension (snap grid)



Published on Feb 28, 2025 - Last updated on Feb 28, 2025

#ui#css#javascript#programming

TLDR; View the working example on jsfiddle.net

Let’s create a solution that resizes a div at fixed 50px increments of its parent container, with smooth transitions, debouncing and ResizeObserver for monitoring.

HTML Setup:

<div class="container">
  <div class="resizable"></div>
</div>

CSS Setup:

.container {
  width: 500px;
  height: 400px;
  border: 2px solid #ccc;
  position: relative;
  overflow: hidden;
}

.resizable {
  width: 250px;
  height: 200px;
  background: #3498db;
  position: absolute;
  resize: both; /* This enables the resizing */
  overflow: auto;
  min-width: 50px;
  min-height: 50px;
  max-width: 100%;
  max-height: 100%;
  transition: all 0.1s ease-out; /* This is important to ensure a smooth drag */
}
  • resize: both enables resizing in both directions
  • transition: all 0.1s ease-out adds smooth transitions to reduce visual glitching
  • min-width/height: 10% and max-width/height: 100% set the boundaries
  • overflow: auto handles content overflow
  • Container has overflow: hidden to prevent content spilling

Javascript Setup:

const resizable = document.querySelector(".resizable")
const container = document.querySelector(".container")

const snapPercent = 20 // Change to fit your needs
const snapPixel = 50 // Change to fit your needs
const usePixels = false // Change this to false to use percentage.

// Function to snap to fixed pixels increments
function snapToGridPixel(size, parentSize) {
  const snappedSize = Math.round(size / snapPixel) * snapPixel
  const value = Math.max(snapPixel, Math.min(snappedSize, parentSize))
  return Math.min(value, parentSize) + "px"
}

// Function to snap to fixed percentage increments
function snapToGridPercentage(size, parentSize) {
  const percentage = (size / parentSize) * 100
  const snappedPercentage = Math.round(percentage / snapPercent) * snapPercent
  return Math.max(snapPercent, Math.min(100, snappedPercentage)) + "%"
}

function snapToGrid(size, parentSize) {
  return usePixels
    ? snapToGridPixel(size, parentSize)
    : snapToGridPercentage(size, parentSize)
}

// Debounce function
function debounce(func, delay) {
  let timeoutId
  return function (...args) {
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

// Debounced resize handler
const handleResize = debounce((entries) => {
  for (let entry of entries) {
    const { width, height } = entry.contentRect
    const parentWidth = container.offsetWidth
    const parentHeight = container.offsetHeight

    // Snap to fixed increments
    const newWidth = snapToGrid(width, parentWidth)
    const newHeight = snapToGrid(height, parentHeight)

    // Apply the snapped sizes with max bounds
    resizable.style.width = newWidth
    resizable.style.height = newHeight
  }
}, 100) // 100ms delay

// ResizeObserver with debounced handler
const resizeObserver = new ResizeObserver(handleResize)

// Start observing the resizable element
resizeObserver.observe(resizable)

Key Components Explained

ResizeObserver

The ResizeObserver continuously monitors size changes and applies snapping whenever the user resizes the div. It immediately snaps to the nearest increment, while the CSS transition makes the snapping motion smooth rather than instantaneous.

Snap to Grid Function

The snapToGrid function calculates the nearest fixed increment:

  • Converts current size to percentage or pixels of parent
  • Rounds to nearest increment using Math.round(size / increment) * increment
  • Clamps values between minimum and maximum bounds

Debouncing

Debouncing ensures that a function only runs after a certain period of inactivity. It delays execution until after the triggering event has stopped firing for a specified amount of time.

Why Use Debouncing?

  • Performance: Prevents excessive function calls that could slow down your application
  • Efficiency: Reduces unnecessary computations or API calls
  • User Experience: Ensures actions complete only when the user has finished interacting