-1

How to implement drag-select (make HTML elements selectable by area mouse selecting)?

I'm interested in the element selecting like it is in file explorers (of Windows, Linux) and many other sites that provide a file explorer behaviour (dropbox.com, google.drive, mega.nz, js-fileexplorer demo).

I mean the selecting when you create a rectangular area by mouse move with all elements are touched by this area are "selected" (for example, an extra CSS class is added to them).

How to do it? Best approach?

KeyKi
  • 2,693
  • 3
  • 12
  • 24
  • 1
    A simple way would be to have a layout where these elements are placed. Then keep track of their position, and on mouseclick, keep track of initial click x,y positions until release. Any elements underneath that area, you would then add a class to. – DragonInTraining Mar 31 '23 at 15:42
  • Well, keeping coordinates (`getBoundingClientRect`) of each selectable `div` and checking the intersection of the selecting area with them should be OK. – KeyKi Mar 31 '23 at 19:17
  • Please post what you have tried and the one challenge that presents you so we may best assist you here. Asking for a recommendation is out of scope for this site. – Mark Schultheiss Mar 31 '23 at 19:19

1 Answers1

0

Here is it.

Just keep {x, y, width, height} from getBoundingClientRect for each selectable item, then check the rectangle intersection of each item with the selecting area in a loop.

There are 29 lines of code from here to create the select area rectangle (with only 2 extra lines), 5 lines to check the rectangle intersection, 5 lines to create demo divs, and only 17 lines for the main logic.

It works fine even with scrolling while selecting.

for (let i = 0; i < 50; i++) {
  const div = document.createElement("div");
  div.style.backgroundColor = `hsl(${Math.random() * 360}, 100%, 50%)`;
  div.classList.add("selectable");
  document.body.append(div);
}

// --- Main ---
const selectables = [];
const selectableElems = [...document.querySelectorAll(".selectable")];
for (const selectable of selectableElems) {
  const {x, y, width, height} = selectable.getBoundingClientRect();
  selectables.push({x: x + window.scrollX, y: y + window.scrollY, width, height, elem: selectable});
  selectable.dataset.info = JSON.stringify({x, y, width, height});
}

function checkSelected(selectAreaElem) {
  const select = selectAreaElem.getBoundingClientRect();
  const {x, y, height, width} = select;
  for (const selectable of selectables) {
    if (checkRectIntersection({x: x + window.scrollX, y: y + window.scrollY, height, width}, selectable)){
      selectable.elem.classList.add("intersected");
    } else {
      selectable.elem.classList.remove("intersected");
    }
  }
}
// ------------

function checkRectIntersection(r1, r2) {
  return !(r1.x + r1.width  < r2.x ||
           r2.x + r2.width  < r1.x ||
           r1.y + r1.height < r2.y ||
           r2.y + r2.height < r1.y);
}

addEventListener("pointerdown", createDiv);
async function createDiv(event){
  event.preventDefault();
  const x = event.pageX;
  const y = event.pageY;

  const div = document.createElement("div");
  div.style.position = "absolute";
  div.style.width = "0";
  div.style.height = "0";
  div.style.left = x + "px";
  div.style.top = y + "px";
  div.classList.add("drag-select");
  document.body.append(div);

  function resize(event) {
    const diffX = event.pageX - x;
    const diffY = event.pageY - y;
    div.style.left = diffX < 0 ? x + diffX + "px" : x + "px";
    div.style.top = diffY < 0 ? y + diffY + "px" : y + "px";
    div.style.height = Math.abs(diffY) + "px";
    div.style.width = Math.abs(diffX) + "px";
    checkSelected(div);
  }
  selectables.forEach(item => item.elem.classList.remove("intersected"));
  addEventListener("pointermove", resize);
  addEventListener("pointerup", () => {
    removeEventListener("pointermove", resize);
    div.remove();
  });
}
html, body {
  display: inline-grid;
  justify-content: center;
  width: 100%;
}
div {
  width: 128px;
  height: 32px;
}
.drag-select {
  background-color: rgba(20, 137, 189, 0.5);
}
.intersected {
  border: 5px solid black;
  box-sizing: border-box;
}
KeyKi
  • 2,693
  • 3
  • 12
  • 24