javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > 前端JS获取本地文件目录内容

前端JavaScript获取本地文件目录内容的两种实现方案

作者:页面仔D

由于浏览器的沙箱安全机制,前端 JavaScript 无法直接访问本地文件系统,必须通过用户主动授权(如选择目录操作)才能获取文件目录内容,所以本文详细介绍两种方案的实现流程、代码示例及适用场景,需要的朋友可以参考下

一、核心原理说明

由于浏览器的 “沙箱安全机制”,前端 JavaScript 无法直接访问本地文件系统,必须通过用户主动授权(如选择目录操作)才能获取文件目录内容。目前主流实现方案基于两种 API:传统 File API(兼容性优先)和现代 FileSystem Access API(功能优先),以下将详细介绍两种方案的实现流程、代码示例及适用场景。

二、方案一:基于 File API 实现(兼容性首选)

1. 方案概述

通过隐藏的 <input type="file"> 标签(配置 webkitdirectorydirectory 属性)触发用户选择目录操作,用户选择后通过 files 属性获取目录下所有文件的元数据(如文件名、大小、相对路径等)。该方案兼容几乎所有现代浏览器(包括 Chrome、Firefox、Safari 等),但仅支持 “一次性获取选中目录内容”,无法递归遍历子目录或修改文件。

2. 完整使用示例

