麻豆黑色丝袜jk制服福利网站-麻豆精品传媒视频观看-麻豆精品传媒一二三区在线视频-麻豆精选传媒4区2021-在线视频99-在线视频a

千鋒教育-做有情懷、有良心、有品質的職業教育機構

手機站
千鋒教育

千鋒學習站 | 隨時隨地免費學

千鋒教育

掃一掃進入千鋒手機站

領取全套視頻
千鋒教育

關注千鋒學習站小程序
隨時隨地免費學習課程

當前位置:首頁  >  技術干貨  > 如何實現拖拽排序

如何實現拖拽排序

來源:千鋒教育
發布人:wjy
時間: 2022-06-02 13:54:00 1654149240

可拖拽排序的菜單效果大家想必都很熟悉,本次我們通過一個可拖拽排序的九宮格案例來演示其實現原理。 

如何實現拖拽排序

## 實現原理概述

**拖拽原理**

- 當鼠標在【可拖拽小方塊】(以下簡稱磚頭)身上按下時,開始監聽鼠標移動事件
- 鼠標事件移動到什么位置,磚頭就跟到什么位置
- 鼠標抬起時,取消鼠標移動事件的監聽

**排序原理**

- 提前定義好9大坑位的位置(相對外層盒子的left和top)
- 將9大磚頭丟入一個數組,以便后期通過splice方法隨意安插和更改磚頭的位置
- 當拖動某塊磚頭時,先將其從數組中移除(剩余的磚頭在邏輯上重新排序)
- 拖動結束時,將該磚頭重新插回數組的目標位置(此時實現數據上的重排)
- 數組中的9塊磚頭根據新的序號,對號入座到9大坑位,完成重新渲染

## 代碼實現

**頁面布局**

9塊磚頭(li元素)相對于外層盒子(ul元素)做絕對定位

```html
<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>
```

樣式如下

```css
<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大坑位位置**

```js
// 定義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],
    ]
```

**找出磚頭并丟入一個數組**

```js
var ulBox = document.querySelector("#box")
    var lis = document.querySelectorAll("#box>li")
    /* 將lis轉化為真數組 */
    lis = toArray(lis)
```

這里我使用了一個將NodeList偽數組轉化為真數組的輪子:

```js
/* 偽數組轉真數組 pseudo array */
    function toArray(pArr){
        var arr = []
        for(var i=0;i<pArr.length;i++){
            arr.push(pArr[i])
        }
        return arr
    }
```

**給所有磚頭內置一個position屬性**

```js
/* 給每塊磚內置一個position屬性 */
    lis.forEach(
        (item, index) => item.setAttribute("position", index)
    )
```

**定義正在拖動的磚頭**

```js
/* 正在拖動的Li(磚頭) */
        var draggingLi = null;

        // 正在拖動的磚頭的zindex不斷加加,保持在最上層
        var maxZindex = 9
```

**在身上按下 誰就是【正在拖動的磚頭】**

```js
/* 在身上按下 誰就是【正在拖動的磚頭】 */
        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++
                    }
                )
            }
        )
```

**在任意位置松開鼠標則停止拖拽**

```js
/* 在頁面的任意位置松開鼠標=不再拖拽任何對象 */
        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;
            }
        )
