Skip to content

实践:利用ArrayBuffer实现预览指定目录下的所有文件的内容

效果

先上效果图,非常朴素,没有用代码高亮插件无样式表

图片

ArrayBuffer是什么

ArrayBuffer对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(TypedArray视图和DataView视图)来读写,视图的作用是以指定格式解读二进制数据。

货比较硬,这里就不展开详细介绍,后续还是通过实例的方式介绍一下这些硬货的用处

读取文件内容

  1. 通过使用 FileReader.readAsArrayBuffer方法将File对象转为ArrayBuffer
  2. 使用TextDecoder将内容转为utf8格式的文本
js
function readFile2Text(file) {
    const fileReader = new FileReader()
    fileReader.readAsArrayBuffer(file)
    return new Promise(resolve=>{
        fileReader.onload = function () {
            const buf = this.result
            const textDecoder = new TextDecoder('utf8')
            resolve(textDecoder.decode(buf))
        }
    })
}
function readFile2Text(file) {
    const fileReader = new FileReader()
    fileReader.readAsArrayBuffer(file)
    return new Promise(resolve=>{
        fileReader.onload = function () {
            const buf = this.result
            const textDecoder = new TextDecoder('utf8')
            resolve(textDecoder.decode(buf))
        }
    })
}

当然这里也可直接用FileReader.readAsText

为了契合主题强行readAsArrayBuffer

js
function readFile2Text(file) {
    const fileReader = new FileReader()
    fileReader.readAsText(file)
    return new Promise(resolve=>{
        fileReader.onload = function () {
            resolve(this.result)
        }
    })
}
function readFile2Text(file) {
    const fileReader = new FileReader()
    fileReader.readAsText(file)
    return new Promise(resolve=>{
        fileReader.onload = function () {
            resolve(this.result)
        }
    })
}

读取指定目录下的所有文件

使用input标签选择目录,只需要给input标签添加webkitdirectorymultiple属性即可,详细介绍请查阅MDN:<input type="file">:

  • webkitdirectory: 只允许选择目录
  • multople: 允许选择多个文件

页面代码

html
<input type="file" id="file" webkitdirectory multiple>
<input type="file" id="file" webkitdirectory multiple>

监听dom的onchange事件,获取选择的所有文件(不包含空目录)

逻辑

js
const $file = document.getElementById('file')

// 选择目录
$file.onchange = function () {
    const files = this.files
    // 打印获取所有的文件
    console.log(files)
}
const $file = document.getElementById('file')

// 选择目录
$file.onchange = function () {
    const files = this.files
    // 打印获取所有的文件
    console.log(files)
}

每个file对象包含以下属性

js
{
    name: String, // 文件吗
    size: Number, // 文件大小
    type: String, // 文件MIME类型
    webkitRelativePath: String, // 文件相对路径
}
{
    name: String, // 文件吗
    size: Number, // 文件大小
    type: String, // 文件MIME类型
    webkitRelativePath: String, // 文件相对路径
}

树型目录生成

使用ul配合li实现

Dom结构

html
<div id="lists" style="width: 36%;">
    <ul>
        <li path="test" deep="0">test
            <ul>
                <li path="test/logo.jpeg" deep="1">logo.jpeg</li>
                <li path="test/1" deep="1">1
                    <ul>
                        <li path="test/1/1-2" deep="2">1-2
                            <ul>
                                <li path="test/1/1-2/index.js" deep="3">index.js</li>
                            </ul>
                        </li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
</div>
<div id="lists" style="width: 36%;">
    <ul>
        <li path="test" deep="0">test
            <ul>
                <li path="test/logo.jpeg" deep="1">logo.jpeg</li>
                <li path="test/1" deep="1">1
                    <ul>
                        <li path="test/1/1-2" deep="2">1-2
                            <ul>
                                <li path="test/1/1-2/index.js" deep="3">index.js</li>
                            </ul>
                        </li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
</div>

展示

  • test
    • logo.jpeg
    • 1
      • 1-2
        • index.js

具体实现

页面代码

html
<input type="file" id="file" webkitdirectory multiple>
<div id="lists"></div>
<input type="file" id="file" webkitdirectory multiple>
<div id="lists"></div>

逻辑

将文件的相对路径通过/拆分:test/abc/index.js => ['test','abc','index.js']

js
const $file = document.getElementById('file')
const $lists = document.getElementById('lists')
// 选择目录
$file.onchange = function () {
    const files = this.files
    // 全部清空
    $lists.innerHTML = ''
    // 拆解目录
    for (const f of files) {
        f.paths = f.webkitRelativePath.split('/')
    }
    appendDir($lists, files)
}
const $file = document.getElementById('file')
const $lists = document.getElementById('lists')
// 选择目录
$file.onchange = function () {
    const files = this.files
    // 全部清空
    $lists.innerHTML = ''
    // 拆解目录
    for (const f of files) {
        f.paths = f.webkitRelativePath.split('/')
    }
    appendDir($lists, files)
}

