A recent project involved a small requirement to support batch operations that needed to be triggered using box checks on the interaction. After looking up some data, I found that the schemes on the Internet are basically based on absolute positioning layout. If this scheme is for the global box selection (on the body), it is still available. But the real requirements are almost always a box selection for a certain area. If the use of absolute positioning is more tedious, the need to adjust the origin of positioning. The following introduces a box selection implementation based on Fixed positioning.

Requirements describe

  1. Hold down the left mouse button and move the mouse to appear a selection box
  2. During mouse movement, the elements within the box selection are highlighted
  3. Release the left mouse button to pop up the edit box and batch operate all selected elements

implementation

event

First, sort out the events you need to use. Hold down the left mouse button, since there is no native left mouse button down event, use the Mousedown event in conjunction with the setTimeout simulation implementation. The mouseDown event is bound to the current zone. Use a flag variable mouseOn to indicate whether to start drawing

HandleMouseDown (e) {// Determine if the left mouse button is pressedif(e.buttons ! == 1 || e.which ! = = 1)return;
  this.settimeId = window.setTimeout(() => {
    this.mouseOn = true; // Set the initial position of the checkbox this.startX = e.clientx; this.startY = e.clientY; }, 300); }, handleMouseUp(e) {// Clear the timer when mouseup is held down for less than 300 milliseconds // then the mouseOn is set tofalse
  this.settimeId && window.clearTimeout(this.settimeId)
  if(! this.mouseOn)return;
}
Copy the code

Mouse movement using mousemove event. Mouseup, use the mouseup event, note that the lift event needs to be bound to the document. Because the user’s box selection is not limited to the current area, releasing the mouse at any point should end the box selection drawing.

Marquee drawing

Once the events are identified, it is only a matter of filling in a few events with concrete drawing and judgment logic. Let’s start with the logic of drawing. In the mouseDown event, set the initial position of the checkbox, where the mouse is pressed. So here we have a div that represents the marquee.

<div class="promotion-range__select" ref="select"></div>
.promotion-range__select {
  background: #598fe6;
  position: fixed;
  width: 0;
  height: 0;
  display: none;
  top: 0;
  left: 0;
  opacity:.6;
  pointer-events: none;
}
Copy the code

Press to display the div and set the initial positioning

this.$refs.select.style.cssText = `display:block;
                                   left:${this.startX}px;
                                   top:${this.startY}px width:0; height:0; `;Copy the code

With the initial position, in the Mousemove event, set the width, height and positioning of the marquee.

handleMouseMove(e) {
  if(! this.mouseOn)return;
  const $select = this.$refs.select; const _w = e.clientX - this.startX; const _h = e.clientY - this.startY; // The upper left corner of the box selection rectangle becomes the position where the mouse moves, so it needs to be determined. This. Top = _h > 0? this.startY : e.clientY; this.left = _w > 0 ? this.startX : e.clientX; this.width = Math.abs(_w); this.height = Math.abs(_h);$select.style.left = `${this.left}px`;
  $select.style.top = `${this.top}px`;
  $select.style.width = `${this.width}px`;
  $select.style.height = `${this.height}px`;
},
Copy the code

If absolute positioning is used, it is necessary to calibrate the origin of coordinates, which can be very tedious when multiple relative positioning containers are nested in the layout. Fixed positioning eliminates the need to consider the relative container.

Determine what is selected in the box

/ / to get the target element const selList = document. GetElementsByClassName ("promotion-range__item-inner"
);
const { bottom, left, right, top } = $select.getBoundingClientRect();
for (leti = 0; i < selList.length; i++) { const rect = selList[i].getBoundingClientRect(); const isIntersect = ! ( rect.top > bottom || rect.bottom < top || rect.right < left || rect.left > right ); selList[i].classList[isIntersect ?"add" : "remove"] ("is-editing");
}
Copy the code

Check that getBoundingClientRect is used and define references from MDN

The return value is a DOMRect object, which is a collection of rectangles returned by the element’s getClientRects() method, that is, the collection of CSS borders associated with the element.

The DOMRect object contains a set of read-only attributes — left, top, Right, and bottom — that describe the border, in pixels. All attributes except width and height are relative to the top-left position of the viewport.

As you can see from the definition, the left, top, right, and bottom values obtained in getBoundingClientRect are relative to the top left corner of the viewport, which is consistent with the fixed location definition. Therefore, we only need to compare the four positional values of the checkbox and the selected element.

Rect. top > bottom The selected element is located above the selection box

Rect. bottom < top The boxed element is located below the marquee

Rect. Right < left The selected element is to the left of the checkbox

Rect. left > right The selected element is located to the right of the selection box

The exception to these four cases is that the selection and the selected element have an intersection. Add a class to those divs, because the user needs to be aware of the selected element during the movement, so this method is also implemented in mousemove.

After determining the selected element in mouseup, set the selection box to display: None.

Function Demo address

Github.com/juenanfeng/…

Refer to the link

www.jianshu.com/p/5052c6fd2… Developer.mozilla.org/zh-CN/docs/… Developer.mozilla.org/zh-CN/docs/…