How to make a draggable div resize-able on all 4 corners with pure javascript?

I noticed these resize pointers in the css spec…

https://drafts.csswg.org/css-ui-3/#valdef-cursor-se-resize

Is there a CSS shortcut for 4 corner resizability similar to the one corner (‘resize: both’) method?
If not, are there known conflicts when combining resizability with a draggable div?

My starting point was here…

https://www.w3schools.com/howto/howto_js_draggable.asp

Any help navigating the posX, posY is appreciated.

const myDiv = document.getElementById('mydiv')
//Make the DIV element draggagle:
dragElement(myDiv);
let isResizing = false;

function dragElement(elmnt) {
  let pos1 = 0,
    pos2 = 0,
    pos3 = 0,
    pos4 = 0;
  if (document.getElementById(elmnt.id + "header")) {
    //if present, the header is where you move the DIV from:
    document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
  } else {
    //otherwise, move the DIV from anywhere inside the DIV:
    elmnt.onmousedown = dragMouseDown;
  }

  function dragMouseDown(e) {
    if (!isResizing) {
      e = e || window.event;
      e.preventDefault();
      // get the mouse cursor position at startup:
      pos3 = e.clientX;
      pos4 = e.clientY;
      document.onmouseup = closeDragElement;
      // call a function whenever the cursor moves:
      document.onmousemove = elementDrag;
    }
  }

  function elementDrag(e) {
    if (!isResizing) {

      e = e || window.event;
      e.preventDefault();
      // calculate the new cursor position:
      pos1 = pos3 - e.clientX;
      pos2 = pos4 - e.clientY;
      pos3 = e.clientX;
      pos4 = e.clientY;
      // set the element's new position:
      elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
      elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
    }
  }

  function closeDragElement() {
    //stop moving when mouse button is released:
    document.onmouseup = null;
    document.onmousemove = null;
  }
}

const resizers = document.querySelectorAll('.resizer')
let currentResizer

for (let resizer of resizers) {
  resizer.addEventListener('mousedown', mouseDown)

  function mouseDown(e) {
    currentResizer = e.target
    isResizing = true;

    let posX = e.clientX;
    let posY = e.clientY;
    myDiv.addEventListener('mousemove', mouseMove)
    myDiv.addEventListener('mouseup', mouseUp)

    function mouseMove(e) {
      const rect = myDiv.getBoundingClientRect()
      if (currentResizer.classList.contains('se')) {
        console.log(currentResizer.classList.value)

        myDiv.style.width = rect.width - (posX - e.clientX) + 'px';
        myDiv.style.height = rect.height - (posY - e.clientY) + 'px';
      } else if (currentResizer.classList.contains('sw')) {
        console.log(currentResizer.classList.value)

        myDiv.style.width = rect.width + (posX - e.clientX) + 'px';
        myDiv.style.height = rect.height - (posY - e.clientY) + 'px';
        myDiv.style.left = rect.left - (posX - e.clientX) + 'px';

      } else if (currentResizer.classList.contains('ne')) {
        console.log(currentResizer.classList.value)

        myDiv.style.width = rect.width - (posX - e.clientX) + 'px';
        myDiv.style.height = rect.height + (posY - e.clientY) + 'px';
        myDiv.style.top = rect.top - (posY - e.clientY) + 'px';

      } else {
        console.log(currentResizer.classList.value)

        myDiv.style.width = rect.width + (posX - e.clientX) + 'px';
        myDiv.style.height = rect.height + (posY - e.clientY) + 'px';
        myDiv.style.top = rect.top - (posY - e.clientY) + 'px';
        myDiv.style.left = rect.left - (posX - e.clientX) + 'px';

      }
      posX = e.clientX;
      posY = e.clientY;
    }

    function mouseUp(e) {
      myDiv.removeEventListener('mousemove', mouseMove)
      myDiv.removeEventListener('mouseup', mouseUp)
      isResizing = false
    }
  }
}
* {
  margin: 0;
}