```

當前磚頭從鼠標事件位置回歸其坑位時用到動畫效果,以下是動畫輪子

```js
/**
 * 多屬性動畫
 * @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;
};
```

**移動鼠標時 磚頭跟隨 所有磚頭實時洗牌**

```js
/* 在ul內移動鼠標 draggingLi跟隨鼠標 */
        ulBox.addEventListener(
            "mousemove",
            function (e) {
                /* 如果draggingLi為空 什么也不做 直接返回 */
                if (draggingLi === null) {
                    return
                }

                // 拿到事件相對于ulBox的位置  
                var offsetX = e.pageX - ulBox.offsetLeft - 100
                var offsetY = e.pageY - ulBox.offsetTop - 100

                /* 校正磚頭的偏移量 */
                offsetX = offsetX < 10 ? 10 : offsetX
                offsetY = offsetY < 10 ? 10 : offsetY
                offsetX = offsetX > 430 ? 430 : offsetX
                offsetY = offsetY > 430 ? 430 : offsetY

                // 將該位置設置給draggingLi
                draggingLi.style.left = offsetX + "px"
                draggingLi.style.top = offsetY + "px"

                /* 實時檢測實時【坑位】 */
                const newPosition = checkPosition([offsetX, offsetY]);

                // 如果當前磚頭的position發生變化 則數據重排
                const oldPosition = draggingLi.getAttribute("position") * 1
                if (newPosition != -1 && newPosition != oldPosition) {
                    console.log(oldPosition, newPosition);

                    /* 數據重排 */
                    // 先將當前磚頭拽出數組(剩余的磚頭位置自動重排)
                    lis.splice(oldPosition, 1)
                    // 再將當前磚頭插回newPosition
                    lis.splice(newPosition, 0, draggingLi)

                    // 打印新數據
                    // logArr(lis,"innerText")

                    // 磚頭洗牌
                    shuffle()
                }

            }
        )
```

**坑位檢測方法**

```js
/* 實時檢測坑位:檢測ep與9大坑位的距離是否小于100 */
        const checkPosition = (ep) => {
            for (let i = 0; i < positions.length; i++) {
                const [x, y] = positions[i]//[10,10]
                const [ex, ey] = ep//[offsetX,offsetY]

                const distance = Math.sqrt(Math.pow(x - ex, 2) + Math.pow(y - ey, 2))
                if (distance < 100) {
                    return i
                }
            }

            // 沒有進入任何坑位
            return -1
        }
```

**磚頭洗牌方法**

```js
/* 磚頭洗牌:lis中的每塊磚去到對應的位置 */
        const shuffle = () => {
            for (var i = 0; i < lis.length; i++) {
                lis[i].style.left = positions[i][0] + "px"
                lis[i].style.top = positions[i][1] + "px"

                // 更新自己的位置
                lis[i].setAttribute("position", i)
            }
        }
```

## 完整代碼實現

**主程序**

```js
<!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;
            }
        )

        /* 在ul內移動鼠標 draggingLi跟隨鼠標 */
        ulBox.addEventListener(
            "mousemove",
            function (e) {
                /* 如果draggingLi為空 什么也不做 直接返回 */
                if (draggingLi === null) {
                    return
                }

                // 拿到事件相對于ulBox的位置  
                var offsetX = e.pageX - ulBox.offsetLeft - 100
                var offsetY = e.pageY - ulBox.offsetTop - 100

                /* 校正磚頭的偏移量 */
                offsetX = offsetX < 10 ? 10 : offsetX
                offsetY = offsetY < 10 ? 10 : offsetY
                offsetX = offsetX > 430 ? 430 : offsetX
                offsetY = offsetY > 430 ? 430 : offsetY

                // 將該位置設置給draggingLi
                draggingLi.style.left = offsetX + "px"
                draggingLi.style.top = offsetY + "px"

                /* 實時檢測實時【坑位】 */
                const newPosition = checkPosition([offsetX, offsetY]);

                // 如果當前磚頭的position發生變化 則數據重排
                const oldPosition = draggingLi.getAttribute("position") * 1
                if (newPosition != -1 && newPosition != oldPosition) {
                    console.log(oldPosition, newPosition);

                    /* 數據重排 */
                    // 先將當前磚頭拽出數組(剩余的磚頭位置自動重排)
                    lis.splice(oldPosition, 1)
                    // 再將當前磚頭插回newPosition
                    lis.splice(newPosition, 0, draggingLi)

                    // 打印新數據
                    // logArr(lis,"innerText")

                    // 磚頭洗牌
                    shuffle()
                }

            }
        )

        /* 實時檢測坑位:檢測ep與9大坑位的距離是否小于100 */
        const checkPosition = (ep) => {
            for (let i = 0; i < positions.length; i++) {
                const [x, y] = positions[i]//[10,10]
                const [ex, ey] = ep//[offsetX,offsetY]

                const distance = Math.sqrt(Math.pow(x - ex, 2) + Math.pow(y - ey, 2))
                if (distance < 100) {
                    return i
                }
            }

            // 沒有進入任何坑位
            return -1
        }

        /* 磚頭洗牌:lis中的每塊磚去到對應的位置 */
        const shuffle = () => {
            for (var i = 0; i < lis.length; i++) {
                lis[i].style.left = positions[i][0] + "px"
                lis[i].style.top = positions[i][1] + "px"

                // 更新自己的位置
                lis[i].setAttribute("position", i)
            }
        }

    </script>
