原生js实现鼠标滑动区域选中文件功能

234 阅读3分钟

原生js实现鼠标滑动区域选中文件功能

前一阵在项目中碰到了鼠标滑动区域选中文件的需求,在这里分享一下:

设计思路: 1、创建一个蒙版当做选中区域 2、根据鼠标的mousedown、mouseup、mousemove鼠标事件对蒙版区域进行实时变化 3、选中区域选中文件碰撞检测 4、考虑滚动条影响

1、创建一个蒙版

代码如下:

function addMaskStyle () {
    const styled = document.createElement("style");
    styled.innerHTML = `.mask {
        background: rgba(203, 235, 247, 0.4)
    }`

    document.head.appendChild(styled);
}
// 
function createMask () {
    addMaskStyle();
    const mask = document.createElement("div");
    mask.classList.add("mask");
    mask.style.position = "absolute";
    document.body.appendChild(mask);
    return mask;
}

2、实时变更蒙版

代码如下:

function addEventHandle () {
    document.body.addEventListener("mousedown", (e) => {
        isDown = true;
        const x = e.pageX;
        const y = e.pageY;
        left = x;
        top = y;
        right = window.innerWidth - getScrollbarWidth() - left;
        bottom = window.innerHeight - getScrollbarWidth() - top;
        mask.style.left = left + "px";
        mask.style.top = top + "px";
        mask.style.right = "auto";
        mask.style.bottom = "auto";
    })

    document.body.addEventListener("mouseup", (e) => {
        isDown = false;
        mask.style.width = 0;
        mask.style.height = 0;
    })

    document.body.addEventListener("mousemove", (e) => {
        if (!isDown) return;
        const width = e.pageX - left;
        const height = e.pageY - top;

        // 鼠标向左滑动
        if (width < 0) {
            mask.style.right = right + "px";
            mask.style.left = "auto";
        }

        if (height < 0) {
            mask.style.bottom = bottom + "px";
            mask.style.top = "auto";
        }

        // 右滑
        if (width > 0) {
            mask.style.right = "auto";
            mask.style.left = left + "px";
        }

        if (height > 0) {
            mask.style.bottom = "auto";
            mask.style.top = top + "px";
        }

        mask.style.width = Math.abs(width) + "px";
        mask.style.height = Math.abs(height) + "px";


        // 检测文件是否在当前选中范围
        checkFileIsCurrentArea(e);
    })
}

3、碰撞检测

根据上传文件在浏览器中位置和鼠标位置以及滑动区域简单判断文件是否在选中区域。

代码如下:

function checkFileIsCurrentArea (e) {
    const {
        left: maskLeft,
        top: maskTop,
        bottom: maskBottom,
        right: maskRight,
        width,
        height
    } = mask.style

    const {
        clientX,
        clientY
    } = e

    for (let i = 0; i < files.length; ++i) {
        const file = files[i];
        const rect = file.getBoundingClientRect();

        // 左上滑
        if (maskLeft == "auto" && maskTop == "auto") {
            // 鼠标开始位置 (left, top) > 物体位置 (rect.x, rect.y)
            // 鼠标滑动实时位置 (clientX, clientY) < 物体位置 (rect.x + rect.width, rect.y + rect.height)
            if (left > rect.x && top > rect.y
                && clientX < rect.x + rect.width && clientY < rect.y + rect.height) {
                file.classList.add("is-active");
            }
            else {
                file.classList.remove("is-active");
            }
        }
        // 右上滑
        if (maskTop == "auto" && maskRight == "auto") {
            if (left < rect.x + rect.width && top > rect.y
                && clientX > rect.x && clientY < rect.y + rect.height) {
                file.classList.add("is-active");
            }
            else {
                file.classList.remove("is-active");
            }
        }
        // 右下滑
        if (maskBottom == "auto" && maskRight == "auto") {
            if (left < rect.x + rect.width && top < rect.y + rect.height
                && clientX > rect.x && clientY > rect.y) {
                file.classList.add("is-active");
            }
            else {
                file.classList.remove("is-active");
            }
        }
        // 左下滑
        if (maskBottom == "auto" && maskLeft == "auto") {
            if (left > rect.x && top < rect.y + rect.height
                && clientX < rect.x + rect.width && clientY > rect.y) {
                file.classList.add("is-active");
            }
            else {
                file.classList.remove("is-active");
            }
        }
    }
}

4、去除滚动条影响

获取滚动条宽度,在计算时去除滚动条影响

// 获取滚动条宽度
function getScrollbarWidth () {
    const outer = document.createElement("div");
    outer.style.visibility = "hidden";
    outer.style.width = "100px";
    outer.style.msOverflowStyle = "scrollbar";

    document.body.appendChild(outer);

    const widthNoScroll = outer.offsetWidth;
    outer.style.overflow = "scroll";

    const inner = document.createElement("div");
    inner.style.width = "100%";
    outer.appendChild(inner);

    const widthWithScroll = inner.offsetWidth;

    outer.parentNode.removeChild(outer);

    return widthNoScroll - widthWithScroll;
}

5、完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
        }

        body {
            height: 200vh;
            width: 200vw;
        }

        .file-list {
            margin-left: 300px;
            margin-top: 300px;
            user-select: none;
        }

        .file-list .img {
            width: 50px;
        }


        .file-item {
            width: 55px;
            position: relative;
            /* height: 160px; */
        }


        .file-item.is-active::before {
            content: "";
            background: rgba(203, 235, 247, 0.6);
            width: 100%;
            height: 100%;
            position: absolute;
        }
    </style>

    <!-- <style>
        .mask {
            background: rgba(203, 235, 247, 0.4)
        }
    </style> -->
</head>

