如何优雅地区分拖拽内容是文件还是文件夹?

1,985 阅读3分钟

写在前面

在诸多的前端项目中,到处可以看到文件上传的影子。上传文件的方式通常有两种:拖拽上传和手动上传。两种方式得到我们想要的结果,但在开发过程中有所差别。

手动上传文件方式中,我们可以做许多文件限制,比如可以通过属性 accept 限制文件上传的类型。而拖拽上传方式,我们就只能在监听拖拽的事件中手动去判断文件类型。

并且在许多文件上传的场景中,是不让上传文件夹的,这就需要我们手动去判断拖拽内容是文件还是文件夹。

判断方法

有问题的文件夹判断方法

刚开始我遇到这个问题的时候,也在网络上搜罗了一圈,有个判断方法出场率很高,说明有很多人用了:

if (file.type === "" && file.size % 4096 === 0) {
    // 文件类型为 文件夹
} else {
    // 是 纯文件
}

该方法两个步骤:

  1. 检查 file.type 类型是否为空,如果拖拽文件类型是不带后缀的文件夹,类型是为空的
  2. 检查 file.size 是否为 4096 的倍数

这个方法能满足大多数场景,但如果文件夹带着后缀,如 xxx.jpgxx.txt ,那么这个方法就有大问题。我们看下命名为 xxx.txt 的文件夹拖拽后拿到的 dataTransfer:

image.png

不出所料,类型是有问题的,因为我们拖拽是文件夹,显然文件夹的 type 不等于 text/plain。虽然文件夹大多不会这么命名,但说明还是存在一定的风险。

那有没有其他的方法呢?答案是有的。

超好用的文件夹判断方法

1. DataTransferItem.webkitGetAsEntry()

webkitGetAsEntry() 有个属性 isFile,我们可以通过它来判断是否为「 纯文件」。

But,兼容性不是很好(现在大多主流浏览器都可使用),实际使用要谨慎,大家可以参考官网 DataTransferItem.webkitGetAsEntry()

const { items } = event.dataTransfer;
if (items) {
    for (const item of items) {
        if (
            item.kind === "file" && item.webkitGetAsEntry().isFile
        ) {
            console.log("this is file");
        } else {
            console.log("this is folder");
        }
    }
}

2. FileReader

思路大概是这样,文件和文件夹不是同个东西,所以通过 FileReader 来读取文件时必然有所不同。比如当文件在进行某些操作时,能正常运行;但如果对象是文件夹,操作就会出错,进而触发 onerror 事件,我们可以通过这个特性来判断目标对象是文件还是文件夹。

const { files } = event.dataTransfer;
for (const file of files) {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => {
        console.log("this is file");
    };
    reader.onerror = () => {
        console.log("this is folder");
    };
}

总结

以上方法中,第一种简单、对性能友好,美中不足就是兼容性不好;第二种方法,绝大多数浏览器都能够支持,但如果拖拽的文件内存很大,文件读取时间就会被拉长,而读取文件的操作又是同步的,容易造成阻塞。

我们不妨可以将两种方法结合一起,如果浏览器支持 DataTransferItem 我们使用第一种,否则使用第二种:

const { files, items } = e.dataTransfer;
if (items) {
    for (const item of items) {
        if (item.kind === "file" && item.webkitGetAsEntry().isFile) {
            console.log("this is file");
        } else {
            console.log("this is folder");
        }
    }
} else {
    for (const file of files) {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => {
            console.log("this is file");
        };
        reader.onerror = () => {
            console.log("this is folder");
        };
    }
}