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%
andmax-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)
Let's break down the key components:
JavaScript with ResizeObserver:
-
snapToGrid
function calculates the nearest 10% increment - Converts current size to percentage of parent
- Rounds to nearest 20% using
Math.round(percentage / 20) * 20
- Clamps values between 20% and 100%
- ResizeObserver continuously monitors size changes and applies snapping
The ResizeObserver ensures that whenever the user resizes the div, it immediately snaps to the nearest 10% increment, while the CSS transition makes the snapping motion smooth rather than instantaneous.
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
Top comments (0)