appendDir:

  • parent:父节点
  • files: 文件
  • deep: 目录深度

通过此方法,生成指定某一级的目录

js
function appendDir(parent, files, deep = 0) {
    const $ul = document.createElement('ul')
    // 使用Set存储所有的文件的公共前缀
    // 利用Set自动去重
    const dirs = new Set()
    for (const f of files) {
        // 取相同深度的目录
        const p = f.paths[deep]

        // 深度为0,说明是选择的那一个目录
        if (deep === 0) {
            dirs.add(p)
        } else {
            // 获取父节点对应的相对目录
            const parentP = parent.getAttribute('path')
            // 判断文件是否属于此父目录
            if (f.webkitRelativePath.startsWith(parentP)) {
                // 存放符合条件的文件路径
                dirs.add([parentP, p].join('/'))
            }
        }
    }
    // 
    for (const d of dirs) {
        const $li = document.createElement('li')

        // 只展示文件名/或目录名 (test/abc/index.js => index.js)
        const idx = d.lastIndexOf('/') + 1
        $li.textContent = idx===0 ? d : d.slice(idx)

        // 记录这个节点的深度与完整相对路径
        $li.setAttribute('path', d)
        $li.setAttribute('deep', deep)
        $ul.appendChild($li)
    }
    // 插入页面
    if (dirs.size !== 0) {
        parent.appendChild($ul)
    }
}
function appendDir(parent, files, deep = 0) {
    const $ul = document.createElement('ul')
    // 使用Set存储所有的文件的公共前缀
    // 利用Set自动去重
    const dirs = new Set()
    for (const f of files) {
        // 取相同深度的目录
        const p = f.paths[deep]

        // 深度为0,说明是选择的那一个目录
        if (deep === 0) {
            dirs.add(p)
        } else {
            // 获取父节点对应的相对目录
            const parentP = parent.getAttribute('path')
            // 判断文件是否属于此父目录
            if (f.webkitRelativePath.startsWith(parentP)) {
                // 存放符合条件的文件路径
                dirs.add([parentP, p].join('/'))
            }
        }
    }
    // 
    for (const d of dirs) {
        const $li = document.createElement('li')

        // 只展示文件名/或目录名 (test/abc/index.js => index.js)
        const idx = d.lastIndexOf('/') + 1
        $li.textContent = idx===0 ? d : d.slice(idx)

        // 记录这个节点的深度与完整相对路径
        $li.setAttribute('path', d)
        $li.setAttribute('deep', deep)
        $ul.appendChild($li)
    }
    // 插入页面
    if (dirs.size !== 0) {
        parent.appendChild($ul)
    }
}

利用事件代理监听li的点击事件

js
$lists.addEventListener('click', function (e) {
    const $li = e.target
    // 不是li不管
    if ($li.tagName.toLowerCase() !== 'li') {
        return
    }
    // 获取点击节点的路径与深度
    const path = $li.getAttribute('path')
    const deep = +$li.getAttribute('deep')

    // 获取选择的所有文件
    const files = $file.files

    // 遍历文件,判断点击的是文件还是目录

    for (const f of files) {
        // 点击的文件
        if (f.webkitRelativePath === path) {
            // 预览内容
            previewFile(f)
            return
        } 
    }

    // 有子项,点击的目录且未被点击添加过
    if ($li.children.length === 0) {
        appendDir($li, files, deep + 1)
    }
})
$lists.addEventListener('click', function (e) {
    const $li = e.target
    // 不是li不管
    if ($li.tagName.toLowerCase() !== 'li') {
        return
    }
    // 获取点击节点的路径与深度
    const path = $li.getAttribute('path')
    const deep = +$li.getAttribute('deep')

    // 获取选择的所有文件
    const files = $file.files

    // 遍历文件,判断点击的是文件还是目录

    for (const f of files) {
        // 点击的文件
        if (f.webkitRelativePath === path) {
            // 预览内容
            previewFile(f)
            return
        } 
    }

    // 有子项,点击的目录且未被点击添加过
    if ($li.children.length === 0) {
        appendDir($li, files, deep + 1)
    }
})

到此的生成目录效果如下:

图片

最后

ArrayBuffer的内容还是比较多,本文只简单讲了利用其获取文件内容

本文主要内容还是实践生成目录的树形结构,由于时间仓促,代码还有很多的优化空间

更新于: