拖放事件
关于拖放事件有些是在被拖动元素上触发的,而有些则是在放置目标上触发的
当我们拖动某个元素时,会依次触发:
- ondragstart
- ondrag
- ondragend
这三个事件都是在被拖动元素上触发的。当拖动开始时会先触发dragstart
事件,然后在拖动的过程中会持续触发drag
事件,当拖动停止时(无论被拖动元素是否放到了有效的放置目标)都会触发dragend
事件,这三个事件类似鼠标的移动事件mousestart,mousemove,mouseend
。
当某个元素被拖动到放置目标上,会依次触发:
- dragenter
- dragover
- dragleave 或 drop
这三个事件都是在放置目标上触发的。当元素进入放置目标时会触发dragenter
事件,当元素在放置目标上移动时会持续触发dragover
事件,当元素移出放置目标时会触发dragleave
事件,当元素被放到了放置目标中会触发drop
事件而不是dragleave
事件,这几个事件(除drop
)也类似鼠标的移动事件mouseenter,mouseover,mouseleave
。
虽然所有的元素都支持drop
事件,但是这些元素默认是不允许放置的,这个时候当我们在放置目标上松开鼠标是不会触发drop
事件的,我们可以通过event.preventDefault()
来阻止默认的行为,如下:
droptarget.ondragenter = event => {
event.preventDefault()
}
droptarget.ondragover = event => {
event.preventDefault()
}
另外在一些浏览器中,当我们移动图片到放置目标上,松开的时候会打开这张图片,如果移动的是超链接,则会打开这个页面。我们有时候需要阻止这种默认的行为,可以这样做
droptarget.ondrop = event => {
event.preventDefault()
}
dataTransfer对象
dataTransfer
对象用来在拖动的过程中从被拖动元素向放置目标传递数据,这个对象有两个方法setData
和getData
。
setData
有两个参数,第一个是MIME
类型,第二个则是我们要保存的值
event.dataTransfer.setData('text/plain', 'msg')
event.dataTransfer.setData('text/uri-list', 'http://baidu.com')
getData
只有一个参数,就是setData
中我们传的第一个参数
event.dataTransfer.getData('text/plain')
event.dataTransfer.setData('text/uri-list')
setData
我们一般在dragstart
中去使用,而getData
只能在drop
事件中去使用,这个务必记住
你拖动一个图片到目标区域,那目标区域怎么获取这个图片的信息呢?就靠它!它是事件对象的一个属性,用于从被拖动元素向放置目标传递字符串格式的数据。
复制粘贴事件
oncopy
事件在用户拷贝元素上的内容时触发。onbeforecut
事件在用户剪切文本,且文本还未删除时触发触发。oncut
事件在用户剪切元素的内容时触发。onbeforepaste
事件在用户向元素中粘贴文本之前触发。onpaste
事件在用户向元素中粘贴文本时触发。
clipboardData对象
粘贴事件提供了一个clipboardData
的属性,它实际上是一个DataTransfer
类型的对象,DataTransfer
是拖动产生的一个对象,但实际上粘贴事件也是它。
onPaste绑定的元素必须聚焦后才能触发粘贴方法
本文是将onpaste
绑定在div
元素上,发现复制一张图片到div
元素区域内粘贴时无法触发onpaste
事件,必须要点击下该div
后粘贴才能触发。
通常是将onpaste
绑定在input
元素上,聚焦后才能粘贴文本。上面点击div
其实也就是聚焦行为。但是div
这种非表单元素是不能直接通过focus()
来使其聚焦的,需要在div
元素上增加contentEditable
属性先使其变成可编辑元素(该类元素可以使用focus
来聚焦),然后给div
绑定onmouseenter
和onmouseleave
方法,鼠标移入时使其聚焦,移出时失焦,这样就能在鼠标移入div
区域时粘贴触发onpaste
事件。
react
项目中控制台可能会报"A component is contentEditable and contains children managed by React"
错误,在元素上加上suppressContentEditableWarning
属性就可以了。
聚焦后div元素会出现光标
在div
上加上如下样式即可:
{
outline: none;
color:transparent;
text-shadow:0 0 0 #000;
}
SparkDM5校验图片重复性
使用图片文件对象申城hash
来判断图片是否一样,避免重复提交相同的图片到服务器中,SparkMD5
可以帮助生成文件hash
。
示例
第一步:先写一个简单页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./index.css">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/layer/3.1.1/layer.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script>
</head>
<body>
<div id="drag-show">
<span class="title">图片展示区:</span>
<span class="show-area"></span>
</div>
<!-- 图片拖拽区 -->
<div id="drag-area" contentEditable="true" ondragenter="drag(this, event);" ondragover="drag(this, event);" ondrop="dragUpload(this, event)" onpaste="pasteUpload(this, event);" onmouseenter="handleMouseEnter(this)" onmouseleave="handleMouseLeave(this)">
<span class="text">将图片拖拽至此处上传</span>
<div class="upload-box">
<input type="file" name="file" multiple="multiple" id="file" style="display: none;" onchange="fileChange(this, event);"/>
<span id="upload" onclick="uploadFile()"></span>
</div>
</div>
<!-- 脚本 -->
<script src="./index.js"></script>
</body>
</html>
实现脚本
实现拖拽功能
在图片拖拽区元素监听ondragenter、ondragover、ondrop事件,监听ondragenter、ondragover事件的目的是防止拖拽图片时执行浏览器默认行为,通过浏览器打开本地图片。ondrop事件可以通过dataTransfer拿到图片文件对象。
/** 防止拖拽图片时打开图片 */
function drag(that, event) {
var e = event || window.event;
e.preventDefault()
e.stopPropagation()
}
/** 拖拽上传 */
function dragUpload(that, event) {
var e = event || window.event;
e.preventDefault()
e.stopPropagation()
const files = e.dataTransfer.files;
upload(files);
}
实现复制粘贴
在图片拖拽区元素监听onpaste事件,通过clipboardData拿到图片文件对象
function pasteUpload(that, event) {
var e = event || window.event;
e.preventDefault()
e.stopPropagation()
var files = e.clipboardData.files;
upload(files);
}
封装文件上传方法,校验图片
拿到图片文件对象后,需要校验图片大小、数量、重复性以及是否压缩等等,这些操作我们都封装到upload中。
在SparkDM5中有appendBinary方法,该方法接受一个字符串,生成一个与该字符串的映射hash值。而图片都可以转化成Data URL,不同的图片Data URL不同,图片越大,Data URL也就越长,但是唯一的。
拓展Data URL知识:Data URL
是一项特殊的技术,可以将资料(例如图片)内嵌在网页之中,不用放到外部文件。使用Data URL
的好处是,您不需要额外再发出一个HTTP
请求到服务器端取得额外的资料;而缺点便是,网页的大小可能会变大。它适合应用在内嵌小图片,不建议将大图像文件编码成Data URL
来使用。您的图像文件不能够超过浏览器限定的大小,否则无法读取图像文件。
FileReader
对象的readAsDataURL
方法可以将读取到的文件编码成Data URL
,需要注意的是读取是异步的。
/** 图片存放数组 */
const img_arr = [];
/** 图片上传 */
function upload(files) {
const MAX_COUNT = 3;
const IMG_COUNT = MAX_COUNT - img_arr.length;
const additional_arr = [];
const IMG_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
if (Array.prototype.some.call(files, function(file) {return !IMG_TYPES.includes(file.type)})) {
layer.msg("图片格式不正确!!!")
$("#file").val('');
return;
}
if (files.length > IMG_COUNT) {
layer.msg(`最多只能上传${MAX_COUNT}张图片!`)
$("#file").val('');
return;
}
Array.prototype.forEach.call(files, function(file, i, arr) {
const fileReader = new FileReader();
const sparkMD5 = new SparkMD5();
fileReader.readAsDataURL(file);
fileReader.onload = (event) => {
const imgDataUrl = event.target.result; // 得到data url
// 生成图片hash
const hash = sparkMD5.appendBinary(imgDataUrl).end();
// 判断是否存在相同的图片
if (!isExists(hash)) {
additional_arr.push({
hash,
src: binary,
file
});
} else {
// 中断文件流
fileReader.abort();
layer.msg("请不要上传相同的图片!!!");
}
};
fileReader.onloadend = (event) => {
// 读取完成
if (additional_arr.length === arr.length) {
img_arr.push(...additional_arr);
showImg(additional_arr); // 展示在展览区
$("#file").val('');
}
}
})
}
// 判断是否有相同的图片
function isExists(hash) {
return img_arr.some(function(item) {
return item.hash === hash;
})
}
附上全部脚本代码和样式
/** 图片存放数组 */
const img_arr = [];
/** 图片上传 */
function upload(files) {
const MAX_COUNT = 3;
const IMG_COUNT = MAX_COUNT - img_arr.length;
const additional_arr = [];
const IMG_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
if (Array.prototype.some.call(files, function(file) {return !IMG_TYPES.includes(file.type)})) {
layer.msg("图片格式不正确!!!")
$("#file").val('');
return;
}
if (files.length > IMG_COUNT) {
layer.msg(`最多只能上传${MAX_COUNT}张图片!`)
$("#file").val('');
return;
}
Array.prototype.forEach.call(files, function(file, i, arr) {
const fileReader = new FileReader();
const sparkMD5 = new SparkMD5();
fileReader.readAsDataURL(file);
fileReader.onload = (event) => {
const binary = event.target.result; // 得到data url
// 生成图片hash
const hash = sparkMD5.appendBinary(binary).end();
// 判断是否存在相同的图片
if (!isExists(hash)) {
additional_arr.push({
hash,
src: binary,
file
});
} else {
// 中断文件流
fileReader.abort();
layer.msg("请不要上传相同的图片!!!");
}
};
fileReader.onloadend = (event) => {
// 读取完成
if (additional_arr.length === arr.length) {
img_arr.push(...additional_arr);
showImg(additional_arr); // 展示在展览区
$("#file").val('');
}
}
})
}
// 判断是否有相同的图片
function isExists(hash) {
return img_arr.some(function(item) {
return item.hash === hash;
})
}
/** 控制删除icon */
function imgMouseEnter(that) {
$(that).find("span.del-icon").css('display', 'block');
};
function imgMouseLeave(that) {
$(that).find("span.del-icon").css('display', 'none');
}
/** 展示图片 */
function showImg(additional_arr) {
if (!additional_arr.length) {
return;
}
const img_html = additional_arr.reduce(function(init, item) {
init += `<span class="img-box" onmouseenter="imgMouseEnter(this);" onmouseleave="imgMouseLeave(this);">
<span class="img-span"><img src="${item.src}" mode="widthFix" class="img-item" ondrag="imgDragOver(this, event);" ondragend="imgDragEnd(this, event);"/></span>
<span class="del-icon" hash="${item.hash}" onclick="deleteImg(this)" style="display: none;"></span>
</span>`
return init;
}, '');
$(".show-area").append(img_html);
}
// 删除
function deleteImg(that) {
const hash = $(that).attr("hash");
const $imgBox = $(that).parents(".img-box");
$imgBox.remove();
img_arr.splice(img_arr.findIndex(function(item) {
return item.hash === hash;
}), 1);
}
// 获取元素位置
function getElementLeft(element){
var actualLeft = element.offsetLeft;
var current = element.offsetParent;
while (current !== null){
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
return actualLeft;
}
function getElementTop(element){
var actualTop = element.offsetTop;
var current = element.offsetParent;
while (current !== null){
actualTop += current.offsetTop;
current = current.offsetParent;
}
return actualTop;
}
// 拖拽删除
function imgDragOver(that, event) {
console.log('start', event)
const $showArea = $(that).parents(".show-area");
$showArea.css("border", "1px solid #0065ff")
}
function imgDragEnd(that, event) {
const hash = $(that).attr("hash");
const $showArea = $(".show-area");
const $imgBox = $(that).parents(".img-box");
const width = $showArea[0].clientWidth;
const height = $showArea[0].clientHeight;
const origin = {
x: getElementLeft($showArea[0]),
y: getElementTop($showArea[0])
};
const target = {
x: event.clientX,
y: event.clientY
}
if (target.x - origin.x > width || target.x < origin.x || target.y - origin.y > height || target.y < origin.y) {
$imgBox.remove();
img_arr.splice(img_arr.findIndex(function(item) {
return item.hash === hash;
}), 1);
}
$showArea.css("border", "1px dashed #c3c3c3")
console.log(width, height, origin, target)
}
/** 手动上传 */
function uploadFile() {
$("#file").click();
}
function fileChange(that, event) {
const files = event.target.files;
upload(files);
}
/** 防止拖拽图片时打开图片 */
function drag(that, event) {
var e = event || window.event;
e.preventDefault()
e.stopPropagation()
}
/** 拖拽上传 */
function dragUpload(that, event) {
var e = event || window.event;
e.preventDefault()
e.stopPropagation()
const files = e.dataTransfer.files;
upload(files);
}
/** 赋值粘贴上传 */
function handleMouseEnter(that) {
$(that).focus();
}
function handleMouseLeave(that) {
$(that).blur();
}
function pasteUpload(that, event) {
var e = event || window.event;
e.preventDefault()
e.stopPropagation()
var files = e.clipboardData.files;
upload(files);
}
css
html, body {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#drag-show {
padding-top: 40px;
line-height: 60px;
text-align: center;
}
.show-area {
display: inline-block;
height: 60px;
line-height: 60px;
width: 180px;
border: 1px dashed #c3c3c3;
border-radius: 5px;
vertical-align: middle;
}
.title {
display: inline-block;
line-height: 60px;
color: #555;
font-size: 20px;
vertical-align: middle;
}
.img-box {
position: relative;
display: inline-block;
width: 50px;
height: 50px;
line-height: 50px;
margin-right: 8px;
vertical-align: middle;
cursor: pointer;
}
.img-span {
display: inline-block;
width: 100%;
height: 100%;
vertical-align: middle;
overflow: hidden;
}
.img-item {
display: inline-block;
width: 100%;
vertical-align: middle;
}
.del-icon {
position: absolute;
top: -7px;
right: -7px;
width: 14px;
height: 14px;
background-image: url('./del.png');
background-size: 100% 100%;
cursor: pointer;
}
#drag-area {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
left: 50%;
width: 300px;
height: 200px;
margin-left: -150px;
margin-top: 20px;
border-radius: 12px;
border: 1px dashed #c3c3c3;
outline: none;
color: transparent;
text-shadow: 0 0 0 #000;
}
.text {
color: #555;
font-size: 20px;
}
.upload-box {
display: inline-block;
width: auto;
height: auto;
}
#upload {
display: inline-block;
width: 50px;
height: 50px;
padding: 8px;
margin-top: 10px;
box-sizing: border-box;
border-radius: 5px;
border: 1px dashed #c3c3c3;
background-size: 100% 100%;
background-image: url('./plus.png');
cursor: pointer;
}
#upload:hover {
background-color: #fafafa;
}
效果:
参考:
https://blog.csdn.net/deng145...
https://blog.csdn.net/HPFBoy/...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。