<body>
    <div class="text">
        <div class="upload-file">
            <input type="file" onchange="handleChange(this)">

            <div class="file-list">

            </div>
        </div>
    </div>
    <script>
        const fileList = document.querySelector(".file-list");
        function handleChange (_this) {
            const img = document.createElement("img");
            img.src = "[任意图片.png]";
            img.classList.add("img");

            const text = document.createElement("div");
            text.innerText = "test.txt";
            text.classList.add("text");


            const item = document.createElement("div");
            item.appendChild(img);
            item.appendChild(text);
            item.classList.add("file-item");

            fileList.appendChild(item);

            masker.addCheckFiles(fileList.children);
        }

    </script>

    <script>

        function createMasker () {
            let isDown = false;
            let left = 0; // 
            let top = 0;
            let right = 0;
            let bottom = 0;
            const files = [];
            const selectionFiles = [];
            const mask = createMask();

            const masker = {
                addCheckFiles,
                addEventHandle
            }

            function addCheckFiles (fileList) {
                files.push(...fileList);
            }

            function createMask () {
                addMaskStyle();
                const mask = document.createElement("div");
                mask.classList.add("mask");
                mask.style.position = "absolute";
                document.body.appendChild(mask);
                return mask;
            }

            function addMaskStyle () {
                const styled = document.createElement("style");
                styled.innerHTML = `.mask {
                    background: rgba(203, 235, 247, 0.4)
                }`

                document.head.appendChild(styled);
            }

            // 获取滚动条宽度
            function getScrollbarWidth () {
                const outer = document.createElement("div");
                outer.style.visibility = "hidden";
                outer.style.width = "100px";
                outer.style.msOverflowStyle = "scrollbar";

                document.body.appendChild(outer);

                const widthNoScroll = outer.offsetWidth;
                outer.style.overflow = "scroll";

                const inner = document.createElement("div");
                inner.style.width = "100%";
                outer.appendChild(inner);

                const widthWithScroll = inner.offsetWidth;

                outer.parentNode.removeChild(outer);

                return widthNoScroll - widthWithScroll;
            }


            function addEventHandle () {
                document.body.addEventListener("mousedown", (e) => {
                    isDown = true;
                    const x = e.pageX;
                    const y = e.pageY;
                    left = x;
                    top = y;
                    right = window.innerWidth - getScrollbarWidth() - left;
                    bottom = window.innerHeight - getScrollbarWidth() - top;
                    mask.style.left = left + "px";
                    mask.style.top = top + "px";
                    mask.style.right = "auto";
                    mask.style.bottom = "auto";
                })

                document.body.addEventListener("mouseup", (e) => {
                    isDown = false;
                    mask.style.width = 0;
                    mask.style.height = 0;
                })

                document.body.addEventListener("mousemove", (e) => {
                    if (!isDown) return;
                    const width = e.pageX - left;
                    const height = e.pageY - top;

                    // 鼠标向左滑动
                    if (width < 0) {
                        mask.style.right = right + "px";
                        mask.style.left = "auto";
                    }

                    if (height < 0) {
                        mask.style.bottom = bottom + "px";
                        mask.style.top = "auto";
                    }

                    // 右滑
                    if (width > 0) {
                        mask.style.right = "auto";
                        mask.style.left = left + "px";
                    }

                    if (height > 0) {
                        mask.style.bottom = "auto";
                        mask.style.top = top + "px";
                    }

                    mask.style.width = Math.abs(width) + "px";
                    mask.style.height = Math.abs(height) + "px";


                    // 检测文件是否在当前选中范围
                    checkFileIsCurrentArea(e);
                })
            }

            function checkFileIsCurrentArea (e) {
                const {
                    left: maskLeft,
                    top: maskTop,
                    bottom: maskBottom,
                    right: maskRight,
                    width,
                    height
                } = mask.style

                const {
                    clientX,
                    clientY
                } = e

                for (let i = 0; i < files.length; ++i) {
                    const file = files[i];
                    const rect = file.getBoundingClientRect();

                    // 左上滑
                    if (maskLeft == "auto" && maskTop == "auto") {
                        // 鼠标开始位置 (left, top) > 物体位置 (rect.x, rect.y)
                        // 鼠标滑动实时位置 (clientX, clientY) < 物体位置 (rect.x + rect.width, rect.y + rect.height)
                        if (left > rect.x && top > rect.y
                            && clientX < rect.x + rect.width && clientY < rect.y + rect.height) {
                            file.classList.add("is-active");
                        }
                        else {
                            file.classList.remove("is-active");
                        }
                    }
                    // 右上滑
                    if (maskTop == "auto" && maskRight == "auto") {
                        if (left < rect.x + rect.width && top > rect.y
                            && clientX > rect.x && clientY < rect.y + rect.height) {
                            file.classList.add("is-active");
                        }
                        else {
                            file.classList.remove("is-active");
                        }
                    }
                    // 右下滑
                    if (maskBottom == "auto" && maskRight == "auto") {
                        if (left < rect.x + rect.width && top < rect.y + rect.height
                            && clientX > rect.x && clientY > rect.y) {
                            file.classList.add("is-active");
                        }
                        else {
                            file.classList.remove("is-active");
                        }
                    }
                    // 左下滑
                    if (maskBottom == "auto" && maskLeft == "auto") {
                        if (left > rect.x && top < rect.y + rect.height
                            && clientX < rect.x + rect.width && clientY > rect.y) {
                            file.classList.add("is-active");
                        }
                        else {
                            file.classList.remove("is-active");
                        }
                    }
                }
            }

            masker.addEventHandle();

            return masker;
        }

        // 使用
        const masker = createMasker();

    </script>
</body>

</html>