Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a "Lasso tool" to select elements

I am trying to achieve this kind of functionality on a web page:

  1. Select square area by dragging the mouse
  2. Pick all the elements that are in the selected area
  3. Process them

=> So I basically want to create something like "photoshop square selection tool" that would get all the HTML elements that are in the selected area... IS this even possible somehow? Any of you done that or know a js (jQuery) library for that?

like image 477
kosta5 Avatar asked Apr 11 '26 01:04

kosta5


1 Answers

Lasso tool selection using collision

JavaScript Lasso Tool

No need to use jQuery in order to create a rectangular lasso tool.

The logic is not hard.

  • The mouse pointer is treated as a "fixed position element", therefore you'll also need a styled lasso element that has position fixed.
  • When starting a pointerEvent always remember the starting clientX,clientY position
  • When positioning and sizing the lasso element use this simple math:
    x = min(pointerStartX, pointerCurrentX) and
    w = abs(pointerStartX - pointerCurrentX); and do the equivalent for y and h

Lasso tool example:

const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);


const toolLasso = {
  onDown({clientX, clientY}) {
    
    this.startX = clientX;
    this.startY = clientY;
    this.el = elNew("div", {className: "lasso"});
    
    this.onMove = this.onMove.bind(this);  
    this.onUp = this.onUp.bind(this);  
    addEventListener("pointermove", this.onMove);
    addEventListener("pointerup", this.onUp);
    
    // Insert into DOM
    Object.assign(this.el.style, {
      position: `fixed`,
      outline: `2px dashed blue`,
      zIndex: `99999`,
      pointerEvents: `none`,
      userSelect: `none`,
    });
    el("body").append(this.el);
  },
  onMove({clientX, clientY}) {
    this.currX = clientX;
    this.currY = clientY;
    const x = Math.min(this.startX, this.currX);
    const y = Math.min(this.startY, this.currY);
    const w = Math.abs(this.startX - this.currX);
    const h = Math.abs(this.startY - this.currY);
    Object.assign(this.el.style, {
      left: `${x}px`,
      top: `${y}px`,
      width: `${w}px`,
      height: `${h}px`,
    });
    
    // Check elements selection:
    // checkElementsCollision(x, y, w, h);
  },
  onUp() {
    removeEventListener("pointermove", this.onMove);
    removeEventListener("pointerup", this.onUp);
    this.el.remove();
  }
};

addEventListener("pointerdown", (evt) => toolLasso.onDown(evt));
* { margin: 0; box-sizing: border-box; }
body { min-height: 200vh; } /* just to force some demo scrollbars */

For the selecting of elements - the task is pretty basic:

  • onMove call a checkElementsCollision(x, y, w, h) where you basically check if an element or group of elements Element.getBoundingClientRect() x , y , width and height are colliding with the passed x, y, w, h, arguments values of your lasso tool.

const el = (sel, par) => (par || document).querySelector(sel);
const els = (sel, par) => (par || document).querySelectorAll(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);

const collides = (a, b) => 
    a.x < b.x + b.width &&
    a.x + a.width > b.x &&
    a.y < b.y + b.height &&
    a.y + a.height > b.y;

const checkElementsCollision = (x, y, width, height) => {
  els(".box").forEach(elBox => {
    const isColliding = collides({x, y, width, height}, elBox.getBoundingClientRect());
    elBox.classList.toggle("is-selected", isColliding);
  });
};


const toolLasso = {
  onDown({clientX, clientY}) {
    
    this.startX = clientX;
    this.startY = clientY;
    this.el = elNew("div", {className: "lasso"});
    
    this.onMove = this.onMove.bind(this);  
    this.onUp = this.onUp.bind(this);  
    addEventListener("pointermove", this.onMove);
    addEventListener("pointerup", this.onUp);
    
    // Insert into DOM
    Object.assign(this.el.style, {
      position: `fixed`,
      outline: `2px dashed blue`,
      zIndex: `99999`,
      pointerEvents: `none`,
      userSelect: `none`,
    });
    el("body").append(this.el);
  },
  onMove({clientX, clientY}) {
    this.currX = clientX;
    this.currY = clientY;
    const x = Math.min(this.startX, this.currX);
    const y = Math.min(this.startY, this.currY);
    const w = Math.abs(this.startX - this.currX);
    const h = Math.abs(this.startY - this.currY);
    Object.assign(this.el.style, {
      left: `${x}px`,
      top: `${y}px`,
      width: `${w}px`,
      height: `${h}px`,
    });
    
    // Check elements selection:
    checkElementsCollision(x, y, w, h);
  },
  onUp() {
    removeEventListener("pointermove", this.onMove);
    removeEventListener("pointerup", this.onUp);
    this.el.remove();
  }
};

addEventListener("pointerdown", (evt) => toolLasso.onDown(evt));
* { margin: 0; box-sizing: border-box; }
body { min-height: 200vh; } /* just to force some demo scrollbars */

.box {
  position: absolute;
  background: gray;
  width: 40px;
  aspect-ratio: 1;
  left: calc(var(--x) * 1px );
  top: calc(var(--y) * 1px);
}
.box.is-selected {
  background: gold;
}
<div class="box" style="--x:100; --y:100;"></div>
<div class="box" style="--x:150; --y:170;"></div>
<div class="box" style="--x:50; --y:200;"></div>
<div class="box" style="--x:180; --y:10;"></div>

Read more on MDN about: 2D collision detection

like image 156
Roko C. Buljan Avatar answered Apr 18 '26 01:04

Roko C. Buljan