在用户拖拽文件到阅读器的某个元素上时,js可以监听到与拖拽相干的事宜,并对拖拽效果举行处置惩罚,本文议论下和拖拽文件相干的一些题目,不过没有处置惩罚太多关于兼容性的题目。
拖拽事宜
js
可以监听到拖拽的事宜有drag
、dragend
、dragenter
、dragexit(没有阅读器完成)
、dragleave
、dragover
、dragstart
、drop
,细致的内容可以看MDN。
个中,与拖拽文件相干的事宜有dragenter(文件拖拽进)
、dragover(文件拖拽在悬浮)
、dragleave(文件拖拽脱离)
、drop(文件拖拽放下)
。
拖拽事宜可以绑定到指定的DOM元素上,可以绑定到全部页面中。
var dropEle = document.querySelector('#dropZone');
dropEle.addEventListener('drop', function (e) {
//
}, false);
document.addEventListener('drop', function (e) {
//
}, false);
阻挠默许行动
一般来说,我们只须要把处置惩罚拖拽文件的营业逻辑写到drop
事宜中就可以了,为何还要绑定dragenter
、dragover
、dragleave
这三个事宜呢?
因为当你拖拽一个文件到没有对拖拽事宜举行处置惩罚的阅读器中的时刻,阅读器会翻开这个文件,比方拖拽一张图片阅读器会翻开这个图片,在没有PDF阅读器的时刻也可以拖拽一个PDF到阅读器中,阅读器就会翻开这个PDF文件。
假如阅读器翻开了拖拽的文件,页面就跳走了,我们愿望取得拖拽的文件,而不是让页面跳走。上面说到阅读器会翻开拖拽的文件是阅读器的默许行动,我们须要阻挠这个默许行动,就须要再上述的事宜中举行阻挠。
dropZone.addEventListener("dragenter", function (e) {
e.preventDefault();
e.stopPropagation();
}, false);
dropZone.addEventListener("dragover", function (e) {
e.preventDefault();
e.stopPropagation();
}, false);
dropZone.addEventListener("dragleave", function (e) {
e.preventDefault();
e.stopPropagation();
}, false);
dropZone.addEventListener("drop", function (e) {
e.preventDefault();
e.stopPropagation();
// 处置惩罚拖拽文件的逻辑
}
实际上dragenter
不阻挠默许行动也不会触发阅读器翻开文件,为了防备某些阅读器能够有的兼容性题目,把拖拽周期中的一切的事宜都阻挠默许行动而且阻挠了事宜冒泡。
取得拖拽的文件
我们会在drop
这个事宜的回调中的事宜对象可以取得文件对象。
在事宜对象中,一个e.dataTransfer
如许的属性,它是一个DataTransfer
范例的数据,有以下的属性
属性 | 范例 | 申明 |
---|---|---|
dropEffect | String | 用来hack某些兼容性题目 |
effectAllowed | String | 临时不必 |
files | FileList | 拖拽的文件列表 |
items | DataTransferItemList | 拖拽的数据(有多是字符串) |
types | Array | 拖拽的数据范例 该属性在Safari下比较杂沓 |
在Chrome
中我们用items
对象取得文件,其他阅读器用files
取得文件,重要是为了处置惩罚拖拽文件夹的题目,最好不允许用户拖拽文件夹,因为文件夹内能够另有文件夹,递归上传文件会良久,假如不递归查找,只上传目次第一层级的文件,用户能够认为上传功用了,然则没有上传子目次文件,所以照样制止上传文件夹比较好,背面我会说要怎样处置惩罚。
Chrome猎取文件
dropZone.addEventListener("drop", function (e) {
e.preventDefault();
e.stopPropagation();
var df = e.dataTransfer;
var dropFiles = []; // 寄存拖拽的文件对象
if(df.items !== undefined) {
// Chrome有items属性,对Chrome的零丁处置惩罚
for(var i = 0; i < df.items.length; i++) {
var item = df.items[i];
// 用webkitGetAsEntry制止上传目次
if(item.kind === "file" && item.webkitGetAsEntry().isFile) {
var file = item.getAsFile();
dropFiles.push(file);
}
}
}
}
其他阅读器猎取文件
这里只测试了Safari,其他阅读器并没有测试,不过看完本文肯定也有思绪处置惩罚其他阅读器的兼容状况。
dropZone.addEventListener("drop", function (e) {
e.preventDefault();
e.stopPropagation();
var df = e.dataTransfer;
var dropFiles = []; // 寄存拖拽的文件对象
if(df.items !== undefined) {
// Chrome拖拽文件逻辑
} else {
for(var i = 0; i < df.files.length; i++) {
dropFiles.push(df.files[i]);
}
}
}
因为Safari
没有item
,天然也没有webkitGetAsEntry
,所以在Safari没法肯定拖拽的是不是是文件照样文件夹。
非Chrome内核阅读器推断目次的要领
阅读器猎取到的每一个file对象有四个属性:lastModified
、name
、size
、type
,个中type
是文件的MIME Type
,文件夹的type
是空的,然则有些文件没有MIME Type
,假如依据type
是不是为空推断是不是是拖拽的文件夹的话,会误伤一部分文件,所以这个要领行。
那末另有什么要领可以推断呢,思绪大概是如许子的,用户拖拽的文件和文件夹应当是不一样的东西,用File API
操纵的时刻应当会有区分,比方举行某些操纵的时刻,文件就可以一般操纵,然则文件夹就会报错,经由历程毛病的捕捉就可以推断是文件照样文件夹了,好我们依据这个思绪来写一下。
dropZone.addEventListener("drop", function (e) {
e.preventDefault();
e.stopPropagation();
var df = e.dataTransfer;
var dropFiles = [];
if(df.items !== undefined){
// Chrome拖拽文件逻辑
} else {
for(var i = 0; i < df.files.length; i++){
var dropFile = df.files[i];
if ( dropFile.type ) {
// 假如type不是空串,肯定是文件
dropFiles.push(dropFile);
} else {
try {
var fileReader = new FileReader();
fileReader.readAsDataURL(dropFile.slice(0, 3));
fileReader.addEventListener('load', function (e) {
console.log(e, 'load');
dropFiles.push(dropFile);
}, false);
fileReader.addEventListener('error', function (e) {
console.log(e, 'error,不可以上传文件夹');
}, false);
} catch (e) {
console.log(e, 'catch error,不可以上传文件夹');
}
}
}
}
}, false);
上面代码创建了一个FileReader
实例,经由历程这个实例对文件举行读取,我测试读取一个1G多的文件要3S多,时候有点长,就用slice
截取了前3个字符,为何是前3个不是前2个或许前4个呢,因为代码是我写的,我高兴这么写呗~
假如load
事宜触发了,就申明拖拽过来的东西是文件,假如error
事宜触发了,就申明是文件夹,为了防备其他能够的潜伏毛病,用try
包起来这段代码。
三方运用的一点点小hack
经由测试发明经由历程Mac
的Finder
拖拽文件没有题目,然则有时刻文件并不肯定在Finder
中,也能够在某些运用中,有一个运用叫做圈点
,这个运用的用户反应文件拖拽失效,去看了其他开源文件上传的源码,发明了如许一行代码:
dropZone.addEventListener("dragover", function (e) {
e.dataTransfer.dropEffect = 'copy'; // 兼容某些三方运用,如圈点
e.preventDefault();
e.stopPropagation();
}, false);
须要把dropEffect
置为copy
,上网搜了下这个题目,源码文档中也没有说为何要加这个,有兴致的同砚可以找一下为何。
可以拿来就用的代码
因为用了FileReader
去读取文件,这是一个异步IO操纵,为了纪录当前处置惩罚了多少个文件,以及什么时刻触发拖拽完毕的回调,写了一个checkDropFinish
的要领一直去比较处置惩罚的文件数目和文件总数,肯定一切文件处置惩罚完了后就去挪用完成的回调。
别的,我在末了调试异步处置惩罚的时刻,用的断点调试,发明断点调试在Safari
中会致使异步回调不触发,须要本身调试定制功用的同砚注意下。
// 取得拖拽文件的回调函数
function getDropFileCallBack (dropFiles) {
console.log(dropFiles, dropFiles.length);
}
var dropZone = document.querySelector("#dropZone");
dropZone.addEventListener("dragenter", function (e) {
e.preventDefault();
e.stopPropagation();
}, false);
dropZone.addEventListener("dragover", function (e) {
e.dataTransfer.dropEffect = 'copy'; // 兼容某些三方运用,如圈点
e.preventDefault();
e.stopPropagation();
}, false);
dropZone.addEventListener("dragleave", function (e) {
e.preventDefault();
e.stopPropagation();
}, false);
dropZone.addEventListener("drop", function (e) {
e.preventDefault();
e.stopPropagation();
var df = e.dataTransfer;
var dropFiles = []; // 拖拽的文件,会放到这里
var dealFileCnt = 0; // 读取文件是个异步的历程,须要纪录处置惩罚了多少个文件了
var allFileLen = df.files.length; // 一切的文件的数目,给非Chrome阅读器运用的变量
// 检测是不是已把一切的文件都遍历过了
function checkDropFinish () {
if ( dealFileCnt === allFileLen-1 ) {
getDropFileCallBack(dropFiles);
}
dealFileCnt++;
}
if(df.items !== undefined){
// Chrome拖拽文件逻辑
for(var i = 0; i < df.items.length; i++) {
var item = df.items[i];
if(item.kind === "file" && item.webkitGetAsEntry().isFile) {
var file = item.getAsFile();
dropFiles.push(file);
console.log(file);
}
}
} else {
// 非Chrome拖拽文件逻辑
for(var i = 0; i < allFileLen; i++) {
var dropFile = df.files[i];
if ( dropFile.type ) {
dropFiles.push(dropFile);
checkDropFinish();
} else {
try {
var fileReader = new FileReader();
fileReader.readAsDataURL(dropFile.slice(0, 3));
fileReader.addEventListener('load', function (e) {
console.log(e, 'load');
dropFiles.push(dropFile);
checkDropFinish();
}, false);
fileReader.addEventListener('error', function (e) {
console.log(e, 'error,不可以上传文件夹');
checkDropFinish();
}, false);
} catch (e) {
console.log(e, 'catch error,不可以上传文件夹');
checkDropFinish();
}
}
}
}
}, false);