Draggable and resizable boxes

Use height dimensions on wrapper

by Luke Harby

HTML

<div class="outer">
    <div class="wrapper">
        <div class="box">
            I am box 1
        </div>
        <div class="box second">
            I am box 2
        </div>

        <div class="box second third dragging">
            I am box 3
        </div>
    </div>
</div>

CSS

body {
    font-family: monospace;
}

.wrapper {
    min-height: 400vh;
    background-color: rgb(255 0 0 / 10%);
    top: 10px;
    right: 10px;
    bottom: 10px;
    left: 10px;
    overflow-y: scroll;
}

.box {
    position: absolute;
    padding: 10px;
    resize: both;
    height: 150px;
    width: 150px;
    margin-bottom: 20px;
    background: #f0f0f0;
    border: 2px solid #555;
    overflow: auto;
    background-image: linear-gradient(-45deg, #ccc 10px, transparent 10px);
}

.second {
    top: 50px;
    left: 50px;
}

.third {
    top: 100px;
    left: 100px;
}

.heading {
    background: red;
    padding: 5px 10px;
}

.dragging {
    opacity: 0.9;
    border: 2px dashed #8181b9;
    user-select: none;
    z-index: 2;
    cursor: move;
}

.actions {
    display: flex;
    flex-direction: column;
    position: absolute;
    right: 0;
    z-index: 1;
}

JavaScript

const draggingClass = 'dragging';

class draggable {

    constructor(constrainHeight, constrainWidth, element, node = [document]) {
        this.constrainHeight = constrainHeight;
        this.constrainWidth = constrainWidth;
        constrainHeight = false;
        constrainWidth = false;
		this.node = node;
        this.el = element;


        if (this.el) {
            this.mouseDown = this.mouseDown.bind(this);
            this.mouseUp = this.mouseUp.bind(this);
            this.mouseMove = this.mouseMove.bind(this);
        }
    }

    setHandle(handle) {
        this.handle = handle;
        return this;
    }

    setCallback(callback) {
        this.callback = callback;
        return this;
    }

    mouseDown(event) {
        if (this.callback?.start) {
            this.callback.start(event, this.el);
        } else {
            this.el.classList.add(draggingClass);
        }
        for (const node of this.node) {
            node.addEventListener('mouseup', this.mouseUp);
            node.addEventListener('mousemove', this.mouseMove);
            node.addEventListener('mouseleave', this.mouseUp);
        }
        this.el.addEventListener('mouseleave', this.mouseUp);
    }

    mouseUp(event) {
        if (this.callback?.end) {
            this.callback.end(event, this.el);
        } else { 
            this.el.classList.remove(draggingClass);
        }
        for (const node of this.node) {
            node.removeEventListener('mouseup', this.mouseUp);
            node.removeEventListener('mousemove', this.mouseMove);
            node.removeEventListener('mouseleave', this.mouseUp);
        }
        this.el.removeEventListener('mouseleave', this.mouseUp);
    }

    mouseMove(event) {
        if (this.callback?.move) {
            this.callback.move(event, this.el);
        } else {
            const style = window.getComputedStyle(this.el);
            const x = (parseFloat(style.getPropertyValue('left')) || 0) + event.movementX;
           ...