BoxSelect.vue

<template>  <div    class="box-select__container"    @mousedown.left="mouseDown"    @mousemove.stop="mouseMove"    :class="uuid"  >    <div      class="box-select__coordinate"      :style="style"      ref="selectContainer"    ></div>    <slot></slot>  </div></template><script>import { debounce, isNumber } from "lodash"import { ref, onUnmounted, nextTick, shallowRef } from "vue"/** * @description 判断元素是否在范畴内 * @param {Object} dom dom元素 */const isWithinRange = (dom, top, bottom, left, right) => {  const eleRect = dom.getBoundingClientRect()  return !(    eleRect.top > bottom ||    eleRect.bottom < top ||    eleRect.right < left ||    eleRect.left > right  )}export default {  name: "BoxSelect",  /**   * @member props   * @property  {String} [node] 要框选的元素,能够是元素名,也能够是class名, 也能够是id名   * @property  {String} [selectedClass] 已选中元素附加的class名   */  props: {    node: {      required: true,      type: String    },    selectedClass: {      type: String,      default: 'box-select__hypocritical'    }  },  // 鼠标按下  emits: ["mouseUp", "mouseDown"],  setup(props, { emit }) {    let top = 0,      left = 0,      width = 0,      height = 0,      startX = 0,      startY = 0,      timer = null,      // 记录是框选还是点击      mouseOn = false    const style = ref({}),      selectContainer = ref(null),      // 给以后框容器加一个惟一辨认符, 以保障所抉择到的元素都是以后容器的. 否则会抉择到容器外同名的元素      uuid = shallowRef("uuid_" + new Date().valueOf())    const query = (className = '') => {      let domName = `.${uuid.value} ${props.node}`      className && (domName += `.${className}`)      return Array.from(document.querySelectorAll(domName) || [])    }    const classOperation = (ele, method = 'add', className = '') => ele.classList[method](className)    const setStyle = (styles = {}, newStyles = {}) => {      Object.keys(styles).map((item) => {        newStyles[item] = styles[item] + (isNumber(styles[item]) ? "px" : '')      })      style.value = newStyles    }    const getAreaWithinElements = () => {      const {        bottom,        left,        right,        top      } = selectContainer.value.getBoundingClientRect()            // 所有可框选元素      const elements = query()      // 已选中元素      const selectedElements = elements.filter(item => classOperation(item, 'contains', props.selectedClass))      // 未选中元素      const unselectedElements = elements.filter(item => !classOperation(item, 'contains', props.selectedClass))            selectedElements.map(item => {        const withinRange = isWithinRange(item, top, bottom, left, right)        withinRange &&         classOperation(item, 'contains', props.selectedClass) &&         classOperation(item, 'remove', props.selectedClass)      })      unselectedElements.map((item) =>         isWithinRange(item, top, bottom, left, right) &&           classOperation(item, 'add', props.selectedClass))      return query(props.selectedClass)    }    const mouseDown = debounce((event) => {      timer = setTimeout(() => {        mouseOn = true        startX = event.clientX        startY = event.clientY        emit("mouseDown")      }, 300)      // 重置本次框选的元素列表      setStyle({ left, startX, top: startY, width: 0, height: 0, display: "block" })    })    const mouseMove = debounce((event) => {      if (!mouseOn) return false      const _width = event.clientX - startX      const _height = event.clientY - startY      top = _height > 0 ? startY : event.clientY      left = _width > 0 ? startX : event.clientX      width = Math.abs(_width)      height = Math.abs(_height)      setStyle({ left, top, width, height })    })    const mouseUp = debounce((event) => {      timer && clearTimeout(timer)      // 判断是否鼠标左键      if (event.which !== 1) return false      // 判断是框选还是点击      if(!mouseOn) return false      mouseOn = false      setStyle({ display: "none" })      // 取得已选中的元素      const selectedEles = getAreaWithinElements()      // 响应事件,并传递本次框选的元素列表      emit("mouseUp", selectedEles)    })    nextTick(() => document.addEventListener("mouseup", mouseUp))    onUnmounted(() => document.removeEventListener("mouseup", mouseUp))    return {      mouseUp,      mouseDown,      mouseMove,      timer,      style,      selectContainer,      uuid    }  }}</script><style lang="scss">.box-select__container {  .box-select__coordinate {    position: fixed;    z-index: 11;    left: 0;    top: 0;    width: 0;    height: 0;    background: rgba(0, 0, 0, .5);    border:1px solid rgba(0, 0, 0, 1);    opacity: 0.6;    pointer-events: none;  }.box-select__hypocritical {          background-color: blue;        }}</style>

应用办法

<box-select node=".box">   <div class="box"></div>   <div class="box"></div>   <div class="box"></div>   <div class="box"></div></box-select>