#mydiv {
  position: absolute; /* NECESSARY */
  background-color: whitesmoke;
  text-align: center;
  border: 1px solid #222;
  height: 200px;
  width: 200px;
  /* resize: both; /* CSS RESIZE */
  overflow: hidden; /* CSS RESIZE */
}

#mydivheader {
  padding: 10px;
  cursor: move;
  background-color: dodgerblue;
  color: #fff;
}

#content {
  color: #000;
  margin: 0px;
  background-color: whitesmoke;
}


/* ::-webkit-resizer {
  position: absolute;
  height: 20px;
  width: 20px;
  border-top-left-radius: 25px;
  background-color: #dd0;
  z-index: 2;
} */

.resizer {
  position: absolute;
  height: 20px;
  width: 20px;
  background-color: #dd0;
  z-index: 2;
}

.resizer.nw {
  top: -1px;
  left: -1px;
  cursor: nw-resize;
  border-bottom-right-radius: 25px;
}

.resizer.ne {
  top: -1px;
  right: -1px;
  cursor: ne-resize;
  border-bottom-left-radius: 25px;
}

.resizer.sw {
  bottom: -1px;
  left: -1px;
  cursor: sw-resize;
  border-top-right-radius: 25px;
}

.resizer.se {
  bottom: -1px;
  right: -1px;
  cursor: se-resize;
  border-top-left-radius: 25px;
}
<div id="mydiv">
  <div class='resizer ne'></div>
  <div class='resizer nw'></div>
  <div class='resizer sw'></div>
  <div class='resizer se'></div>

  <div id="mydivheader">Click here to move

    <div id='content'>

      <div id='image-container'><img height='auto' width='100%' src='https://picsum.photos/600' /></div>

    </div>
  </div>
</div>

