捡到一张csdn发的首页推荐卡,嘻嘻嘻,那肯定是用掉阿
tips:题外话
我认真看完所有的博客和草稿箱。。。你大爷的。。。居然没有一篇符合标准可以直接用的。。。
2000字,还要最近两个月的博客,不然不新鲜了呗???优惠券设个有效期呗??要求字数2000+,限制最低消费呗???消费者协会没有请你们去喝茶吗??
这次的需求呢,听起来比较正常,指定目标区域的拖拽 + 预投影
行吧,就这么个小东西,网上绝对有很多成功的案例哈哈哈哈哈哈哈哈哈哈~
小手抄一抄,快乐又逍遥~
为什么全部都是jquery的版本 一群垃圾 市面上的博客都不与时俱进的吗???
还得自己来,唉!
先随便写个demo,把几个节点写一下,随便写点样式
浏览器切换到移动端模式,尝试拖动一下,可以看到startTouch方法已经成功触发。
startTouch方法不生效90%是因为浏览器没有切换到移动端模式
这个时候立一个标志位,干啥呢,标记已经进入拖拽,不允许重复进入。此举可以有效防止多指操作巴拉巴拉。
然后就是创建拖拽的那个元素啦~记住要将之前的元素隐藏掉~
这里为什么要在body下创建一个专门用来拖拽的元素呢,是因为直接改变拖动的那个元素,可能会改变页面布局,而且可能这个拖拽元素上层有不是static定位的父元素会导致位置偏差
然后就是监听页面touchmove,计算鼠标位置什么的我就直接写在代码注释里面了
需要注意的是,这里在调用预投影方法之前有个节流
因为拖动过程中这个方法会触发很多很多次,而预投影会对dom进行操作,操作频繁会造成渲染压力,所以我们需要加上节流避免可能会出现的卡顿(代码注释里面也写了)
接下来就是显示预投影部分了,就是遍历所有的目标区域的节点看看拖拽到哪个目标节点下了,然后给个投影
这里可以对目标节点的判断给予优化,记录每次预投影的节点,循环前去掉,循环中找到对应的目标节点之后就跳出来,不用继续循环。
这里判断的是拖拽元素的中心点是否在该目标区域的范围内,如果目标区域有重合,直接取用这里的判断逻辑会有bug,需要将if条件根据实际情况进行修改
当然啦,因为我是写demo嘛~就没有写那么多情况,上面的内容请根据个人情况自行配置哦~
然后就是拖拽结束处理的部分啦~~
首先判断有没有进入目标区域,同时还原标志位清空拖拽中间元素和挂载在页面上的监听方法
最后的检查是否为有效拖拽的方法类同于预投影~
有心的少年可以将重复代码整合,然后精简一下~~
(最后附上效果图~)
代码完整版
(注释我都尽可能写的比较详细,如果觉得不够明白的就写留言~)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="drag-part">
<!-- 拖拽元素区域 -->
<div class="drag-dom drag1" ontouchstart="startTouch(event, 1)">1</div>
<div class="drag-dom drag2" ontouchstart="startTouch(event, 2)">2</div>
</div>
<div class="goal-part">
<!-- 目标位置区域 -->
<div class="goal-dom"></div>
<div class="goal-dom"></div>
</div>
<script type="text/javascript">
let drag_flag = false
let obj_height, obj_width
function startTouch(e, i) {
if (drag_flag) return
drag_flag = true
// 复制节点
const obj_div = e.target.cloneNode(false)
// 获取当前节点的位置
const { top, left, width, height } = e.target.getBoundingClientRect()
// absolute是相对于最近的且不是static定位的父元素来定位,所以一定要控制好
// 如果你有拖拽区域限制一定要注意上面一条以及设置好overflow
obj_div.style.position = 'absolute'
obj_div.style.top = top + 'px'
obj_div.style.left = left + 'px'
obj_div.innerText = e.target.innerText
document.body.appendChild(obj_div)
// 记录下块状的宽高,方便之后计算位置
obj_height = height
obj_width = width
// 隐藏原节点
e.target.classList.add('hidden')
document.ontouchmove = (e_2) => {
// 直接取鼠标位置减去一半的宽高,这样拖拽元素一直以鼠标为中心点
// 和别的处理方式不一样这点是因为我个人有强迫症,这里可以像别人一样前面计算相对位置,也不麻烦
obj_div.style.top = e_2.targetTouches[0].clientY - height / 2 + 'px'
obj_div.style.left = e_2.targetTouches[0].clientX - width / 2 + 'px'
// 因为这个是拖拽过程中是一直在触发,所以加一个节流减少dom操作
throttle(() => {
showDragShadow(obj_div, i)
}, 200)
}
document.ontouchend = (e_3) => {
// 判断是否是一次成功的拖拽
if (!checkVaildDrag(obj_div)) {
e.target.classList.remove('hidden')
}
// 删掉移动中加入的元素
obj_div.parentNode.removeChild(obj_div)
drag_flag = false
// 清掉数据监听
document.ontouchmove = null
document.ontouchend = null
}
}
// 节流
function throttle(func, delay) {
if (!this.timer) {
this.timer = setTimeout(() => {
// 只有当它处于拖拽过程中才执行方法
if(drag_flag) func()
this.timer = null
}, delay)
}
}
// 显示阴影
function showDragShadow(obj_div, i) {
// 拖拽元素的位置
const {
left = 0,
right = 0,
top = 0,
bottom = 0
} = obj_div.getBoundingClientRect()
const elements = document.getElementsByClassName('goal-dom')
Array.prototype.forEach.call(elements, (element, index) => {
// 如果已经有值
if (element.value) return
// 目标dom的位置
const position = element.getBoundingClientRect()
// 用中心点计算
// (left + right) / 2表示拖拽元素的x方向中心
// (top + bottom) / 2表示拖拽元素的y方向中心
if (
(left + right) / 2 >= position.left &&
(left + right) / 2 <= position.right &&
(top + bottom) / 2 >= position.top &&
(top + bottom) / 2 <= position.bottom
) {
// 显示阴影
element.innerText = i
element.style.opacity = 0.5
element.shadow = true
} else if (element.shadow) {
// 去掉阴影
element.innerText = ''
element.style.opacity = 1
element.shadow = false
}
})
}
function removeShadow() {
const elements = document.getElementsByClassName('goal-dom')
Array.prototype.forEach.call(elements, (element, index) => {
// 如果已经有值
if (element.value) return
if (element.shadow) {
// 投影样式,按照个人要求修改
element.innerText = ''
element.style.opacity = 1
element.shadow = false
}
})
}
function checkVaildDrag (obj_div) {
//去掉所有的阴影
removeShadow()
let drag_in_flag = false
const {
left = 0,
right = 0,
top = 0,
bottom = 0
} = obj_div.getBoundingClientRect()
const elements = document.getElementsByClassName('goal-dom')
Array.prototype.forEach.call(elements, (element, index) => {
const position = element.getBoundingClientRect()
if (
(left + right) / 2 >= position.left &&
(left + right) / 2 <= position.right &&
(top + bottom) / 2 >= position.top &&
(top + bottom) / 2 <= position.bottom
) {
// 成功拖进去的方法(因为我这里就是个demo,所以就只是单纯把数值写了进去)
drag_in_flag = true
element.innerText = obj_div.innerText
element.value = obj_div.innerText
}
})
return drag_in_flag
}
</script>
<style type="text/css">
body {
padding: 40px;
text-align: center;
}
.drag-dom {
display: inline-block;
width: 100px;
height: 100px;
line-height: 100px;
font-size: 40px;
border: 1px solid;
}
.goal-part {
margin-top: 200px;
}
.goal-dom {
display: inline-block;
width: 110px;
height: 110px;
line-height: 110px;
color: #ffffff;
font-size: 40px;
vertical-align: top;
}
.goal-dom .drag-dom {
color: #ffffff;
}
.drag-dom.drag1 {
color: darkorange;
}
.drag-dom.drag2 {
color: darkseagreen;
}
.goal-dom:nth-child(1) {
background-color: darkorange;
}
.goal-dom:nth-child(2) {
background-color: darkseagreen;
}
.hidden {
visibility: hidden;
}
</style>
</body>
</html>
很简单的demo~~可以自己手撸玩一下~~~
同样的,留一个思考题,我只做了从拖拽区到目标区(才不是因为懒),可以在上面的基础上做:
1.双向的(即也可以从目标区拽出去到拖拽区)
2.还可以做不限制返回原位的(demo目前是无效区域会回到原状)
…
可以做的还很多~~
不仅原创,每次为了写博客专门手撸demo我真的是个良心小博主了呜呜呜,说这么多就是为了要个赞,爱你们哟~~