2.1 HTML 结构(含 UI 交互区)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>File API 目录访问示例</title>
    <!-- 引入 Tailwind 简化样式(也可自定义 CSS) -->
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        .file-item { display: flex; align-items: center; padding: 8px; border-bottom: 1px solid #eee; }
        .file-icon { margin-right: 8px; font-size: 18px; }
        .file-info { flex: 1; }
        .file-size { color: #666; font-size: 14px; }
    </style>
</head>
<body class="p-8 bg-gray-50">
    <div class="max-w-4xl mx-auto bg-white p-6 rounded-lg shadow">
        <h2 class="text-2xl font-bold mb-4">File API 目录内容获取</h2>
        <!-- 触发按钮(隐藏原生 input) -->
        <button id="selectDirBtn" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
            选择本地目录
        </button>
        <input type="file" id="dirInput" webkitdirectory directory style="display: none;">
        
        <!-- 文件列表展示区 -->
        <div class="mt-4 border rounded-lg max-h-80 overflow-y-auto">
            <div id="fileList" class="p-4 text-center text-gray-500">
                请选择目录以查看文件列表
            </div>
        </div>
    </div>

    <script>
        // 2.2 JavaScript 逻辑实现
        const dirInput = document.getElementById('dirInput');
        const selectDirBtn = document.getElementById('selectDirBtn');
        const fileList = document.getElementById('fileList');

        // 1. 点击按钮触发原生 input 选择目录
        selectDirBtn.addEventListener('click', () => {
            dirInput.click();
        });

        // 2. 监听目录选择变化,处理文件数据
        dirInput.addEventListener('change', (e) => {
            const selectedFiles = e.target.files; // 获取选中目录下的所有文件(含子目录文件)
            if (selectedFiles.length === 0) {
                fileList.innerHTML = '<div class="p-4 text-center text-gray-500">未选择任何文件</div>';
                return;
            }
            
            // 3. 解析文件数据并渲染到页面
            let fileHtml = '';
            Array.from(selectedFiles).forEach(file => {
                // 判断是否为目录(通过 type 为空且 size 为 0 间接判断)
                const isDir = file.type === '' && file.size === 0;
                // 获取文件在目录中的相对路径(webkitRelativePath 为非标准属性,但主流浏览器支持)
                const relativePath = file.webkitRelativePath || file.name;
                // 格式化文件大小(辅助函数)
                const fileSize = isDir ? '—' : formatFileSize(file.size);

                fileHtml += `
                    <div class="file-item">
                        <span class="file-icon ${isDir ? 'text-yellow-500' : 'text-gray-400'}">
                            ${isDir ? '📁' : '📄'}
                        </span>
                        <div class="file-info">
                            <div class="font-medium">${file.name}</div>
                            <div class="text-xs text-gray-500">${relativePath}</div>
                        </div>
                        <div class="file-size text-sm">${fileSize}</div>
                    </div>
                `;
            });

            fileList.innerHTML = fileHtml;
        });

        // 辅助函数:格式化文件大小(Bytes → KB/MB/GB)
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const units = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return `${(bytes / Math.pow(k, i)).toFixed(2)} ${units[i]}`;
        }
    </script>
</body>
</html>

3. 关键特性与限制

优势:兼容性强(支持 Chrome 15+、Firefox 4+、Safari 6+),无需额外依赖,实现简单。

限制

  1. 无法直接识别 “目录” 类型,需通过 typesize 间接判断;
  2. 仅能获取选中目录下的 “扁平化文件列表”,无法递归获取子目录结构;
  3. 无文件读写能力,仅能获取元数据。

三、方案二:基于 FileSystem Access API 实现(功能优先)

1. 方案概述

FileSystem Access API 是 W3C 正在标准化的现代 API(目前主要支持 Chromium 内核浏览器,如 Chrome 86+、Edge 86+),提供 “目录选择、递归遍历、文件读写、持久化权限” 等更强大的能力。通过 window.showDirectoryPicker() 直接请求用户授权,授权后可主动遍历目录结构,支持复杂的文件操作。

2. 完整使用示例

2.1 HTML 结构(含子目录遍历功能)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>FileSystem Access API 目录访问示例</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        .dir-tree-item { padding: 4px 0 4px 16px; border-left: 1px solid #eee; }
        .dir-header { display: flex; align-items: center; cursor: pointer; padding: 4px 0; }
        .dir-icon { margin-right: 8px; }
        .file-meta { color: #666; font-size: 14px; margin-left: 8px; }
    </style>
</head>
<body class="p-8 bg-gray-50">
    <div class="max-w-4xl mx-auto bg-white p-6 rounded-lg shadow">
        <h2 class="text-2xl font-bold mb-4">FileSystem Access API 目录遍历</h2>
        <!-- 触发目录选择按钮 -->
        <button id="openDirBtn" class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
            打开并遍历目录
        </button>
        
        <!-- 目录树展示区 -->
        <div class="mt-4 border rounded-lg p-4 max-h-80 overflow-y-auto">
            <div id="dirTree" class="text-gray-500">
                请点击按钮选择目录
            </div>
        </div>
    </div>

    <script>
        // 2.2 JavaScript 逻辑实现(含递归遍历)
        const openDirBtn = document.getElementById('openDirBtn');
        const dirTree = document.getElementById('dirTree');

        openDirBtn.addEventListener('click', async () => {
            try {
                // 1. 检查浏览器兼容性
                if (!window.showDirectoryPicker) {
                    alert('您的浏览器不支持该功能,请使用 Chrome 或 Edge 浏览器');
                    return;
                }

                // 2. 请求用户选择目录(获取 DirectoryHandle 对象)
                const dirHandle = await window.showDirectoryPicker({
                    mode: 'read', // 权限模式:read(只读)/ readwrite(读写)
                    startIn: 'documents' // 默认打开目录(可选:documents、downloads 等)
                });

                // 3. 递归遍历目录结构并渲染
                dirTree.innerHTML = '<div class="text-center text-gray-500">正在读取目录...</div>';
                const treeHtml = await renderDirectoryTree(dirHandle, 0);
                dirTree.innerHTML = treeHtml;

            } catch (err) {
                // 捕获用户取消选择或权限拒绝错误
                if (err.name === 'AbortError') {
                    dirTree.innerHTML = '<div class="text-center text-gray-500">用户取消选择</div>';
                } else {
                    dirTree.innerHTML = `<div class="text-center text-red-500">错误:${err.message}</div>`;
                    console.error('目录访问失败:', err);
                }
            }
        });

        /**
         * 递归渲染目录树
         * @param {DirectoryHandle} handle - 目录/文件句柄
         * @param {number} depth - 目录深度(用于缩进)
         * @returns {string} 目录树 HTML
         */
        async function renderDirectoryTree(handle, depth) {
            const isDir = handle.kind === 'directory';
            const indent = 'margin-left: ' + (depth * 16) + 'px;'; // 按深度缩进
            let itemHtml = '';

            if (isDir) {
                // 处理目录:添加展开/折叠功能
                itemHtml += `
                    <div class="dir-header" style="${indent}" onclick="toggleDir(this)">
                        <span class="dir-icon text-yellow-500">📁</span>
                        <span class="font-medium">${handle.name}</span>
                        <span class="file-meta">(目录)</span>
                    </div>
                    <div class="dir-children" style="display: none;">
                `;

                // 遍历目录下的所有子项(递归)
                for await (const childHandle of handle.values()) {
                    itemHtml += await renderDirectoryTree(childHandle, depth + 1);
                }

                itemHtml += '</div>'; // 闭合 dir-children

            } else {
                // 处理文件:获取文件大小等元数据
                const file = await handle.getFile();
                const fileSize = formatFileSize(file.size);
                itemHtml += `
                    <div style="${indent} display: flex; align-items: center; padding: 4px 0;">
                        <span class="dir-icon text-gray-400">📄</span>
                        <span>${handle.name}</span>
                        <span class="file-meta">${fileSize}</span>
                    </div>
                `;
            }

            return itemHtml;
        }

        // 目录展开/折叠切换(全局函数,用于 HTML 内联调用)
        function toggleDir(el) {
            const children = el.nextElementSibling;
            children.style.display = children.style.display === 'none' ? 'block' : 'none';
            el.querySelector('.dir-icon').textContent = children.style.display === 'none' ? '📁' : '📂';
        }

        // 复用文件大小格式化函数(同方案一)
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const units = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return `${(bytes / Math.pow(k, i)).toFixed(2)} ${units[i]}`;
        }
    </script>
</body>
</html>

3. 关键特性与限制

优势

  1. 直接识别 “目录 / 文件” 类型(通过 handle.kind);
  2. 支持递归遍历目录结构,可实现 “目录树” 交互;
  3. 提供文件读写能力(通过 fileHandle.createWritable());
  4. 可请求持久化权限(handle.requestPermission()),下次访问无需重新授权。

限制:兼容性差,仅支持 Chromium 内核浏览器,Firefox 和 Safari 暂不支持。

四、两种方案对比分析

对比维度方案一(File API)方案二(FileSystem Access API)
浏览器兼容性强(支持所有现代浏览器)弱(仅 Chromium 内核浏览器)
目录识别能力间接判断(依赖 type 和 size)直接识别(handle.kind)
目录遍历能力仅扁平化列表,无递归支持支持递归遍历,可构建目录树
文件操作能力仅读取元数据,无读写能力支持文件读写、删除等完整操作
权限持久化不支持(每次刷新需重新选择)支持(可请求持久化权限)
交互体验依赖隐藏 input,体验较基础原生 API 调用,体验更流畅
适用场景兼容性优先的简单目录查看需求现代浏览器下的复杂文件管理需求

五、注意事项与最佳实践

if (window.showDirectoryPicker) {
    // 使用方案二(FileSystem Access API)
} else {
    // 使用方案一(File API)
}

以上就是前端JavaScript获取本地文件目录内容的两种实现方案的详细内容,更多关于前端JS获取本地文件目录内容的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文