Skip to content

类似桌面的鼠标框选,和拖动选择的所有元素(360°的框选) #14

@lzpong

Description

@lzpong

类似桌面的鼠标框选,和拖动选择的所有元素
亮点是实现了360°框选, 和框选边界(必须框住多少才算)

.draggable{position: absolute;}
.slct_rec{
    position: absolute;
    border: 1px dashed blue;
    overflow: hidden;
    background: rgba(239, 239, 239, 0.53);
}
.slctd{
    background: blue;
    opacity: 0.5;
}
window.onload = function(e) {
    // startX, startY 为鼠标点击时初始坐标
    var startX, startY;
    // diffX, diffY 为鼠标初始坐标与 拖动元素 左上角坐标之差,用于拖动
    // 移动时使用了`e.movementX`和`e.movementY`替代了 diffX, diffY
    //var diffX, diffY;
    // 是否拖动,初始为 false
    var dragging = null;
    // 选择框
    var slct_box=null;
    //框选的元素
    var slectedBoxs=[];
    //文档设置不能选择文字
    document.onselectstart=function (e){e.returnValue=false;}
    // 鼠标按下
    document.onmousedown = function(e) {
        startX = e.pageX;
        startY = e.pageY;
        // 如果鼠标在 可拖动元素 上被按下
        if(e.target!=document && e.target.classList.contains("draggable")) {
            // 记住拖动的元素
            dragging = e.target;
            // 计算坐标差值
            //diffX = startX - e.target.offsetLeft;
            //diffY = startY - e.target.offsetTop;
        }
        else {
            // 若无,在页面创建 选择框
            if(!slct_box){
                slct_box = document.createElement("div");
                slct_box.className = "slct_rec";
                document.body.appendChild(slct_box);
            }
            slct_box.style.top = startY + 'px';
            slct_box.style.left = startX + 'px';
            slct_box.style.width = '0px';
            slct_box.style.height = '0px';
            slct_box.style.display="block";
        }
        document.addEventListener("mousemove", onMouseMove, false );
        document.addEventListener("mouseup", onMouseDown, false );
    };

    // 鼠标移动
    function onMouseMove(e){
        // 移动,更新 拖动元素 坐标
        if(dragging)
        {
            var mvSlts=false;
            if(slectedBoxs.length>0){//是否要拖动选择的元素们
                slectedBoxs.forEach((o)=>{if(o==dragging)mvSlts=true;});
            }
            if(mvSlts){//拖动选择的元素们
                 slectedBoxs.forEach((o)=>{
                    o.style.top = parseInt(o.style.top)+e.movementY+ 'px';
                    o.style.left = parseInt(o.style.left)+e.movementX + 'px';
                 });
            }
            else{
                dragging.style.top = parseInt(dragging.style.top)+e.movementY+ 'px';
                dragging.style.left = parseInt(dragging.style.left)+e.movementX + 'px';
                //dragging.style.top = e.pageY - diffY + 'px';
                //dragging.style.left = e.pageX - diffX + 'px';
            }
        }
        // 更新 选择框 尺寸/位置
        else if(slct_box)
        {
            h=e.pageY - startY;
            w=e.pageX - startX;
            if(h<0) //Y
            {
                slct_box.style.top=startY+h+"px";
                slct_box.style.height = -h + 'px';
            }
            else
                slct_box.style.height = h + 'px';

            if(w<0) //X
            {
                slct_box.style.left=startX+w+"px";
                slct_box.style.width=-w+"px"
            }
            else
                slct_box.style.width=w+"px"
        }
    };

    // 鼠标抬起
    function onMouseDown(e) {
        // 释放拖动元素
        if(dragging)
            dragging= null;
        else if(slct_box) {
            // 如果长宽其一大于 20px,则回调
            if(slct_box.offsetWidth > 20 || slct_box.offsetHeight > 20) {
                var p=GetRecPosition(slct_box);
                onSelectDiv(p);
            }
            slct_box.style.display="none";
        }
        document.removeEventListener("mousemove", onMouseMove );
        document.removeEventListener("mouseup", onMouseDown );
    };

    //取四个顶点的坐标: [`左上角`,`右上角`, `右下角`, `左下角`] 的坐标点
    function GetRecPosition(o)
    {
        return [
            {x:o.offsetLeft, y:o.offsetTop},
            {x:o.offsetLeft+o.offsetWidth, y:o.offsetTop},
            {x:o.offsetLeft+o.offsetWidth, y:o.offsetTop+o.offsetHeight},
            {x:o.offsetLeft, y:o.offsetTop+o.offsetHeight}
        ];
    }
    //覆盖检测,四个顶点至少有一个在范围内,或覆盖区在检测区之内(横/竖的覆盖/半覆盖)
    //(被覆盖元素四个顶点坐标,覆盖元素四个顶点坐标,四角判断至少覆盖像素(相当于四角向内缩了))
    function isOverOn(op, pp, off) {
        var isIn = 0;
        off = off || 1;
        //覆盖区竖半覆盖检测区
        ((pp[0].x > op[0].x && pp[2].x < op[2].x) && ((pp[0].y < op[0].y && pp[2].y < op[2].y && pp[2].y > op[0].y) || (pp[0].y > op[0].y && pp[2].y > op[2].y && pp[0].y < op[2].y))) && isIn++;
        if (isIn > 0) return true;
        //覆盖区横半覆盖检测区
        (((pp[0].x < op[0].x && pp[2].x < op[2].x && pp[2].x > op[0].x) || (pp[0].x > op[0].x && pp[2].x > op[2].x && pp[0].x < op[2].x)) && (pp[0].y > op[0].y && pp[2].y < op[2].y)) && isIn++;
        if (isIn > 0) return true;
        //覆盖区竖穿过检测区
        ((pp[0].x > op[0].x && pp[2].x < op[2].x) && (pp[0].y < op[0].y && pp[2].y > op[2].y)) && isIn++;
        if (isIn > 0) return true;
        //覆盖区横穿过检测区
        ((pp[0].x < op[0].x && pp[2].x > op[2].x) && (pp[0].y > op[0].y && pp[2].y < op[2].y)) && isIn++;
        if (isIn > 0) return true;
        //四个顶点
        op[0].x += off; op[0].y += off;
        op[1].x -= off; op[1].y += off;
        op[2].x -= off; op[2].y -= off;
        op[3].x += off; op[3].y -= off;
        op.forEach((o) => {
            ((o.x >= pp[0].x + off && o.x <= pp[2].x - off) && (o.y >= pp[0].y - off && o.y <= pp[2].y - off)) && isIn++;
        });
        return (isIn > 0);
    }
    //检测覆盖,重置选择的元素列表
    function onSelectDiv(pp)
    {
        slectedBoxs=[];
        ds=document.querySelectorAll(".divb");
        ds.forEach((d)=>{
            //console.log(d);
            var op=GetRecPosition(d);
            if(isOverOn(op,pp,3)>0)
            {
                slectedBoxs.push(d);
                if(!d.classList.contains("slctd"))
                    d.classList.add("slctd");
            }
            else if(d.classList.contains("slctd"))
                d.classList.remove("slctd");
        });
    }

};

使用如下代码测试

.divb{
    width: 100px;
    height: 100px;
    position: absolute;
    border: 1px solid;
    background: #febcad;
}
for(y=0;y<8;y++)
for(x=0;x<8;x++)
{
    d=document.createElement("div");
    d.className="divb draggable";
    d.style.top=y*110+60+"px";
    d.style.left=x*110+30+"px";
    document.body.appendChild(d);
}
*/

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions