A good[ish] website
Web development blog, loads of UI and JavaScript topics
How to resize DOM elements supers easily with pure CSS, or in a more fancy way using React lib.
You know how textareas can be resized easily by dragging from the corner? This same behavior can be activated for any type of element.
For example, the below demo is showing the classic Holy Grail layout. When rendering it, I think it’s important to see the fluidity of the layout, which makes a good use-case for resizable content:
Choose the resizing behavior:
Here’s how that component looks like (nothing too surprising there):
const HolyGrail = () => (
<Wrap>
<Header>Resizable wrapper</Header>
<SidebarLeft>Sidebar content</SidebarLeft>
<Content>
<p>Choose the resizing behavior:</p>
</Content>
<SidebarRight>Sidebar content</SidebarRight>
<Footer>Holy Grail</Footer>
</Wrap>
)
When using it, wrap it to a ResizableContainer
component to make it resizable:
<ResizableContainer>
<HolyGrail />
</ResizableContainer>
That’s pretty simple, check the CSS below.
Here’s the CSS for the resizable container:
.resizable-container {
height: 400px;
overflow: auto;
resize: both;
}
The height
is not necessarily needed, I just like it since it’s a wrapper, then I blast the inner component 100% height. The overflow
should be anything else than visible
.
Possible values for resize
are:
none
both
horizontal
vertical
block
experimentalinline
experimentalIf I understand right, the block
and inline
values are just a different ways of saying horizontal and vertical, because some languages (like Japanese) are written from top to bottom. If my computer would be in Japanese, setting resize
to block
would actually be same as horizontal, where as in my English computer it means vertical.
By setting resize: none
you can easily disable textarea sizing:
Which I think is the only HTML element that has resizing on by default.
This is an old feature, I’ve just learned about it recently heh. The block
and inline
modes only work in FireFox for now (2021).
Iframes can be made resizable the same as any other element, comes handy when displaying demos and such.
I wrapped the Iframe to a div
, and set the resizing to the outer element, then set the Iframe width and height to 100%:
<div style="resize: both; overflow: auto; height: 300px;">
<iframe
src="https://clubmate.fi"
style="width: 100%; height: 100%; border: 1px solid #aaa"
></iframe>
</div>
The only benefit of making something resizable with JavaScript: have more control.
Below is a component made using a simple resize hook I wrote. Compared to the CSS implementation it has few advantages:
There’s 3 parts to the code that renders that box.
I build it as a render prop component, so the size is returned from the function. The usage of it looks like this:
<ResizableComponent options={{ step: 40 }}>
{size => (
<span>
I’m resizable, my size is {size.width} x {size.height}
</span>
)}
</ResizableComponent>
At the heart of it, is the useResize
hook (below), which doesn’t render anything on its own, but merely provides the logic. You feed the hook a ref
to the element you want to resize, and the optional options. Building a hook, rather than a component makes it super flexible, you’re not bound to the limitations of the component, but can make your own.
The resizing hook gives back 3 things: initResize
, size
, and cursor
(more on those below):
import React from 'react'
import PropTypes from 'prop-types'
import { useResize } from './useResize'
import { Handle, Resizable } from './styles'
const ResizableComponent = props => {
const ref = React.useRef()
const { initResize, size, cursor } = useResize(ref, props.options)
return (
<Resizable ref={ref}>
{props.children(size)}
<Handle cursor={cursor} onMouseDown={initResize} />
</Resizable>
)
}
This hooks is pretty basic, you could easily bake in more features to it if needed, like keyboard support, Max/min dimensions and so on. But there’s already many React helpers for resizing components, building this hook was more of an exercise.
See a demo in CodeSandbox:
React.useRef()
both|horizontal|vertical
. Both is the default.An the resize hook will return these three things:
onMouseDown
in your resizing handle.options.axis
. Then you can pass this along into the your resizing handle.import React from 'react'
const cursor = {
both: 'nwse-resize',
vertical: 'ns-resize',
horizontal: 'ew-resize'
}
export const useResize = (ref, options) => {
ref = ref || {}
const { step = 1, axis = 'both' } = options || {}
const [coords, setCoords] = React.useState({ x: Infinity, y: Infinity })
const [dims, setDims] = React.useState({ width: Infinity, height: Infinity })
const [size, setSize] = React.useState({ width: Infinity, height: Infinity })
const initResize = event => {
if (!ref.current) return
setCoords({ x: event.clientX, y: event.clientY })
const { width, height } = window.getComputedStyle(ref.current)
setDims({ width: parseInt(width, 10), height: parseInt(height, 10) })
}
React.useEffect(() => {
// Round the size based to `props.step`.
const getValue = input => Math.ceil(input / step) * step
const doDrag = event => {
if (!ref.current) return
// Calculate the box size.
const width = getValue(dims.width + event.clientX - coords.x)
const height = getValue(dims.height + event.clientY - coords.y)
// Set the box size.
if (axis === 'both') {
ref.current.style.width = width + 'px'
ref.current.style.height = height + 'px'
}
if (axis === 'horizontal') ref.current.style.width = width + 'px'
if (axis === 'vertical') ref.current.style.height = width + 'px'
setSize({ width, height })
}
const stopDrag = () => {
document.removeEventListener('mousemove', doDrag, false)
document.removeEventListener('mouseup', stopDrag, false)
}
document.addEventListener('mousemove', doDrag, false)
document.addEventListener('mouseup', stopDrag, false)
}, [dims, coords, step, ref, axis])
return { initResize, size, cursor: cursor[axis] }
}
Here’s pure JavaScript solution, based heavily to this StackOverflow answer.
See a demo:
Usage:
<div id="box" class="box">Click the element to make it resizable.</div>
The script:
const el = document.getElementById('box')
el.addEventListener('click', init, false)
const coords = { x: Infinity, y: Infinity }
let startWidth, startHeight
function init() {
el.removeEventListener('click', init, false)
el.classList.add('resizable')
makeTheHandle()
}
function makeTheHandle() {
const handle = document.createElement('div')
handle.classList.add('handle')
el.appendChild(handle)
handle.addEventListener('mousedown', initDrag, false)
}
function initDrag(event) {
coords.x = event.clientX
coords.y = event.clientY
startWidth = parseInt(window.getComputedStyle(el).width, 10)
startHeight = parseInt(window.getComputedStyle(el).height, 10)
document.addEventListener('mousemove', doDrag, false)
document.addEventListener('mouseup', stopDrag, false)
}
function doDrag(event) {
el.style.width = startWidth + event.clientX - coords.x + 'px'
el.style.height = startHeight + event.clientY - coords.y + 'px'
}
function stopDrag() {
document.removeEventListener('mousemove', doDrag, false)
document.removeEventListener('mouseup', stopDrag, false)
}
And the styles:
.box {
background-color: Pink;
padding: 20px;
position: relative;
}
.handle {
background-color: Black;
bottom: 0;
cursor: se-resize;
height: 10px;
position: absolute;
right: 0;
width: 10px;
}
I would use the native CSS solution whenever possible, and then fallback to a JS solution if I have design or accessibility needs that are not met by the native resize
.
There are also resizing sensors out there, that detect resizing and return the dimensions etc. Here's just few first ones I found with a quick search:
Thanks for reading. Hope this was helpful.
Comments would go here, but the commenting system isn’t ready yet, sorry.