2 thoughts on “How to make a draggable div resize-able on all 4 corners with pure javascript?”

  1. So, you want the resize handle to appear on all four corners? That’ll require some bounds checking.

    You can modify the offset check in drag to account for any corner and handle the drag as a resize event instead. The code somewhat works for top-left and bottom-right resizing, but doe not work too well with the opposite corners. This is a start.

    This is a start:

    const cursor = document.querySelector('#cursor');
    const cornerThreshold = 4;
    
    const dragStart = e => {
      const [ horz, vert ] = getDirection(e, cornerThreshold);
      const bounds = e.target.getBoundingClientRect();
      const cursor = getCursorType(e);
      
      if (cursor === 'grab') {
        e.target.dataset.isDragging = true;
      } else {
        e.target.dataset.isResizing = true;
      }
    
      e.target.dataset.startWidth = bounds.width;
      e.target.dataset.startHeight = bounds.height;
      e.target.dataset.originX = e.clientX;
      e.target.dataset.originY = e.clientY;
      e.target.dataset.offsetX = e.clientX - e.target.offsetLeft;
      e.target.dataset.offsetY = e.clientY - e.target.offsetTop;
      e.target.dataset.dirHorz = horz;
      e.target.dataset.dirVert = vert;
      e.target.style.zIndex = 999;
    };
    
    const dragEnd = e => {
      delete e.target.dataset.isDragging;
      delete e.target.dataset.offsetX;
      delete e.target.dataset.offsetY;
      delete e.target.dataset.originX;
      delete e.target.dataset.originY;
      delete e.target.dataset.startWidth;
      delete e.target.dataset.startHeight;
      delete e.target.dataset.dirHorz;
      delete e.target.dataset.dirVert;
      delete e.target.dataset.resizeDirection;
      
      e.target.style.removeProperty('z-index');
      e.target.style.removeProperty('cursor');
    };
    
    const drag = e => {
      e.target.style.cursor = getCursorType(e);
      cursor.textContent = `(${e.clientX}, ${e.clientY})`;
      if (e.target.dataset.isDragging) {
        e.target.style.left = `${e.clientX - parseInt(e.target.dataset.offsetX, 10)}px`;
        e.target.style.top = `${e.clientY - parseInt(e.target.dataset.offsetY, 10)}px`;
      } else if (e.target.dataset.isResizing) {
        const bounds = e.target.getBoundingClientRect();
        
        const startWidth = parseInt(e.target.dataset.startWidth, 10);
        const startHeight = parseInt(e.target.dataset.startWidth, 10);
        const deltaX = e.clientX - parseInt(e.target.dataset.originX, 10);
        const deltaY = e.clientY - parseInt(e.target.dataset.originY, 10);
        const originX = parseInt(e.target.dataset.originX, 10);
        const originY = parseInt(e.target.dataset.originY, 10);
        const dirHorz = parseInt(e.target.dataset.dirHorz, 10);
        const dirVert = parseInt(e.target.dataset.dirVert, 10);
        
        if (dirHorz < 0) {
          e.target.style.left = `${originX + deltaX}px`;
          e.target.style.width = `${startWidth - deltaX}px`
        } else if (dirHorz > 0) {
          e.target.style.width = `${startWidth + deltaX}px`;
        }
        if (dirVert < 0) {
          e.target.style.top = `${originY + deltaY}px`;
          e.target.style.height = `${startHeight - deltaY}px`;
        } else if (dirVert > 0) {
          e.target.style.height = `${startHeight + deltaY}px`;
        }
      }
    };
    
    const focus = e => { };
    
    const unfocus = e => { e.target.style.removeProperty('cursor'); };
    
    const getDirection = (e, threshold) => {
      const bounds = e.target.getBoundingClientRect();
      const offsetX = e.clientX - e.target.offsetLeft;
      const offsetY = e.clientY - e.target.offsetTop;
    
      const isTop = offsetY <= threshold;
      const isLeft = offsetX <= threshold;
      const isBottom = offsetY > (bounds.height - threshold);
      const isRight = offsetX > (bounds.width - threshold);
      
      if      (isTop    && isLeft)  return [ -1, -1 ];
      else if (isTop    && isRight) return [ -1,  1 ];
      else if (isBottom && isLeft)  return [  1, -1 ];
      else if (isBottom && isRight) return [  1,  1 ];
      else                          return [  0,  0 ];
    };
    
    const getCursorType = (e) => {
      if (e.target.dataset.isDragging) {
        return 'grabbing';
      } else {
        const [ horz, vert ] = getDirection(e, cornerThreshold);
    
        const isTop = vert === -1;
        const isLeft = horz === -1;
        const isBottom = vert === 1;
        const isRight = horz === 1;
      
        if ((isTop && isLeft) || (isBottom && isRight)) return 'nwse-resize';
        if ((isTop && isRight) || (isBottom && isLeft)) return 'nesw-resize';
      }
      return 'grab';
    };
    
    document.querySelectorAll('.draggable').forEach(draggable => {
     draggable.addEventListener('mousedown', dragStart);
     draggable.addEventListener('mouseup', dragEnd);
     draggable.addEventListener('mousemove', drag);
     draggable.addEventListener('mouseenter', focus);
     draggable.addEventListener('mouseleave', unfocus);
    });
    html, body {
      margin: 0;
      padding: 0;
      width: 100%;
      height: 100%;
    }
    
    .container {
      position: relative;
      width: 60%;
      height: 60%;
      border: thin solid grey;
    }
    
    .square {
      position: absolute;
      height: 2em;
      width: 2em;
    }
    
    .color-red { background: red; }
    .color-blue { background: blue; }
    .color-green { background: green; }
    
    #square-1 { top:  10px; left:  10px; }
    #square-2 { top:  50px; left:  50px; }
    #square-3 { top: 100px; left: 100px; }
    <div class="container">
      <div id="square-1" class="square color-red draggable resizable"></div>
      <div id="square-2" class="square color-blue draggable resizable"></div>
      <div id="square-3" class="square color-green draggable resizable"></div>
    </div>
    <br />
    <div>Cursor: <span id="cursor">(0, 0)</span></div>
    Reply
  2. Well if you want to make it nice and easy you could use jQuery to help it resize would probably be the easiest way. Then after you learn how to do it with jQuery you could do it with pure js.

    Reply

Leave a Comment