今天在某前端群里看到有群友提到,想到以后可能会碰到类似的场景,于是就动手敲了敲

1️⃣ eg

2️⃣ About

1. 监听鼠标的 按下 & 移动 & 抬起

onmousedown onmousemove onmouseup
document.onmousedown = (e) => {
    document.onmousemove = (e2) => {
        selectHandle(e.pageX, e.pageY, e2.pageX, e2.pageY)
    }
    document.onmouseup = (e3) => {
        cleanSelect()
        document.onmousemove = null
    }
}

按下 (onmousedown) 时拿到开始坐标并记录,移动 (onmousemove) 后的结果坐标为结束坐标,在两坐标 x轴 & y轴 相交范围内的 Dom 即为需要添加选中的

鼠标抬起 (onmouseup) 时清空鼠标移动监听

2. 选中状态的添加

document.elementFromPoint(x, y)

用到了根据坐标获取 Dom元素,同时也是实现的关键

(这个函数印象中我几乎没有实际使用过 🤔

/**
* 选中处理
* @param beginX
* @param beginY
* @param endX
* @param endY
*/
const selectHandle = (beginX, beginY, endX, endY) => {
    cleanSelect()
    /**
    * x - 记录当前列的坐标 x,在一列处理完后根据 dom元素 的(宽度 + x坐标)进行累加
    * y - 记录当前行的坐标 y,在一行处理完后根据 dom元素 的(高度 + y坐标)进行累加
    * isReverseY - Y轴移动方向,向上移动为 true,向下为 false
    * isReverseX - X轴移动方向,向左移动为 true,向右为 false
    * range - 允许的选中偏差
    */
    let x = beginX, y = beginY, isReverseY = beginY > endY, isReverseX = beginX > endX, range = 10
    const xHandle = () => {
        const yHandle = () => {
            let e = document.elementFromPoint(x, y)
            if (e) {
                addDomToArr(e)
                y = isReverseY ? e.offsetTop - range : e.clientHeight + e.offsetTop + range
                if (isReverseY ? y >= endY : y <= endY) xHandle()
            }
        }
        let e = document.elementFromPoint(x, y)
        if (e) {
            yHandle()
            y = beginY
            x = isReverseX ? e.offsetLeft - range : e.clientWidth + e.offsetLeft + range
            if (isReverseX ? x >= endX : x <= endX) xHandle()
        }
    }
    xHandle()
    
    // 改变选中元素的边框颜色
    arr.forEach(v => {
        v.style.border = '2px solid #F8CBA6'
    })
}
/**
* 将dom元素添加至 `arr(已选中)`
* @param dom
*/
const addDomToArr = (dom) => {
    if (dom.parentNode.getAttribute('class') != 'container') return
    let has = false
    arr.forEach(v => {
        // 判断是否已存在
        if (v.offsetLeft == dom.offsetLeft && v.offsetTop == dom.offsetTop) {
            has = true
            return
        }
    })
    if (!has) arr.push(dom)
}

Source Code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div class="container">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
    <div>7</div>
    <div>8</div>
    <div>9</div>
    <div>10</div>
    <div>11</div>
    <div>12</div>
    <div>13</div>
    <div>14</div>
    <div>15</div>
    <div>16</div>
    <div>17</div>
    <div>18</div>
    <div>19</div>
    <div>20</div>
</div>
</body>

<script>
    let items, arr = []
    window.onload = () => {
        items = document.querySelector('.container').children
        document.onmousedown = (e) => {
            document.onmousemove = (e2) => {
                selectHandle(e.pageX, e.pageY, e2.pageX, e2.pageY)
            }
            document.onmouseup = (e3) => {
                cleanSelect()
                document.onmousemove = null
            }
        }
    }

    /**
     * 选中处理
     * @param beginX
     * @param beginY
     * @param endX
     * @param endY
     */
    const selectHandle = (beginX, beginY, endX, endY) => {
        cleanSelect()
        /**
         * 采用按列添加选中的方式
         * eg: 从 1,1 移动至 3,3
         *      * -> ** -> ***
         *      * -> ** -> ***
         *      * -> ** -> ***
         *
         * x - 记录当前列的坐标 x,在一列处理完后根据 dom元素 的(宽度 + x坐标)进行累加
         * y - 记录当前行的坐标 y,在一行处理完后根据 dom元素 的(高度 + y坐标)进行累加
         * isReverseY - Y轴移动方向,向上移动为 true,向下为 false
         * isReverseX - X轴移动方向,向左移动为 true,向右为 false
         * range - 允许的选中偏差
         */
        let x = beginX, y = beginY, isReverseY = beginY > endY, isReverseX = beginX > endX, range = 10
        const xHandle = () => {
            const yHandle = () => {
                let e = document.elementFromPoint(x, y)
                if (e) {
                    addDomToArr(e)
                    y = isReverseY ? e.offsetTop - range : e.clientHeight + e.offsetTop + range
                    if (isReverseY ? y >= endY : y <= endY) xHandle()
                }
            }
            let e = document.elementFromPoint(x, y)
            if (e) {
                yHandle()
                y = beginY
                x = isReverseX ? e.offsetLeft - range : e.clientWidth + e.offsetLeft + range
                if (isReverseX ? x >= endX : x <= endX) xHandle()
            }
        }
        xHandle()

        // 改变选中元素的边框颜色
        arr.forEach(v => {
            v.style.border = '2px solid #F8CBA6'
        })
    }

    /**
     * 将dom元素添加至 `arr(已选中)`
     * @param dom
     */
    const addDomToArr = (dom) => {
        if (dom.parentNode.getAttribute('class') != 'container') return
        let has = false
        arr.forEach(v => {
            // 判断是否已存在
            if (v.offsetLeft == dom.offsetLeft && v.offsetTop == dom.offsetTop) {
                has = true
                return
            }
        })
        if (!has) arr.push(dom)
    }

    /**
     * 清空已选中
     */
    const cleanSelect = () => {
        arr = []
        for (let i = 0; i < items.length; i++) {
            items[i].style.border = '2px solid #FFFBEB00'
        }
    }

</script>



<style>
    body {
        display: flex;
        align-items: center;
        justify-content: center;
        background-color: #ECF9FF;
        min-width: 100vw;
        min-height: 100vh;
        margin: unset;
    }

    .container {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr 1fr;
        gap: 1px;
        background-color: #FFE7CC;
    }

    .container div {
        padding: 20px 50px;
        background-color: white;
        user-select: none;
        border: 2px solid #FFFBEB00;
        color: #F99417;
    }
</style>
</html>

Ex - ploooosion!