首页>>前端>>JavaScript->如何实现拖拽排序

如何实现拖拽排序

时间:2023-12-01 本站 点击:0

可拖拽排序的菜单效果大家想必都很熟悉,本次我们通过一个可拖拽排序的九宫格案例来演示其实现原理。 先看一下完成效果:

实现原理概述

拖拽原理

当鼠标在【可拖拽小方块】(以下简称砖头)身上按下时,开始监听鼠标移动事件

鼠标事件移动到什么位置,砖头就跟到什么位置

鼠标抬起时,取消鼠标移动事件的监听

排序原理

提前定义好9大坑位的位置(相对外层盒子的left和top)

将9大砖头丢入一个数组,以便后期通过splice方法随意安插和更改砖头的位置

当拖动某块砖头时,先将其从数组中移除(剩余的砖头在逻辑上重新排序)

拖动结束时,将该砖头重新插回数组的目标位置(此时实现数据上的重排)

数组中的9块砖头根据新的序号,对号入座到9大坑位,完成重新渲染

代码实现

页面布局

9块砖头(li元素)相对于外层盒子(ul元素)做绝对定位

    <ul id="box">        <li style="background-color:black;top: 10px; left: 10px">1</li>        <li style="background-color:black;top: 10px; left: 220px">2</li>        <li style="background-color:black;top: 10px; left: 430px">3</li>        <li style="background-color:black;top: 220px; left: 10px">4</li>        <li style="background-color:black;top: 220px; left: 220px">5</li>        <li style="background-color:black;top: 220px; left: 430px">6</li>        <li style="background-color:black;top: 430px; left: 10px">7</li>        <li style="background-color:black;top: 430px; left: 220px">8</li>        <li style="background-color:black;top: 430px; left: 430px">9</li>    </ul>

样式如下

    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>

定义砖头的背景色和9大坑位位置

    // 定义9大li的预设背景色    var colorArr = [        "red",        "orange",        "yellow",        "green",        "blue",        "cyan",        "purple",        "pink",        "gray",    ];    /* 定义9大坑位 */    const positions = [        [10, 10], [220, 10], [430, 10],        [10, 220], [220, 220], [430, 220],        [10, 430], [220, 430], [430, 430],    ]

找出砖头并丢入一个数组

    var ulBox = document.querySelector("#box")    var lis = document.querySelectorAll("#box>li")    /* 将lis转化为真数组 */    lis = toArray(lis)

这里我使用了一个将NodeList伪数组转化为真数组的轮子:

    /* 伪数组转真数组 pseudo array */    function toArray(pArr){        var arr = []        for(var i=0;i<pArr.length;i++){            arr.push(pArr[i])        }        return arr    }

给所有砖头内置一个position属性

    /* 给每块砖内置一个position属性 */    lis.forEach(        (item, index) => item.setAttribute("position", index)    )

定义正在拖动的砖头

        /* 正在拖动的Li(砖头) */        var draggingLi = null;        // 正在拖动的砖头的zindex不断加加,保持在最上层        var maxZindex = 9

