浏览器坐标系
浏览器中大概有以下几个坐标系:

- screenX, screenY,以显示器(屏幕)为坐标系的坐标
- clientX, clientY(简写是 x,y),以浏览器窗口(可视区域)为坐标系的坐标
- pageX, pageY,以文档窗口(可滚动)为坐标系的坐标
- offsetX, offsetY,以元素为坐标系的坐标
- layerX, layerY,返回点击的目标对象相对于当前层的坐标
screenX, screenY

clientX, clientY

这两个值是指可视区域,并且从可视区域的左上角开始计算距离。就算页面存在滚动,也不管。那么谁管呢,pageX, pageY 管!如果页面不存在滚动,client 和 page 的值是一样的。如果垂直方向上存在滚动,那么有以下公式:
js
pageY = scrollTop + clientY
offsetX, offsetY

如果点击粉色快,则 offsetX, offsetY 是以自己本身左上顶角为原点坐标计算当前值。
layerX, layerY
- 返回相对点击的元素相对于其父级元素(有定位属性)的坐标
- 如果点击的元素以及父级元素都没有定位属性,那就返回相对于body标签的坐标
- 如果点击的元素自身有定位属性返回的就是相对于自身的坐标
拖放与坐标系之间有什么关系呢,因为以前是通过这些坐标系进行模拟拖放的,但是现在 Html5 中已经有了相应的拖放 api 了,变的十分方便了。
drag 事件
问?将大象装进冰箱需要几步?
- 首先你得有大象和冰箱
- 再者执行装这个操作
如果我们将此问题类比与拖放事件,那么拖放事件也得经过两个步骤:
- 大象------被拖放的元素,将想要拖放的元素的
draggable
设为true
,draggable="true"
。这样才能将元素进行拖放。另外img
与a
标签默认允许拖放。 - 冰箱------目标元素并执行装这个操作------编写拖放有关的代码。
执行装这个操作可以细分为:
- 大象开始拖放
- 大象拖放中
- 大象开始进入冰箱
- 大象完全进入冰箱,还翻了个身
- 大象从冰箱出来了
- 大象被放进了冰箱
- 关门
那么用拖放有关的 api 描述则为:
- 大象开始拖放------dragstart
- 大象拖放中------drag
- 大象开始进入冰箱------dragenter
- 大象完全进入冰箱,还翻了个身------dragover
- 大象从冰箱出来了------dragleave
- 大象被放进了冰箱------drop
- 关门------dragend
那么动作的发生一定和主体有关,那么这些 api 的主体就不是一样的。
我们将大象称为 drag 元素
,冰箱称为 drop 元素
drag 元素
------dragstart------开始拖放操作drag 元素
------drag------拖放过程中drop 元素
------dragenter------被拖放的元素进入目标元素范围(更精确的是鼠标进入)drop 元素
------dragover------被拖放的元素在目标元素内移动drop 元素
------dragleave------被拖放的元素离开目标元素范围drop 元素
------drop------被拖放的元素放在目标元素范围drag 元素
------dragend------拖放操作结束
案例

代码如下:
html
<!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>drag</title>
</head>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-boxss;
}
.link {
display: block;
margin: 20px auto;
width: 40px;
height: 20px;
text-align: center;
color: #fff;
background-color: pink;
}
.box {
margin: 20px auto;
width: 100px;
height: 100px;
border: 1px solid;
}
.text {
text-align: center;
font-size: 30px;
}
</style>
<body>
<a class="link" href="https://juejin.cn/" draggable="true">掘金</a>
<div class="box"></div>
</body>
<script>
let source = document.querySelector('.link')
let target = document.querySelector('.box')
source.addEventListener('dragstart', event => {
event.dataTransfer.setData('text/plain', event.target.className)
})
target.addEventListener('dragover', event => {
// 浏览器默认是不允许拖放,要阻止默认事件
event.preventDefault()
})
target.addEventListener('drop', event => {
// 阻止默认事件
event.preventDefault()
const className = event.dataTransfer.getData('text/plain')
target.appendChild(document.querySelector(`.${className}`))
})
</script>
</html>
整体思路就是:
- dragstart 时,保存一些数据到
dataTransfer
,使用的是setData
- drop 时,获取保存的数据,得到元素,插入到目标元素中。
这里面出现了一些新的属性:
dataTransfer
:对象用于保存拖动并放下过程中的数据,它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型dataTransfer.setData(format, data)
方法用来设置拖放操作的数据和类型。设置的类型format
要与保存时getData
一致,不然取不到值的。
要特别注意的是:
- 浏览器默认不允许拖放,因此在目标元素上要在
dragover
时,阻止这一特性,只能在这个周期。
dataTransfer.dropEffect,dataTransfer.effectAllowed
- dropEffect:属性控制在拖放操作中给用户的反馈(copy、move、link、none)。项目可能禁止拖放
- effectAllowed:属性指定拖放操作所允许的一个效果
这两个属性如果要设置的话,需要注意一下,可能会导致无法拖放。 设置为 none 时,也无法拖放。