需求背景
用户可以任意拖拽文件/文件夹到页面上,不限制文件个数,自动分析里面的图片然后自动生成sprite图
- 关于sprite图,现在常规的前端可能已经不在使用了
- 如果是从事图形工作的同学应该知道其作为
webgl纹理使用可以大大提高程序性能 - 整个需求的要求要做到如下面的视频链接里所示
sprite-creator-video
核心功能分析
file dndHTML 拖放(Drag and Drop),对用户拖进去的文件或者文件夹进行自动分析sprite图的布局和生成(该功能这篇文章里不做介绍)
调研
- 从github 找了半天没有找到好的
file dnd库,于是决定自己写一个file dnd - 如果小伙伴有好的相关的库推荐,可以在评论区@我
自己写一个File DND
查阅MDN
于是便去MDN Web Docs (mozilla.org)寻宝,看了相关API文档
-
DataTransfer.items提供一个包含所有拖动数据列表的DataTransferItemList对象。 -
DataTransferItem.webkitGetAsEntry()返回一个基于FileSystemEntry的对象来表示文件系统中选中的项目。通常是返回一个FileSystemFileEntry或是FileSystemDirectoryEntry对象。- 这个方法非常重要,可以拿到文件的FileSystemEntry - Web API 接口参考 | MDN (mozilla.org)
-
FileSystemEntry - Web API 接口参考 | MDN (mozilla.org)
-
isDirectoryABooleanwhich istrueif the entry represents a directory; otherwise, it'sfalse. -
isFile(en-US) 只读 A Boolean which istrueif the entry represents a file. If it's not a file, this value isfalse. -
这两个属性可以用来判断上传的文件是文件还是文件夹
-
-
FileSystemFileEntry - Web API 接口参考 | MDN (mozilla.org)
file()(en-US) 创建新的File对象,它可以用于读取文件。
-
FileSystemDirectoryEntry - Web API 接口参考 | MDN (mozilla.org)
createReader()(en-US) 创建FileSystemDirectoryReader对象,它可以用于服务目录中的条目。
看到这里一下子就豁然开朗了,整个前端已经可以判断文件和文件夹了,且文件的读取和文件夹的读取都支持,只需要对文件夹进行递归遍历读取就可以把所有的文件都读取出来了
设计和编码
- 读取文件夹里
FileSystemFileEntry集合
//from [FileSystemDirectoryEntry.createReader() - Web APIs | MDN (mozilla.org)](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry/createReader)
function readDirectory(directory) {
let dirReader = directory.createReader();
let entries = [];
let getEntries = () => {
dirReader.readEntries((results) => {
if (results.length) {
entries = entries.concat(toArray(results));
getEntries();
}
}, (error) => {
/* handle error — error is a FileError object */
});
};
getEntries();
return entries;
}
注意:FileSystemDirectoryReader 每次读取的文件数量是有限的,当一个文件夹文件特别多时FileSystemDirectoryReader只能读取前多少条的,所有需要一直循环读取,类似分页,一直分页下去,直至读取不到数据结束读取
- 所以只要一直递归读取文件夹即可,把所有的文件读取出来存放到一个结果集合(
FileSystemFileEntry Array) - 直到所有的
FileSystemDirectoryEntry读取结束,然后将所有的FileSystemFileEntry集合读取为File,简易的代码如下:
let fileEntryList = [], isReading = false, idx = 0;
const readFiles = () => {
idx = 0;
const readFile = () => {
if (idx < files.length) {
const fileEntry = files[idx];
if (fileEntry.isDirectory) {
idx++;
readFile();
} else {
fileEntry.file((file) => {
file.path = fileEntry.path || '/';
file.parentName = fileEntry.parentName;
file.id = fileEntry.id;
file.pid = fileEntry.pid;
file.isDirectory = !!file.isDirectory;
files[idx] = file;
idx++;
readFile();
});
}
} else {
callback(files);
}
};
readFile();
};
- 在
drop回调函数里回调这个files集合 - 具体代码请参考 github.com/deyihu/file…
注意firefox里会丢失 file.type字段,如果想判断文件的类型可以:- 利用
file.name里的扩展名简单的判断 - 使用专业的库来判断文件类型 sindresorhus/file-type: Detect the file type of a Buffer/Uint8Array/ArrayBuffer (github.com)
- 利用
树形数据结构的支持
- 方便前端来展示用户拖进去的文件的目录结构
- 在读取文件和文件夹时为每个节点负值id和pid,方便用来组装成树形结构
- 并自动拼接每个文件的
path地址,方便用户进行文件上传时后端自动根据path来自动创建对应的目录结构 - 将平行的数组组装成树形结构即可
- 简易代码如下
toTree() {
const files = this.files;
const fileMap = {};
files.forEach(file => {
const { id, path, name, parentName } = file;
if (!fileMap[id]) {
fileMap[id] = {
id,
name,
label: name,
path,
parentName: parentName,
children: [],
isDirectory: file.isDirectory
};
}
});
files.forEach(file => {
const { pid, id } = file;
if (fileMap[pid]) {
fileMap[pid].children.push(fileMap[id]);
}
});
for (const id in fileMap) {
const children = fileMap[id].children;
const dirs = [], files = [];
//模拟windows文件管理效果,把文件夹放到前面
children.forEach(child => {
if (child.isDirectory) {
dirs.push(child);
} else {
files.push(child);
}
});
fileMap[id].children = dirs.concat(files);
}
return Object.values(fileMap).filter(d => {
return !d.parentName;
});
}
性能表现
- 拖拽一个
node_modules文件夹进去,里面包含30000个文件,解析无压力
提供独立的NPM包filednd
npm i filednd
#or
yarn add filednd
- 仓库地址 deyihu/filednd: Files Drag and Drop (github.com)
- 在线示例 File DND (deyihu.github.io)
- Features
支持多文件支持文件夹支持分析和生成用户拖进去的文件目录支持每个文件自动生成path属性
思维拓展
- 既然已经可以拿到用户上传的文件集合了(
File Array),也已经拿到整个文件的目录结构了,能否做一个类似vscode这样一个简单的编辑器? - 方法:
- 将用户拖进去的文件目录的结构用树形展示
- 当用户点击一个文件节点时,读取文件内容然后利用一些编辑器来展示
- 演示视频
- 当然这里只是做个简单的效果,主要强调的有了这个
file dnd后是可以做出很多有意思的事情了 - 如果有这方面的需求的同学可以加大在这方面的研究
总结
- MDN里有无穷的宝藏
- 纯前端可以完整的读取文件和文件目录了
- 欢迎使用我的的NPM 包deyihu/filednd: Files Drag and Drop (github.com)
PS
- 自动生成
sprite的工具地址:Sprite Creator (deyihu.github.io) - maptalks/maptalks.js: A light and plugable JavaScript library for integrated 2D/3D maps. (github.com) 下个版本将会支持
sprite的作为图标