EN
JavaScript - resizable div element (on drag and drop actions)
8
points
In this short article, we would like to show how to create div element that may be resizable on drag and drop actions on its borders. Presended solution works on desktop and mobile devices where mouse or touch gestures are used.

Practical example
// ONLINE-RUNNER:browser;
<!doctype html>
<html>
<head>
<style>
* {
/* this style disables elements selection when user is dragging */
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox in the past (old versions) */
-ms-user-select: none; /* Internet Explorer (>=10) / Edge */
user-select: none; /* Currently supported: */
/* Chrome, Opera and Firefox */
}
div.resizable {
position: absolute;
left: 20px; right: 20px;
border: 5px solid gray;
touch-action: none; /* <--------- REQUIRED (blocks other gestures on touch screens) */
}
div.body {
padding: 3px;
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
background: cyan;
overflow: hidden;
}
div.handle {
position: absolute;
background: transparent;
}
div.handle:hover {
background: rgba(255, 255, 255, 0.4);
}
div.handle.horizontal {
top: 0;
right: -5px;
bottom: 0;
width: 5px;
cursor: ew-resize;
}
div.handle.vertical {
left: 0;
right: 0;
bottom: -5px;
height: 5px;
cursor: ns-resize;
}
div.handle.both {
right: -5px;
bottom: -5px;
width: 5px;
height: 5px;
cursor: nwse-resize;
}
</style>
<script>
// Source: https://dirask.com/snippets/JavaScript-custom-pointerdown-pointermove-and-pointerup-events-DW3qzD
//
const addPointerdownListener = (element, callback) => {
const handleMouse = (event) => {
callback(event.clientX, event.clientY, 1.0);
};
const handleTouch = (event) => {
const touch = event.touches[0];
if (touch.identifier === 0) {
callback(touch.clientX, touch.clientY, 0.7);
}
};
element.addEventListener('mousedown', handleMouse);
element.addEventListener('touchstart', handleTouch);
return () => {
element.removeEventListener('mousedown', handleMouse);
element.removeEventListener('touchstart', handleTouch);
};
};
// Source: https://dirask.com/snippets/JavaScript-custom-pointerdown-pointermove-and-pointerup-events-DW3qzD
//
const addPointermoveListener = (element, callback) => {
const handleMouse = (event) => {
callback(event.clientX, event.clientY, 0.7);
};
const handleTouch = (event) => {
const touch = event.touches[0];
if (touch.identifier === 0) {
callback(touch.clientX, touch.clientY, 1.0);
}
};
element.addEventListener('mousemove', handleMouse);
element.addEventListener('touchmove', handleTouch);
return () => {
element.removeEventListener('mousemove', handleMouse);
element.removeEventListener('touchmove', handleTouch);
};
};
// Source: https://dirask.com/snippets/JavaScript-custom-pointerdown-pointermove-and-pointerup-events-DW3qzD
//
const addPointerupListener = (element, callback) => {
const handleMouse = () => {
callback();
};
const handleTouch = () => {
const touch = event.touches[0];
if (touch === undefined || touch.identifier !== 0) {
callback();
}
};
element.addEventListener('mouseup', handleMouse);
element.addEventListener('touchend', handleTouch);
return () => {
element.removeEventListener('mouseup', handleMouse);
element.removeEventListener('touchend', handleTouch);
};
};
const prepareGestures = (element, callbacks) => {
let destroyed = false;
const start = callbacks.start;
const end = callbacks.end;
const doing = callbacks.doing;
const handlePointerdown = (clientX, clientY, speed) => {
const x = clientX;
const y = clientY;
const handlePointermove = (clientX, clientY, speed) => {
if (doing) {
doing(clientX - x, clientY - y, speed);
}
};
const handlePointerup = () => {
removePointermoveListener();
removePointerupListener();
if (end) {
end();
}
};
const removePointermoveListener = addPointermoveListener(document, handlePointermove);
const removePointerupListener = addPointerupListener(document, handlePointerup);
if (start) {
start();
}
};
const removePointerdownListener = addPointerdownListener(element, handlePointerdown);
return {
destroy: () => {
if (destroyed) {
return;
}
removePointerdownListener();
destroyed = true;
}
};
};
const prepareResizing = (element, handles) => {
let destroyed = false;
let width = null;
let height = null;
const style = element.style;
const horizontal = handles.horizontal;
const vertical = handles.vertical;
const both = handles.both;
const removers = new Array();
if (horizontal) {
const callbacks = {
start: function() {
width = element.clientWidth;
},
doing: function(dx, dy) {
style.width = Math.max(0, width + dx) + 'px';
}
};
removers.push(prepareGestures(horizontal, callbacks));
}
if (vertical) {
const callbacks = {
start: function() {
height = element.clientHeight;
},
doing: function(dx, dy) {
style.height = Math.max(0, height + dy) + 'px';
}
};
removers.push(prepareGestures(vertical, callbacks));
}
if (both) {
const callbacks = {
start: function() {
width = element.clientWidth;
height = element.clientHeight;
},
doing: function(dx, dy) {
style.width = Math.max(0, width + dx) + 'px';
style.height = Math.max(0, height + dy) + 'px';
}
};
removers.push(prepareGestures(both, callbacks));
}
return () => {
if (destroyed) {
return;
}
for (const entry of removers) {
entry.destroy(null);
}
destroyed = true;
};
};
</script>
</head>
<body style="height: 300px;">
<div id="element" class="resizable" style="width: 200px; height: 200px;">
<div class="body">
Something inside...
</div>
<div id="horizontal" class="handle horizontal"></div>
<div id="vertical" class="handle vertical"></div>
<div id="both" class="handle both"></div>
</div>
<script>
const element = document.querySelector('#element');
const handles = {
horizontal: document.querySelector('#horizontal'),
vertical: document.querySelector('#vertical'),
both: document.querySelector('#both')
};
prepareResizing(element, handles);
</script>
</body>
</html>