我给自己写了一个本地截图标注工具:安静、不上传、导出即走实录

举报
yd_294377644 发表于 2026/10/26 21:23:52 2026/10/26
【摘要】 截图后想加个箭头、圈个重点、打个马赛克,市面工具要么功能臃肿、要么强推云端、要么标注完还要登录才能导出。我想要一个安静、本地、标注完即存的截图工具,于是自己写了一个。这篇博客记录从需求到落地的全过程:技术选型、画布实现、标注工具、导出心得,以及那些自己写一遍才明白的细节。

目录

  1. 缘起:为什么我不想要任何云端截图工具
  2. 需求拆解:从"能截图"到"能标注"
  3. 技术选型:浏览器 + Canvas 就够了
  4. 核心实现一:截图粘贴与画布初始化
  5. 核心实现二:标注工具与撤销栈
  6. UI 定制:标注工具也是"自己的工作台"
  7. 三个让我卡了一阵的坑
  8. 结尾:截图工具的"即用即走"哲学

1. 缘起:为什么我不想要任何云端截图工具

我每天要截图几十次——bug 复现、设计标注、文档配图。截图本身不难,难的是截图之后:

  • 想加个箭头指向重点,要打开一个重型编辑器;
  • 想打个马赛克遮敏感信息,云端工具会先把图传上去;
  • 标注完想导出,弹窗提示"登录会员才能去水印"。

我忽然意识到:截图标注是最高频的本地操作,凭什么要联网、要登录、要等上传。既然现成工具都在制造摩擦,那就自己写一个。

2. 需求拆解:从"能截图"到"能标注"

把"做一个截图标注工具"拆成可验收清单:

诉求(人话) 拆解后(可验收)
能导入图 Ctrl+V 粘贴 / 拖入文件 / 选文件三种入口
能标注 箭头、矩形、椭圆、画笔、文字、马赛克六种工具
能撤销 Ctrl+Z / Ctrl+Y 撤销重做,不限步数
能导出 保存为 PNG,可选复制到剪贴板
不上传 全程本地,不发起任何网络请求
UI 我说了算 工具栏、配色、画布背景可自定义

这张表决定了工具的骨架。功能不多,但每一项都做到位——这是我对所有"自己用的工具"的一贯要求。

3. 技术选型:浏览器 + Canvas 就够了

我犹豫过要不要上 Electron,但仔细想需求——我不需要全局截图快捷键、不需要系统托盘,浏览器 + Canvas 完全够用:

能力 方案
粘贴截图 clipboardData.items 读 image
画布绘制 Canvas 2D API
标注交互 自实现工具状态机
撤销重做 操作栈 + 重绘
导出 canvas.toDataURL + a[download]
复制到剪贴板 ClipboardItem + navigator.clipboard.write

整个工具就是一个 HTML 文件,双击就能开。没有安装包、没有更新提示、没有后台进程——图片全程在内存里,关掉即消失,不留痕迹。

心得:选型时先问"我到底需要什么"。本地小工具的最高境界是双击即用、关掉即走。

4. 核心实现一:截图粘贴与画布初始化

粘贴是最高频的入口,必须做到"Ctrl+V 即进画布":

document.addEventListener('paste', e => {
    for (const item of e.clipboardData.items) {
        if (item.type.startsWith('image/')) {
            const blob = item.getAsFile();
            const img = new Image();
            img.onload = () => initCanvas(img);
            img.src = URL.createObjectURL(blob);
            break;
        }
    }
});

画布初始化的关键:按图片真实尺寸建画布,不缩放。缩放会损失精度,标注时再按视口缩放显示即可:

function initCanvas(img) {
    canvas.width  = img.naturalWidth;   // 真实分辨率
    canvas.height = img.naturalHeight;
    ctx.drawImage(img, 0, 0);
    fitToViewport();                     // CSS 缩放显示
}

心得:截图工具的"专业感"来自一个细节——导出的图和原图同分辨率。任何"标注完画质变糊"的工具都是不合格的。

