一个纯浏览器实现的、完全离线的文件传输工具,通过二维码序列在没有网络连接的环境下传输文件。不需要任何服务器,不需要安装软件,仅需浏览器和摄像头。
项目依赖以下 JavaScript 库,需要手动下载到 js/ 文件夹:
| 库文件 | 用途 | 下载地址 |
|---|---|---|
pako.min.js |
压缩/解压 (zlib) | https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.0/pako.min.js |
qrcode.min.js |
二维码生成(发送端) | https://raw.githubusercontent.com/davidshimjs/qrcodejs/master/qrcode.min.js |
localforage.min.js |
本地存储(接收端) | https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js |
index.min.js |
二维码扫描(接收端,ZXing) | https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/umd/index.min.js |
Base45 编解码已内置于 HTML 中,无需额外文件。
离线文件二维码传输系统/
├── send/ # 发送端程序
│ ├── send.html # 发送端主文件
│ └── js/ # 发送端依赖库
│ ├── pako.min.js # 压缩库
│ └── qrcode.min.js # 二维码生成库
└── receiver/ # 接收端程序
├── receiver.html # 接收端主文件
└── js/ # 接收端依赖库
├── index.min.js # ZXing二维码扫描库
├── localforage.min.js # 本地存储库
└── pako.min.js # 解压缩库
发送端(电脑):
send.html接收端(手机/另一台电脑):
receiver.html发送端:
文件 → 读取为 ArrayBuffer → pako.deflate (level 9) 压缩
→ Base45 编码整个压缩数据 → 按字符数分片
→ 每个分片包装成 JSON {i, t, h, f, d}
→ 生成二维码图片 → 显示并支持导航
接收端:
扫描二维码 → 解析 JSON → 校验 CRC32 → 存储分片(IndexedDB)
→ 检测是否收齐所有数据分片和文件名分片
→ 自动拼接 Base45 字符串 → Base45 解码
→ pako.inflate 解压 → 创建 Blob 下载
数据分片 (t 字段不存在或为 'data'):
{
"i": 0, // 分片索引(从0开始)
"t": 5, // 总分片数
"h": "abc12", // CRC32 校验码(5位36进制)
"f": "x9k2m", // 文件指纹(5位随机字符串,标识同一文件)
"d": "ABCD..." // 分片数据(Base45 编码的片段)
}
文件名分片 ("t": "fn"):
{
"t": "fn", // 类型标识
"f": "x9k2m", // 文件指纹
"n": "JUU5JTlG...",// Base64 编码的文件名(支持中文)
"s": 102400, // 原始文件大小(字节)
"ts": 1712345678, // 时间戳
"tc": 5, // 数据分片总数
"h": "def34" // CRC32 校验(对除 h 外的字段计算)
}
项目中 Base45 采用纯 JavaScript 实现,符合 RFC 9285 标准。
d 字段(Base45 字符串)计算 CRC32,取 36 进制后 5 位作为 h。d 的 CRC32,与 h 比对,不一致则丢弃该分片。t, f, n, s, ts, tc 字段的 JSON 字符串计算 CRC32。使用 pako.crc32 保证与 pako 库一致,备用实现兼容无 pako 环境。
← / ↑ 上一张,→ / ↓ 下一张(全局监听,页面任意位置生效)。localforage 库存储已接收的分片和文件信息,刷新页面或关闭浏览器后数据不丢失。receivedDataChunks.size >= totalDataChunks 且 fileNameData 存在,满足条件立即调用 assembleFileAutomatically()。d 字段 → 完整 Base45 字符串。base45.decode() 得到压缩后的 Uint8Array。pako.inflate() 解压得到原始文件字节。const compressed = pako.deflate(new Uint8Array(fileBuffer), { level: 9 });
level: 9 为最高压缩级别,对文本类文件(如 JSON、HTML、日志)可压缩至原大小的 20%~40%,对已压缩文件(图片、视频)效果有限但无负面影响。| 文件大小 | 压缩后大小 | Base45 长度 | 分片数 (1200字符/片) | 建议 |
|---|---|---|---|---|
| 100 KB | ~60 KB | ~90 KB | 75 数据 + 1 文件名 | 快速 |
| 500 KB | ~300 KB | ~450 KB | 375 + 1 | 可接受 |
| 1 MB | ~600 KB | ~900 KB | 750 + 1 | 较慢,建议调大分片 |
| ≥ 2 MB | ≥1.2 MB | ≥1.8 MB | ≥1500 + 1 | 不推荐(二维码数量过多) |
实测:2KB 文件约需 2-3 个二维码,5MB 文件约需 400-500 个二维码(扫描耗时约 15-20 分钟)。
| 浏览器 | 发送端 | 接收端 | 备注 |
|---|---|---|---|
| Chrome 80+ | ✅ | ✅ | 最佳体验 |
| Edge 80+ | ✅ | ✅ | Chromium 内核 |
| Firefox 75+ | ✅ | ✅ | 完全支持 |
| Safari 14+ | ✅ | ✅ | 需授予摄像头权限 |
| 移动端浏览器 | ✅ (生成) | ✅ (扫描) | 接收端扫码效果好 |
generateShortFileId(): 生成5位随机文件指纹(时间戳+随机数转36进制)。encodeFileName(): 将原始文件名 UTF-8 编码后 Base64,保证中文传输。processFileChunks(): 压缩 → Base45 整体编码 → 按字符分片 → 构造分片对象。generateSingleQR(): 异步生成单个二维码图片,支持深蓝色文件名二维码。showQRAtIndex() / goPrev() / goNext(): 单视图导航逻辑。processChunkData(): 解析 JSON,区分数据/文件名分片,校验 CRC32,存储到 IndexedDB。checkCompletion(): 检查所有分片是否收齐,触发自动重组。assembleFileAutomatically(): 拼接 Base45 → 解码 → 解压 → 创建下载。loadSavedChunks(): 页面加载时从 IndexedDB 恢复进度。updateUI(): 更新进度条、分片网格显示。Q: 扫描时提示“找不到摄像头”?
A: 请确保已在浏览器权限设置中允许摄像头访问,且设备有摄像头硬件。部分浏览器需要 HTTPS 环境(本地 file:// 协议也可能工作,但推荐使用本地 HTTP 服务器)。
Q: 为什么需要 Base45?直接用 Base64 不行吗?
A: Base64 膨胀率更高,同样大小的二维码可容纳的有效数据更少,分片数量更多。Base45 是专为二维码设计的标准,能提高约 10% 的传输效率。
Q: 分片大小设置为多少合适?
A: 推荐 1200 字符。过小会导致二维码数量增多,过大会超过 JSON 长度限制(≥1800 字符可能无法生成二维码)。若生成时提示 JSON 过大,请减小分片大小。
Q: 接收端能兼容旧版本(Base64 编码)的二维码吗?
A: 不兼容。此版本使用 Base45 编码,与之前的 Base64 版本数据格式不同。如果之前有旧二维码,请用旧版接收端。
Q: 为什么接收端没有“重组文件”按钮?
A: 为了提高自动化程度,接收端检测到所有分片完成后会立即自动重组,无需手动点击。重组成功后显示下载按钮。
MIT License。欢迎自由使用、修改、分发。
⭐ 如果这个项目对您有帮助,请给个Star! 您的支持是我们持续改进的动力!
注意: 本项目仍在积极开发中,欢迎反馈和建议!
| 最后更新: 2025年12月 | 版本: 1.0 |