web系列之打开文件夹showDirectoryPicker

前言

通过浏览器直接打开文件夹的功能,用户可以在不离开浏览器的情况下,轻松管理本地文件,实现浏览器打开文件夹及读取文件的操作。

本文主要介绍Web API中的showDirectoryPicker()方法,并实现一个类似vscode打开文件的功能。

1. showDirectoryPicker介绍

showOpenFilePicker() 方法用于显示一个文件选择器,以允许用户选择一个或多个文件并返回这些文件的句柄。通过这个对象,可以读取文件夹中的文件列表,可以对文件夹进行遍历和操作。

具体属性参考: MDN-showOpenFilePicker

2. 实现网页版vscode打开文件功能

案例: 实现一个按钮读取文件夹,并读取任意文件的内容进行展示。

具体实现过程:

  1. 通过showDirectoryPicker读取文件夹内容,通过返回的句柄生成目录结构
  2. 点击文件,通过句柄得到file对象,获取内容
  3. 使用highlightjs进行代码修饰

具体代码如下:

html 复制代码
<!DOCTYPE html>
<style>
	.container {
		display: flex;
		height: 100vh;
	}

	.left {
		width: 30%;
		height: 100%;
		overflow: auto;
		padding: 16px;
		border: 1px solid #999;
	}

	.right {
		width: 70%;
		height: 100%;
		overflow: auto;
		padding: 16px;
		border: 1px solid #999;
		border-left: 0;
	}


	.tree-node {
		cursor: pointer;
		/* 为伪元素设置定位上下文 */
		position: relative;
	}

	.tree-node::before {
		/* 使用Unicode字符表示箭头  向下箭头*/
		content: '▾';
		/* content: '▶'; */
		display: inline-block;
		/* 为箭头和文字之间添加间距 */
		margin-right: 5px;
		/* 平滑变换箭头方向 */
		transition: transform 0.2s ease-in-out;
	}

	.tree-node.expanded::before {
		/* 旋转箭头以表示展开状态 */
		transform: rotate(-90deg);
	}

	.ml-20 {
		margin-left: 20px;
	}

	.content {
		height: 200px;
	}
</style>

<body>
	<div class="container">
		<div class="left">
			<button>打开文件夹</button>
			<div id="folder"></div>
		</div>
		<div class="right">
			<pre>
				<code id="content">
						
				</code>
			</pre>
		</div>
	</div>

	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
	<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>

	<!-- and it's easy to individually load additional languages -->
	<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>

	<script>


		const folderNode = document.getElementById('folder')
		const contentNode = document.getElementById('content')
		const btnNode = document.querySelector('button')


		btnNode.onclick = async () => {
			//使用showDirectoryPicker()方法打开文件夹选择器,并获取文件夹句柄(handle)
			const handle = await showDirectoryPicker()
			await processHandle(handle)
			// 将文件夹内容添加到页面中
			let container = processHtml(handle)
			folderNode.appendChild(container)
		}
		/**
		 * 读取文件内容
		 * @param {*} handle 
		 * @returns 
		 */
		async function readFile(handle) {
			return new Promise(async (resolve, reject) => {
				const file = await handle.getFile()
				const fileReader = new FileReader()
				fileReader.readAsText(file)
				fileReader.onload = e => {
					resolve(e.target.result)
				}
			})
		}


		/**
		 * 递归处理句柄
		 * @param {*} handle 
		 * @returns 
		 */
		function processHtml(handle) {
			const isFolder = handle.kind === 'directory'
			const div = document.createElement('div')
			div.textContent = handle.name
			div.classList.add('ml-20')
			if (isFolder) {
				div.classList.add('tree-node')
				div.dataset.isFolder = true
				// 添加折叠效果
				div.addEventListener('click', function (e) {
					// 如果点击的是文件夹,则进行展开或折叠  
					e.stopPropagation(); // 阻止事件冒泡,防止父级文件夹也被点击  
					// 切换展开/折叠状态  
					div.classList.toggle('expanded');
					// 将子元素隐藏
					// 获取所有的直接子元素  
					const childElements = div.children;
					// 遍历所有的直接子元素并将它们隐藏  
					for (let i = 0; i < childElements.length; i++) {
						let isHidden = childElements[i].style.display === 'none';
						childElements[i].style.display = isHidden ? 'block' : 'none';
					}
				});
				// 递归处理文件夹目录
				if (handle.children.length) {
					for (let i = 0; i < handle.children.length; i++) {
						const element = handle.children[i];
						const eleHtml = processHtml(element)
						div.appendChild(eleHtml)
					}
				}
			} else {

				div.dataset.isFolder = false
				// 添加背景色
				div.style.backgroundColor = '#f0f0f0'
				div.addEventListener('click', async function (e) {
					e.stopImmediatePropagation(); // 阻止事件冒泡,防止父级文件夹也被点击  
				const	content = await readFile(handle)
					contentNode.innerText = content
					hljs.highlightAll();
				})
			}
			return div
		}

		/** 
		 * 递归处理文件夹目录
		 * @param {*} handle 
		 * @returns 
		 */
		async function processHandle(handle) {
			if (handle.kind === 'file') {
				return;
			}
			handle.children = []
			// 获取文件夹中的所有内容
			const iter = await handle.entries()
			// 返回异步迭代器
			for await (const entry of iter) {
				await processHandle(entry[1])
				handle.children.push(entry[1])
			}
		}
	</script>
</body>

</html>

在上面的代码中:

  1. 确定访问权限:我们可以看到如何使用showDirectoryPicker()方法打开文件夹,此时会询问是否有查看的权限。
  1. 遍历文件结构:通过processHandle()函数对文件夹进行遍历,processHandle()函数是一个递归函数,它会遍历文件夹中的所有子项,并将它们添加到handle.children数组中。这样我们就可以在控制台中看到文件夹的完整结构。
  1. 读取文件内容:点击文件夹中的任意文件,并通过getFile()方法获取了它的File对象。使用FileReader对象读取了文件的内容,并在控制台中打印出来。
  1. 最后通过highlightjs进行代码装饰即可。

效果如下:

3. 总结

最后总结一下:

  • 通过Web API中的showDirectoryPicker()方法实现浏览器打开文件夹的功能,返回一个代表该文件夹的DirectoryHandle对象。通过这个对象,可以获取层级结构。点击任一文件通过getFile()方法获取了它的File对象。使用FileReader对象读取了文件的内容并展示。

如有错误,请指正O^O!

相关推荐
钢铁小狗侠13 分钟前
前端(3)——快速入门JaveScript
前端
咔咔库奇16 分钟前
CSS基础知识04
前端·css
Black蜡笔小新31 分钟前
无插件H5播放器EasyPlayer.js网页web无插件播放器选择全屏时,视频区域并没有全屏问题的解决方案
前端·javascript·音视频
Augenstern、1 小时前
vue3 element el-table实现表格动态增加/删除/编辑表格行,带有校验规则
前端·javascript·vue.js
A黄俊辉A1 小时前
electron安装遇到的问题
前端·javascript·electron
lvbb661 小时前
ES6更新的内容中什么是proxy
前端·ecmascript·es6
痕忆丶1 小时前
鸿蒙北向开发 : hdmfs-分布式文件系统
前端
赵锦川1 小时前
微信小程序设置屏幕安全距离
前端·javascript·vue.js
GISer_Jing2 小时前
Javascript——设计模式(一)
前端·javascript·设计模式
yqcoder2 小时前
react 中 useCallback Hook 作用
前端·javascript·react.js