JavaScript怎么实现拖拽排序效果
发表于:2024-11-13 作者:千家信息网编辑
千家信息网最后更新 2024年11月13日,这篇"JavaScript怎么实现拖拽排序效果"文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看
千家信息网最后更新 2024年11月13日JavaScript怎么实现拖拽排序效果
这篇"JavaScript怎么实现拖拽排序效果"文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇"JavaScript怎么实现拖拽排序效果"文章吧。
先看一下完成效果:
实现原理概述
拖拽原理
当鼠标在【可拖拽小方块】(以下简称砖头)身上按下时,开始监听鼠标移动事件
鼠标事件移动到什么位置,砖头就跟到什么位置
鼠标抬起时,取消鼠标移动事件的监听
排序原理
提前定义好9大坑位的位置(相对外层盒子的left和top)
将9大砖头丢入一个数组,以便后期通过splice方法随意安插和更改砖头的位置
当拖动某块砖头时,先将其从数组中移除(剩余的砖头在逻辑上重新排序)
拖动结束时,将该砖头重新插回数组的目标位置(此时实现数据上的重排)
数组中的9块砖头根据新的序号,对号入座到9大坑位,完成重新渲染
代码实现
页面布局
9块砖头(li元素)相对于外层盒子(ul元素)做绝对定位
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
样式如下
* { 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给所有砖头内置一个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) } }完整代码实现
主程序
九宫格拖拽排序
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
动画轮子
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这里大家也可以简单地
const arr = [...pArr]以上就是关于"JavaScript怎么实现拖拽排序效果"这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注行业资讯频道。
砖头
动画
属性
位置
数组
鼠标
正在
速度
目标
事件
元素
目标值
大坑
实时
数据
检测
排序
单位
移动
效果
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
apache缓存服务器
网络安全控制架构
excel存入数据库插件
客户信息管理数据库
网络安全竞赛工会领导讲话
北京服务器搬家找哪家
事务数据库的特性包括
控制网络技术实验
软件开发团队求合作
资讯及网络安全问题
沧州服务器视频教程
网络技术基础实验总结
我的世界rgp服务器
阿里云mysql数据库ip
首钢网络安全日是哪一天
视频会议软件开发背景
tcbd数据库注释
dhcp服务器设置租期
网络安全宣传主题活动总结
电台网络安全
连接至观战服务器失败
服务器是否支持硬盘扩展
服务器如何把磁盘映射到本地用
网络安全法集中宣传周
甘肃省斯玛特服务器云服务器
vs和数据库制作网上书店
网络安全存在的潜在威胁
提高软件开发人员的执行力
网络安全日开始施行的时间是
c 访问 远程数据库