如何通过浏览器File System Access API获取客户端目录文件列表
基于File System Access API实现目录选择与树形视图渲染
核心实现步骤
1. 触发目录选择
首先需要绑定用户交互事件(比如按钮点击)调用目录选择接口,这个API必须由用户主动手势触发,不能自动弹出选择框:
<button id="selectDirBtn">选择本地目录</button> <div id="treeContainer"></div>
const selectDirBtn = document.getElementById('selectDirBtn'); const treeContainer = document.getElementById('treeContainer'); selectDirBtn.addEventListener('click', async () => { // 先做兼容性判断 if (!('showDirectoryPicker' in window)) { alert('当前浏览器不支持File System Access API,请使用最新版Chrome/Edge浏览器'); return; } try { // 弹出目录选择框,用户授权后拿到根目录句柄 const rootDirHandle = await window.showDirectoryPicker(); // 递归遍历生成目录树结构 const treeData = await traverseHandle(rootDirHandle); // 渲染树形视图 treeContainer.innerHTML = ''; const rootList = document.createElement('ul'); rootList.appendChild(renderTree(treeData)); treeContainer.appendChild(rootList); } catch (err) { // 用户取消选择会抛错,直接忽略即可 if (err.name !== 'AbortError') console.error(err); } });
2. 递归遍历目录生成结构化数据
拿到目录句柄后,递归遍历所有子项,区分文件和子目录,生成统一的树结构数据:
/** * 递归遍历文件/目录句柄,生成树节点 * @param {FileSystemHandle} handle 文件/目录句柄 * @returns 树节点对象 */ async function traverseHandle(handle) { const node = { name: handle.name, kind: handle.kind, // 'file' 或 'directory' handle: handle }; if (handle.kind === 'directory') { node.children = []; // 异步遍历目录下的所有条目 for await (const childHandle of handle.values()) { const childNode = await traverseHandle(childHandle); node.children.push(childNode); } } return node; }
3. 递归渲染树形视图
基于生成的树结构数据,递归生成DOM节点,可按需添加折叠/展开交互:
/** * 递归渲染树节点为DOM * @param {object} node 树节点 * @returns DOM元素 */ function renderTree(node) { const li = document.createElement('li'); li.style.listStyle = 'none'; const contentSpan = document.createElement('span'); li.appendChild(contentSpan); if (node.kind === 'directory') { // 目录节点添加折叠交互 contentSpan.style.cursor = 'pointer'; const childList = document.createElement('ul'); childList.style.paddingLeft = '20px'; childList.style.margin = '4px 0'; // 默认展开 let isCollapsed = false; contentSpan.addEventListener('click', () => { isCollapsed = !isCollapsed; childList.style.display = isCollapsed ? 'none' : 'block'; contentSpan.textContent = isCollapsed ? `▶ ${node.name}` : `▼ ${node.name}`; }); // 初始标记为展开状态 contentSpan.textContent = `▼ ${node.name}`; // 渲染所有子节点 node.children.forEach(child => { childList.appendChild(renderTree(child)); }); li.appendChild(childList); } else { // 文件节点加文件标记 contentSpan.textContent = `📄 ${node.name}`; } return li; }
注意事项
- 兼容性:该API目前仅支持Chromium内核浏览器(Chrome 86+、Edge 86+),Firefox、Safari默认未开放该能力,必须做兼容兜底。
- 权限边界:用户授权目录后,仅能访问用户所选目录范围内的文件,无法越权访问其他路径;如果需要读取文件内容,可在拿到文件句柄后调用
getFile()方法获取File对象,和原生<input type="file">拿到的文件对象用法一致。 - 生命周期:页面刷新、关闭后,之前获取的文件/目录句柄会失效,需要用户重新选择授权,无法持久化保留访问权限。
- 性能:如果选择的目录包含上万级别的文件,递归遍历会有一定耗时,大目录场景建议加加载状态提示,避免页面假死。
内容的提问来源于stack exchange,提问作者Atul Bhatt
相关产品推荐
相关产品推荐

