您好,登錄后才能下訂單哦!
這篇“JavaScript怎么實現拖拽排序效果”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“JavaScript怎么實現拖拽排序效果”文章吧。
先看一下完成效果:
拖拽原理
當鼠標在【可拖拽小方塊】(以下簡稱磚頭)身上按下時,開始監聽鼠標移動事件
鼠標事件移動到什么位置,磚頭就跟到什么位置
鼠標抬起時,取消鼠標移動事件的監聽
排序原理
提前定義好9大坑位的位置(相對外層盒子的left和top)
將9大磚頭丟入一個數組,以便后期通過splice方法隨意安插和更改磚頭的位置
當拖動某塊磚頭時,先將其從數組中移除(剩余的磚頭在邏輯上重新排序)
拖動結束時,將該磚頭重新插回數組的目標位置(此時實現數據上的重排)
數組中的9塊磚頭根據新的序號,對號入座到9大坑位,完成重新渲染
頁面布局
9塊磚頭(li元素)相對于外層盒子(ul元素)做絕對定位
<ul id="box"> <li >1</li> <li >2</li> <li >3</li> <li >4</li> <li >5</li> <li >6</li> <li >7</li> <li >8</li> <li >9</li> </ul>
樣式如下
* { 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; }
定義磚頭的背景色和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; };
移動鼠標時 磚頭跟隨 所有磚頭實時洗牌
/* 在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) } }
主程序
<!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 >1</li> <li >2</li> <li >3</li> <li >4</li> <li >5</li> <li >6</li> <li >7</li> <li >8</li> <li >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>
動畫輪子
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; };
偽數組轉真數組輪子
/* 偽數組轉真數組 pseudo array */ function toArray(pArr){ var arr = [] for(var i=0;i<pArr.length;i++){ arr.push(pArr[i]) } return arr }
這里大家也可以簡單地
const arr = [...pArr]
以上就是關于“JavaScript怎么實現拖拽排序效果”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。