lyrstar.com

纯前端工具页的几个实现细节

2 分钟
目录

工具页的逻辑看起来都很简单,但每个都有几处不按直觉走的地方。记录几个实际遇到的。

btoa 处理不了中文

btoa 只接受每个字符码位在 0-255 范围内的字符串,传中文会直接抛 InvalidCharacterError:

btoa('你好'); // InvalidCharacterError

正确做法是先用 TextEncoder 转成字节,再逐字节交给 btoa:

function encodeBase64(text) {
  const bytes = new TextEncoder().encode(text);
  let binary = '';
  for (const byte of bytes) binary += String.fromCharCode(byte);
  return btoa(binary);
}

解码时反向走一遍,用 TextDecoder 还原。注意别用 String.fromCharCode(...bytes) 展开大数组——参数过多会栈溢出,长文本上必翻车。

Web Crypto 没有 MD5

crypto.subtle.digest 支持 SHA-1 / SHA-256 / SHA-384 / SHA-512,但不支持 MD5,而且这是规范层面的有意取舍,不是浏览器没实现。

MD5 早就不该用于安全场景,但校验文件完整性、对接老系统时还是会碰到。只能自己实现或引第三方库。于是同一个工具页里会出现两条代码路径:MD5 走库,SHA 系列走原生 API。

另外 crypto.subtle 只在安全上下文(HTTPS 或 localhost)下可用。本地用 IP 访问开发服务器时它是 undefined,这个坑很容易误判成代码写错了。

剪贴板要准备退路

navigator.clipboard.writeText 同样要求安全上下文,而且可能被权限策略拒绝。退路是老掉牙的 execCommand('copy'):

try {
  await navigator.clipboard.writeText(text);
} catch {
  const ta = document.createElement('textarea');
  ta.value = text;
  ta.style.position = 'fixed';
  ta.style.opacity = '0';
  document.body.appendChild(ta);
  ta.select();
  document.execCommand('copy');
  document.body.removeChild(ta);
}

execCommand 已经废弃,但目前没有浏览器真的移除它,作为兜底仍然可靠。几个细节:临时 textarea 必须真的插进文档才能 select(),并且要 position: fixed 配合透明,否则页面会闪一下跳动。

JSON 错误要换算成行列

JSON.parse 抛出的 SyntaxError 只告诉你字符偏移量:

Unexpected token } in JSON at position 42

对着几百行的 JSON,position 42 没什么用。换算成行列号只要几行:

const before = text.slice(0, pos);
const line = before.split('\n').length;
const column = pos - before.lastIndexOf('\n');

不同引擎的报错文案并不统一(V8、SpiderMonkey、JavaScriptCore 各写各的),所以要用正则去抓 position,抓不到就原样展示消息,别假设格式固定。

时间戳的位数判断

时间戳输入框要同时接受秒和毫秒。按位数猜是最省事的办法:10 位当秒,13 位当毫秒。

更稳的判断是看数值范围——小于 10^11 的基本都是秒级(10^11 秒已经是公元 5138 年),超过的按毫秒处理。这样粘贴进来的时间戳带不带引号、有没有多余空格都不影响判断。