在油猴脚本里读取本地文件,思路是:动态创建隐藏的 <input type="file">,触发点击,再用 FileReader 读取内容。
封装函数
/**
* 选择文件并读取内容
* @param {Object} options
* @param {string} [options.accept] - 文件类型限制,如 ".json,.txt"
* @param {string} [options.readAs="text"] - text | dataURL | arrayBuffer
* @param {string} [options.encoding="utf-8"] - 文本编码(仅 text 模式有效)
* @returns {Promise<{file: File, content: any}>}
*/
function selectFileAndRead(options = {}) {
const { accept = "", readAs = "text", encoding = "utf-8" } = options;
return new Promise((resolve, reject) => {
let input = document.getElementById("__file_input__");
if (!input) {
input = document.createElement("input");
input.type = "file";
input.id = "__file_input__";
input.style.display = "none";
document.body.appendChild(input);
}
input.accept = accept;
input.onchange = () => {
const file = input.files[0];
if (!file) {
reject(new Error("未选择文件"));
return;
}
const reader = new FileReader();
reader.onload = (e) => resolve({ file, content: e.target.result });
reader.onerror = () => reject(new Error("读取失败"));
switch (readAs) {
case "dataURL":
reader.readAsDataURL(file);
break;
case "arrayBuffer":
reader.readAsArrayBuffer(file);
break;
default:
reader.readAsText(file, encoding);
}
};
// 必须重置 value,否则选同一个文件不会触发 change 事件
input.value = "";
input.click();
});
}
使用示例
读取文本文件
selectFileAndRead({ accept: ".txt,.json" }).then(({ file, content }) => {
console.log(file.name, content);
});
读取 JSON 并解析
selectFileAndRead({ accept: ".json" })
.then(({ content }) => JSON.parse(content))
.then((data) => console.log("JSON 数据:", data))
.catch((err) => console.error(err));
读取图片为 base64
selectFileAndRead({ accept: "image/*", readAs: "dataURL" }).then(
({ content }) => {
const img = document.createElement("img");
img.src = content;
document.body.appendChild(img);
}
);
async/await 版
document.addEventListener("keydown", async (e) => {
if (e.ctrlKey && e.key === "u") {
try {
const { file, content } = await selectFileAndRead({ accept: ".json" });
const config = JSON.parse(content);
applyConfig(config);
} catch (err) {
console.error(err);
}
}
});
关键坑
浏览器安全限制:input.click() 必须在用户事件(点击、键盘)的同步调用链里触发,不能在 setTimeout 或 Promise 回调里调用,否则会被拦截。
同一文件无法重复选择:input.value = "" 在 click() 前必须执行,否则选同一个文件第二次不会触发 change 事件。
中文乱码:Windows 下生成的文件有时是 GBK 编码:
selectFileAndRead({ encoding: "gbk" });
DOM 污染:函数复用同一个 __file_input__ 元素,不会重复创建,避免 DOM 堆积导致的性能问题。
在油猴脚本中的完整示例
// ==UserScript==
// @name 配置文件导入
// @namespace http://tampermonkey.net/
// @version 1.0
// @match https://example.com/*
// @grant none
// ==/UserScript==
(function () {
"use strict";
// 插入导入按钮
const btn = document.createElement("button");
btn.textContent = "导入配置";
btn.style.cssText = "position:fixed;top:10px;right:10px;z-index:9999";
document.body.appendChild(btn);
btn.addEventListener("click", async () => {
const { content } = await selectFileAndRead({ accept: ".json" });
const config = JSON.parse(content);
console.log("已加载配置:", config);
});
// ... selectFileAndRead 函数定义 ...
})();