Select elements by dragging a box

by Génesis García Morilla

HTML

<ul></ul>

CSS

ul {
  list-style-type: none;
  display: flex;
  flex-flow: row wrap;
}

li {
  width: 80px;
  height: 80px;
  margin: 2px;
  font-size: 2rem;
  color: darkslategrey;
  display: flex;
  justify-content: center;
  align-items: center;
  user-select: none;
}

#box {
  position: absolute;
  left: 0;
  top: 0;
  border: 1px dotted darkslategrey;
}

.selected {
  background: #FECA40;
}

JavaScript

document.querySelector('ul').innerHTML = [...Array(1000).keys()]
  .reduce((sum, _, i) => sum + `<li>${i}</li>`, '')
  
// Select elements by dragging a box
let x0, y0
const $$li = document.querySelectorAll('li')

function is_inside(a, b, x, y) {
  return a <= x && a >= x0 && b <= y && b >= y0
}

document.onmousedown = ({ pageX: x, pageY: y } = event) => {
  x0 = x, y0 = y
  $$li.forEach($li => $li.classList.remove('selected'))
  
  let box = document.createElement('div')
  box.id = 'box'
  document.body.append(box)
  box.style.left = `${x0}px`
  box.style.top = `${y0}px`
  
  document.onmousemove = ({ pageX: x, pageY: y } = event) => {
    if (x0 > x) box.style.left = `${x}px`
    if (y0 > y) box.style.top = `${y}px`
    box.style.width = `${Math.abs(x - x0)}px`
    box.style.height = `${Math.abs(y - y0)}px`
  }
}

document.onmouseup = ({ pageX: x, pageY: y } = event) => {
  document.querySelector('#box').remove()
  document.onmousemove = null

  // Swap variables to allow selections from all directions
  if (x0 > x) [x0, x] = [x, x0]
  if (y0 > y) [y0, y] = [y, y0]
  if (x - x0 < 15 && y - y0 < 15) return

  // Make selection at least as big as the element where it begins
  let { left, top, width, height } =
    document.elementFromPoint(x0 - scrollX, y0 - scrollY)
    .getBoundingClientRect()  

  if (x - x0 < width) x0 = left + scrollX
  if (y - y0 < height) y0 = top + scrollY
  
  $$li.forEach($li => {
    let { left: a, top: b, width: w, height: h } = 
      $li.getBoundingClientRect()
      
    a += scrollX
    b += scrollY

    if ([[a, b], [a + w, b], [a, b + h], [a + w, b + w]]
      .some(coo => is_inside(...coo, x, y))) 
      $li.classList.add('selected')
  })
}