5. 核心实现二:标注工具与撤销栈

每个标注工具是一个状态机:idle → drawing → done。以箭头为例:

const tool = {
    name: 'arrow',
    onDown(x, y) { this.start = {x, y}; this.end = {x, y}; },
    onMove(x, y) { this.end = {x, y}; redraw(); drawArrow(this.start, this.end); },
    onUp()       { stack.push({type:'arrow', ...this.start, ...this.end}); },
};

撤销栈是核心。每个标注是一个独立操作对象,重绘时按顺序回放:

function redraw() {
    ctx.drawImage(original, 0, 0);   // 重画底图
    for (const op of stack) drawOp(op);  // 回放所有标注
}

function undo() { stack.pop(); redoStack.push(...); redraw(); }
function redo() { stack.push(redoStack.pop()); redraw(); }

几个让体验"顺手"的细节:

  • 马赛克是独立工具,不是滤镜。选中区域才打码,不污染整图;
  • 标注时实时预览。onMove 里重绘 + 画当前正在拖的形状,松手才入栈;
  • 工具切换不清空栈。画完箭头切矩形,之前的箭头还在,撤销仍可回退。

心得:标注工具的核心指标是"手跟得上眼"。任何拖动延迟、松手跳变都会破坏手感,requestAnimationFrame 重绘是底线。

6. UI 定制:标注工具也是"自己的工作台"

本地工具最大的特权,是 UI 完全我说了算。我给自己定了几个商业软件不会这么做的设计选择:

  • 画布占满中央,工具栏左侧细窄。标注时视觉焦点全在图上,工具不抢戏;
  • 暗色画布背景。亮色背景会让浅色截图边缘融化,暗色让任何图都"浮"出来;
  • 无导出水印、无登录提示。标注完按 Ctrl+S 即存,没有"升级会员"弹窗;
  • 快捷键全覆盖。A 箭头、R 矩形、E 椭圆、B 画笔、T 文字、M 马赛克,左手不离键盘。

这些设计在商业产品里几乎不可能出现——它们太"个人化"了。但本地工具的意义,恰恰就是让软件贴合你的标注习惯,而不是让你迁就软件的功能预设。

7. 三个让我卡了一阵的坑

坑一:高 DPI 屏幕模糊。 画布 CSS 尺寸和实际像素不一致,标注线条发虚。解决办法是按 devicePixelRatio 放大画布像素,再 CSS 缩放回显示尺寸——所有 Canvas 工具都要过这一关。

坑二:马赛克要遮原图不能遮标注。 如果先画标注再打马赛克,标注也会被遮。正确顺序是马赛克直接改写底图层,而不是作为普通标注入栈。这是个反直觉的设计——有些"标注"本质是破坏性编辑,要单独处理。

坑三:复制到剪贴板要 ClipboardItem。 旧式 document.execCommand('copy') 复制不了图片。必须用 navigator.clipboard.write([new ClipboardItem({'image/png': blob})]),且要在用户手势里调用(不能异步延迟太久)。

8. 结尾:截图工具的"即用即走"哲学

做完这个工具,我对"截图标注"有了一种更深的体会:

  1. 它不抢你注意力。没有云空间、没有历史记录、没有"猜你想标注什么";
  2. 它尊重你的图片。全程本地,不发起任何网络请求,敏感信息放心打码;
  3. 它尊重你的时间。Ctrl+V 进图、标注、Ctrl+S 导出,三步完事。

在这个所有软件都在抢用户时长的时代,一个"打开就标注、关掉即消失"的本地工具,反而成了一种奢侈。

如果你也每天截图、也想要一个安静的标注工具,我强烈建议自己写一个。技术门槛比想象中低,一个 HTML 文件就够;真正难的是克制住加功能的冲动——不做云同步、不做历史记录、不做"AI 自动标注",就做一个老老实实的本地画板。

工具小而静,价值往往不在多,而在"它永远在那里,用完即走"。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。