在身上按下 谁就是【正在拖动的砖头】

        /* 在身上按下 谁就是【正在拖动的砖头】 */        lis.forEach(            function (li, index) {                li.style.backgroundColor = colorArr[index]                /* li中的文字不可选(禁止selectstart事件的默认行为) */                li.addEventListener(                    "selectstart",                    function (e) {                        // 阻止掉拖选文本的默认行为                        e.preventDefault()                    }                )                /* 在任意li身上按下鼠标=我想拖动它 */                li.addEventListener(                    "mousedown",                    function (e) {                        draggingLi = this                        draggingLi.style.zIndex = maxZindex++                    }                )            }        )

在任意位置松开鼠标则停止拖拽

        /* 在页面的任意位置松开鼠标=不再拖拽任何对象 */        document.addEventListener(            "mouseup",            function (e) {                // 当前砖头自己进入位置躺好                const p = draggingLi.getAttribute("position") * 1                // draggingLi.style.left = positions[p][0] + "px"                // draggingLi.style.top = positions[p][1] + "px"                move(                    draggingLi,                     {                        left:positions[p][0] + "px",                        top:positions[p][1] + "px"                    },                     200                    // callback                )                // 正在拖拽的砖头置空                draggingLi = null;            }        )

当前砖头从鼠标事件位置回归其坑位时用到动画效果,以下是动画轮子

/** * 多属性动画 * @param {Element} element 要做动画的元素 * @param {Object} targetObj 属性目标值的对象 封装了所有要做动画的属性及其目标值 * @param {number} timeCost 动画耗时,单位毫秒 * @param {Function} callback 动画结束的回调函数 */const move = (element, targetObj, timeCost = 1000, callback) => {    const frameTimeCost = 40;    // 500.00px 提取单位的正则    const regUnit = /[\d\.]+([a-z]*)/;    // 计算动画总帧数    const totalFrames = Math.round(timeCost / frameTimeCost);    // 动态数一数当前动画到了第几帧    let frameCount = 0;    /* 查询特定属性的速度(汤鹏飞的辣鸡) */    // const getAttrSpeed = (attr) => (parseFloat(targetObj[attr]) - parseFloat(getComputedStyle(element)[attr]))/totalFrames    // 存储各个属性的初始值和动画速度    const ssObj = {};    /* 遍历targetObj的所有属性 */    for (let attr in targetObj) {        // 拿到元素属性的初始值        const attrStart = parseFloat(getComputedStyle(element)[attr]);        // 动画速度 = (目标值 - 当前值)/帧数        const attrSpeed =            (parseFloat(targetObj[attr]) - attrStart) / totalFrames;        // 将【属性初始值】和【属性帧速度】存在obj中 以后obj[left]同时拿到这两个货        // obj{ left:[0px初始值,50px每帧] }        ssObj[attr] = [attrStart, attrSpeed];    }    /* 开始动画 */    const timer = setInterval(        () => {            // element.style.left = parseFloat(getComputedStyle(element).left)+"px"            // element.style.top = parseFloat(getComputedStyle(element).top)+"px"            // element.style.opacity = getComputedStyle(element).opacity            // 帧数+1            frameCount++;            /* 每个属性的值都+=动画速度 */            for (let attr in targetObj) {                // console.log(attr, ssObj[attr], totalFrames, frameCount);                // 用正则分离出单位                // console.log(regUnit.exec("500px"));                // console.log(regUnit.exec(0));                const unit = regUnit.exec(targetObj[attr])[1];                // 计算出当前帧应该去到的属性值                const thisFrameValue =                    ssObj[attr][0] + frameCount * ssObj[attr][1];                // 将元素的属性掰到当前帧应该去到的目标值                element.style[attr] = thisFrameValue + unit;            }            /* 当前帧 多个属性动画完成 判断是否应该终止动画  */            if (frameCount >= totalFrames) {                // console.log(frameCount, totalFrames);                clearInterval(timer);                /* 强制矫正(反正用户又看不出来 V) */                // for (let attr in targetObj) {                //     element.style[attr] = targetObj[attr];                //     console.log(attr, getComputedStyle(element)[attr]);                // }                // 如果有callback就调用callback                // if(callback){                //     callback()                // }                callback && callback();            }        },        frameTimeCost    );    /* 动画结束后再过一帧 执行暴力校正 */    setTimeout(() => {        /* 强制矫正(反正用户又看不出来 V) */        for (let attr in targetObj) {            element.style[attr] = targetObj[attr];            // console.log(attr, getComputedStyle(element)[attr]);        }    }, timeCost + frameTimeCost);    // 返回正在运行的定时器    return timer;};

移动鼠标时 砖头跟随 所有砖头实时洗牌

    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>0

坑位检测方法

    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>1

砖头洗牌方法

    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>2

完整代码实现

主程序

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8" />    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>九宫格拖拽排序</title>    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style></head><body>    <ul id="box">        <li style="background-color:black;top: 10px; left: 10px">1</li>        <li style="background-color:black;top: 10px; left: 220px">2</li>        <li style="background-color:black;top: 10px; left: 430px">3</li>        <li style="background-color:black;top: 220px; left: 10px">4</li>        <li style="background-color:black;top: 220px; left: 220px">5</li>        <li style="background-color:black;top: 220px; left: 430px">6</li>        <li style="background-color:black;top: 430px; left: 10px">7</li>        <li style="background-color:black;top: 430px; left: 220px">8</li>        <li style="background-color:black;top: 430px; left: 430px">9</li>    </ul>    <!--     position 位置     -->    <script src="../../../tools/arr_obj_tool.js"></script>    <script src="../../../tools/animtool.js"></script>    <script>        // 定义9大li的预设背景色        var colorArr = [            "red",            "orange",            "yellow",            "green",            "blue",            "cyan",            "purple",            "pink",            "gray",        ];        /* 定义9大坑位 */        const positions = [            [10, 10], [220, 10], [430, 10],            [10, 220], [220, 220], [430, 220],            [10, 430], [220, 430], [430, 430],        ]        var ulBox = document.querySelector("#box")        var lis = document.querySelectorAll("#box>li")        /* 将lis转化为真数组 */        lis = toArray(lis)        /* 给每块砖内置一个position属性 */        lis.forEach(            (item, index) => item.setAttribute("position", index)        )        /* 正在拖动的Li(砖头) */        var draggingLi = null;        // 正在拖动的砖头的zindex不断加加,保持在最上层        var maxZindex = 9        /* 在身上按下 谁就是【正在拖动的砖头】 */        lis.forEach(            function (li, index) {                li.style.backgroundColor = colorArr[index]                /* li中的文字不可选(禁止selectstart事件的默认行为) */                li.addEventListener(                    "selectstart",                    function (e) {                        // 阻止掉拖选文本的默认行为                        e.preventDefault()                    }                )                /* 在任意li身上按下鼠标=我想拖动它 */                li.addEventListener(                    "mousedown",                    function (e) {                        draggingLi = this                        draggingLi.style.zIndex = maxZindex++                    }                )            }        )        /* 在页面的任意位置松开鼠标=不再拖拽任何对象 */        document.addEventListener(            "mouseup",            function (e) {                // 当前砖头自己进入位置躺好                const p = draggingLi.getAttribute("position") * 1                // draggingLi.style.left = positions[p][0] + "px"                // draggingLi.style.top = positions[p][1] + "px"                move(                    draggingLi,                    {                        left: positions[p][0] + "px",                        top: positions[p][1] + "px"                    },                    200                    // callback                )                // 正在拖拽的砖头置空                draggingLi = null;            }        )    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>0    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>1    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>2    </script></body></html>

动画轮子

function moveWithTransition(element, targetObj, duration) {    element.style.transition = `all ${duration / 1000 + "s"} linear`;    for (var attr in targetObj) {        element.style[attr] = targetObj[attr];    }    setTimeout(() => {        element.style.transition = "none";    }, duration);}/** * 多属性动画 * @param {Element} element 要做动画的元素 * @param {Object} targetObj 属性目标值的对象 封装了所有要做动画的属性及其目标值 * @param {number} timeCost 动画耗时,单位毫秒 * @param {Function} callback 动画结束的回调函数 */const move = (element, targetObj, timeCost = 1000, callback) => {    const frameTimeCost = 40;    // 500.00px 提取单位的正则    const regUnit = /[\d\.]+([a-z]*)/;    // 计算动画总帧数    const totalFrames = Math.round(timeCost / frameTimeCost);    // 动态数一数当前动画到了第几帧    let frameCount = 0;    /* 查询特定属性的速度(汤鹏飞的辣鸡) */    // const getAttrSpeed = (attr) => (parseFloat(targetObj[attr]) - parseFloat(getComputedStyle(element)[attr]))/totalFrames    // 存储各个属性的初始值和动画速度    const ssObj = {};    /* 遍历targetObj的所有属性 */    for (let attr in targetObj) {        // 拿到元素属性的初始值        const attrStart = parseFloat(getComputedStyle(element)[attr]);        // 动画速度 = (目标值 - 当前值)/帧数        const attrSpeed =            (parseFloat(targetObj[attr]) - attrStart) / totalFrames;        // 将【属性初始值】和【属性帧速度】存在obj中 以后obj[left]同时拿到这两个货        // obj{ left:[0px初始值,50px每帧] }        ssObj[attr] = [attrStart, attrSpeed];    }    /* 开始动画 */    const timer = setInterval(        () => {            // element.style.left = parseFloat(getComputedStyle(element).left)+"px"            // element.style.top = parseFloat(getComputedStyle(element).top)+"px"            // element.style.opacity = getComputedStyle(element).opacity            // 帧数+1            frameCount++;            /* 每个属性的值都+=动画速度 */            for (let attr in targetObj) {                // console.log(attr, ssObj[attr], totalFrames, frameCount);                // 用正则分离出单位                // console.log(regUnit.exec("500px"));                // console.log(regUnit.exec(0));                const unit = regUnit.exec(targetObj[attr])[1];                // 计算出当前帧应该去到的属性值                const thisFrameValue =                    ssObj[attr][0] + frameCount * ssObj[attr][1];                // 将元素的属性掰到当前帧应该去到的目标值                element.style[attr] = thisFrameValue + unit;            }            /* 当前帧 多个属性动画完成 判断是否应该终止动画  */            if (frameCount >= totalFrames) {                // console.log(frameCount, totalFrames);                clearInterval(timer);                /* 强制矫正(反正用户又看不出来 V) */                // for (let attr in targetObj) {                //     element.style[attr] = targetObj[attr];                //     console.log(attr, getComputedStyle(element)[attr]);                // }                // 如果有callback就调用callback                // if(callback){                //     callback()                // }                callback && callback();            }        },        frameTimeCost    );    /* 动画结束后再过一帧 执行暴力校正 */    setTimeout(() => {        /* 强制矫正(反正用户又看不出来 V) */        for (let attr in targetObj) {            element.style[attr] = targetObj[attr];            // console.log(attr, getComputedStyle(element)[attr]);        }    }, timeCost + frameTimeCost);    // 返回正在运行的定时器    return timer;};

伪数组转真数组轮子

    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>5

这里大家也可以简单地

    <style>        * {            margin: 0;            padding: 0;        }        html,        body {            width: 100%;            height: 100%;        }        ul,        li {            list-style: none;        }        ul {            width: 640px;            height: 640px;            border: 10px solid pink;            border-radius: 10px;            margin: 50px auto;            position: relative;        }        li {            width: 200px;            height: 200px;            border-radius: 10px;            display: flex;            justify-content: center;            align-items: center;            color: white;            font-size: 100px;            position: absolute;        }    </style>6

祝大家撸码愉快,身心健康!

原文:https://juejin.cn/post/7096186729895297060


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/6389.html