</body>

</html>
```

**動畫輪子**

```js
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;
};
```

**偽數組轉真數組輪子**

```js
/* 偽數組轉真數組 pseudo array */
function toArray(pArr){
    var arr = []
    for(var i=0;i<pArr.length;i++){
        arr.push(pArr[i])
    }
    return arr
}
```

這里大家也可以簡單地

```js
const arr = [...pArr]
```

祝大家擼碼愉快,身心健康!更多關于“web前端培訓”的問題,歡迎咨詢千鋒教育在線名師。千鋒已有十余年的培訓經驗,課程大綱更科學更專業,有針對零基礎的就業班,有針對想提升技術的提升班,高品質課程助理你實現夢想。

tags:
聲明:本站稿件版權均屬千鋒教育所有,未經許可不得擅自轉載。
10年以上業內強師集結,手把手帶你蛻變精英
請您保持通訊暢通,專屬學習老師24小時內將與您1V1溝通
免費領取
今日已有369人領取成功
劉同學 138****2860 剛剛成功領取
王同學 131****2015 剛剛成功領取
張同學 133****4652 剛剛成功領取
李同學 135****8607 剛剛成功領取
楊同學 132****5667 剛剛成功領取
岳同學 134****6652 剛剛成功領取
梁同學 157****2950 剛剛成功領取
劉同學 189****1015 剛剛成功領取
張同學 155****4678 剛剛成功領取
鄒同學 139****2907 剛剛成功領取
董同學 138****2867 剛剛成功領取
周同學 136****3602 剛剛成功領取
相關推薦HOT
主站蜘蛛池模板: 性高湖久久久久久久久| 四虎www成人影院| 日韩特黄| 台湾一级淫片高清视频| 7777精品久久久大香线蕉| 国产精品免费一区二区三区| 高清对白精彩国产国语| 久久久久免费精品国产| 2022国产精品最新在线| 美女的胸www又黄的网站| 交换韩国电影| 884aa四虎四虎永久播放地址| 美女的大胸又黄又www又爽| 亚洲福利电影在线观看| 欧美日产国产亚洲综合图区一 | 中国一级特黄高清免费的大片中国一级黄色片| 健身私教弄了我好几次啊| 女人扒下裤让男人桶到爽| 久久精品人人做人人爽电影蜜月| 国产白嫩美女在线观看| 蜜臀91精品国产高清在线观看| 无毒不卡在线观看| 国产破处在线| а√天堂资源8在线官网在线| 精品久久久久国产免费| 一个人看的www免费高清中文字幕| 亚洲国产精品久久久天堂| 小莹与翁回乡下欢爱姿势| 欧美日韩久久中文字幕| 国产va免费精品高清在线| 免费a级毛片在线播放| 中文字幕福利片| 东北女大战28公分黑人| 日韩美女hd高清电影| 久久精品精品| 在厨房里挺进美妇雪臀| 毛片日韩| 国产69精品久久久久妇女| 国产日产久久高清欧美一区| 中文字幕专区高清在线观看| 黑白高清在线观看|