Annotorious入门教程:图片注释工具
本文简介
最近有工友问我前端怎么给图片做标注。使用 Fabric.js 或者 Konva.js 等库确实可以实现,但我又好奇有没有专门做图片标注的工具呢?
在网上搜了一下发现 Annotorious 可以实现这个功能。Annotorious 提供了图片注释和标注功能,而且用法很简单。

本文分为 【快速入门】和【API讲解】两部分。
【快速入门】部分包含 Annotorious 的安装、使用、导入导出的讲解。这几点应该是项目中比较核心的流程,给希望快速入门的工友提供一丢丢帮助。
【API讲解】这部分主要讲一下我认为比较常用的功能。注意:是“我认为”。
快速入门
快速入门部分会讲解Annotorious 的安装、使用、导入和导出数据功能。
安装 Annotorious
CDN
<!-- 引入样式 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@recogito/annotorious@2.7.10/dist/annotorious.min.css"><!-- 引入js -->
<script src="https://cdn.jsdelivr.net/npm/@recogito/annotorious@2.7.10/dist/annotorious.min.js"></script>
你可以把这两份文件下载到自己的项目里再引入。
NPM
用以下命令安装 Annotorious
npm install @recogito/annotorious
然后在项目中引入
import { Annotorious } from '@recogito/annotorious'
import '@recogito/annotorious/dist/annotorious.min.css'
使用 annotorious
把 Annotorious 安装到项目后就可以使用了。
Annotorious 的用法很简单,只需做以下2步:
- 在html部分插入图片
- 初始化
Annotorious,并绑定图片元素(元素的ID或者元素本身)

CDN 和 NPM 在初始化时的用法稍微有点不同。
CDN
<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image' // 元素ID
})
</script>
使用 CDN 的方式引入 Annotorious,在初始化时要 Annotorious.init 这样写。
NPM
<img src="./img.jpg" id="my-image" /><script>
const anno = new Annotorious({image: document.getElementById('my-image') // 元素本身
})
</script>
使用 NPM 的方式引入 Annotorious 在初始化时需要 new Annotorious 这样写。
注意:在 Annotorious 初始化代码最好放在你所使用的框架的页面加载完成后的生命周期函数里!
导出数据 getAnnotations()
如果你需要将图片上的标注保存到服务器,就需要把数据导出。
所用到的方法是 getAnnotations()。

<button οnclick="save()">保存</button>
<img src="./44.jpg" id="img" /><script>let anno = nullonload = function() {anno = Annotorious.init({image: 'img'})}function save() {let res = anno.getAnnotations()console.log(res)}
</script>
导入数据 loadAnnotations(url)
Annotorious 可以通过 loadAnnotations() 方法加载数据。
loadAnnotations(url) 支持传入一个 URL 参数,这个 URL 所指的是数据文件地址。
比如我在本地创建一个 data.json 文件,文件内容是使用前面讲到的 getAnnotations() 方法导出的数据,我的数据内容如下:
[{"@context": "http://www.w3.org/ns/anno.jsonld","type": "Annotation","body": [{"type": "TextualBody","value": "1","purpose": "commenting"}],"target": {"source": "http://127.0.0.1:5500/44.jpg","selector": {"type": "FragmentSelector","conformsTo": "http://www.w3.org/TR/media-frags/","value": "xywh=pixel:100,100,500,300"}},"id": "#cabe2e71-b19f-4499-80c6-235882fd50ba"}
]
在本地测试时,我使用了本地服务器把 data.json 管理起来,在浏览器可以通过 http://127.0.0.1:5500/data.json 访问到该文件。
然后再使用 loadAnnotations(url) 方法把数据渲染出来即可。

<button οnclick="load()">加载</button>
<img src="./44.jpg" id="img" /><script>
let anno = nullonload = function() {anno = Annotorious.init({image: 'img'})
}function load() {anno.loadAnnotations("http://127.0.0.1:5500/data.json")
}
</script>
点击加载按钮后,图片上就会出现一个选框,点击选框可以看到数据已经成功加载出来。
添加数据 addAnnotation()
但在实际项目中,后台不一定会给前端返回一个文件地址,后台可能会直接返回一个json数据。
这时候如果使用 loadAnnotations() 方法加载 json 数据是行不通的,要通过遍历读取数据中心的 data 里的数据,然后调用 addAnnotation() 方法将元素添加到页面。
我使用 json-server 简单的在本地搭建一个服务器给前端访问对应的资源,前端用 axios 请求资源。
<button οnclick="load()">加载</button>
<img src="./44.jpg" id="img" /><script>
let anno = nullonload = function() {anno = Annotorious.init({image: 'img'})
}function load() {axios.get('http://localhost:3000/anno').then(res => {res.data.data.forEach(item => {anno.addAnnotation(item)})})
}
</script>
很久之前写过一篇 《『前端必备』本地数据接口 —— json-server 从入门到膨胀》 文章介绍 json-server 的基础用法,有兴趣的工友可以去瞧瞧。
API讲解
这部分主要讲一些我关注到的功能,如果想全面了解 Annotorious 可以查看文档。
汉化 locale
Annotorious 是根据浏览器的设置来确定使用哪种语言。

如果需要修改 Annotorious 使用的语言,可以在初始化时配置一下 locale 字段。
比如配置简体中文可以用 zh-CN,配置繁体中文可以用 zh-TW。

<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',locale: 'zh-CN' // 修改语言
})
</script>
自定义提示文本 messages
如果想自定义按钮或者输入框的提示文本可以配置 messages 。
<img src="./img.jpg" id="my-image" /><script>// 创建一个包含自定义消息的对象
var customMessages = {"Add a comment...": "评论评论","Add a reply...": "回复两句","Add tag...": "这是标签啊","Cancel": "取消","Close": "关闭","Edit": "编辑~","Delete": "删除❌","Ok": "确定"
}let anno = Annotorious.init({image: 'my-image',messages: customMessages // 自定义消息内容
})
</script>
如果同时配置了 locale 和 messages ,会优先使用 message 的值。
空注释 allowEmpty
默认情况下,如果框选后没输入标签或者评论就按确定是不会保存选框的。

如果想保存空选框,可以将 allowEmpty 设置为 true 。

<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',allowEmpty: true // 允许空注释
})
</script>
框选辅助线 crosshair
有些鼠标指针可能并不是那么标准,会影响你框选的准确性。
如果需要非常准确去框选,可以开启辅助线功能,只需将 crosshair 设置为 true 即可。

<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',crosshair: true // 开启辅助线
})
</script>
只读 readOnly
如果不打算提供框选、添加和删除信息的操作给用户,可以将 readOnly 设置为 true 。
<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',readOnly: true // 只读模式
})
</script>
禁止编辑 disableEditor
如果只需要画框框,不需要写注释,可以将 disableEditor 和 allowEmpty 同时设置为 true。

<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',allowEmpty: true, // 允许空注释disableEditor: true // 禁用编辑
})
</script>
为什么要同时将 allowEmpty 设为 true ?
因为如果你不允许注释为空的话,当你点击空白处时选框就会消失。
禁止选中选框 disableSelect
将 disableSelect 设置为 true 后,画布上的选框就无法再次选中了。
<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',disableSelect: true // 禁止选中选框
})
</script>
虽然还没想到有什么引用场景,但还是打算记录一下。
手柄半径 handleRadius
箭头所指的就是手柄。

手柄的默认半径是6。如果需要修改手柄半径可以设置 handleRadius 属性。

<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',handleRadius: 20 // 设置手柄半径
})
</script>
自定义选框样式
Annotorious 的选框和编辑器都是可以使用 css 设置样式的。
选框部分使用了 SVG ,编辑器部分直接用了 HTML 元素。
对 SVG 不了解的工友可以阅读 《SVG专栏》。
回到 Annotorious ,官方也有给出一个自定义样式的案例 《Customizing Visual Appearance》。

<style>/* 选框 *//* 隐藏外部形状-对于这种风格,我们只需要一个 */svg.a9s-annotationlayer .a9s-selection .a9s-outer, svg.a9s-annotationlayer .a9s-annotation .a9s-outer {display:none;}svg.a9s-annotationlayer .a9s-handle .a9s-handle-outer {display:none;}/* 虚线边框 */svg.a9s-annotationlayer .a9s-selection .a9s-inner,svg.a9s-annotationlayer .a9s-annotation .a9s-inner {stroke-width:4;stroke:white;stroke-dasharray:5;}/* 选中时的填充色 */svg.a9s-annotationlayer .a9s-annotation.editable:hover .a9s-inner {fill:transparent;}/* 手柄颜色 */svg.a9s-annotationlayer .a9s-handle .a9s-handle-inner {fill:white;stroke:white;}/* 选中选框时,遮罩层颜色 */svg.a9s-annotationlayer .a9s-selection-mask {fill:rgba(0, 0, 0, 0.6);}/* 编辑器 *//* 容器 */.r6o-editor .r6o-editor-inner {box-sizing: border-box;padding: 10px;border-radius: 6px;background: #F4F2DE;}/* 箭头 */.r6o-editor .r6o-arrow:after {background-color: #F4F2DE;}/* 编辑器 */.r6o-widget.comment.editable,.r6o-widget.r6o-tag {background-color: #EEE3CB;}.r6o-widget.comment {background-color: #D7C0AE;}.r6o-editor .r6o-editor-inner .r6o-widget {border-bottom-color: #7C9D96;}.r6o-editor .r6o-editor-inner .r6o-widget.r6o-tag {border-bottom: none;}/* 按钮 */.r6o-editor .r6o-btn {border-radius: 100px;background-color: #7C9D96;border-color: #7C9D96;color: #fff;}/* 线框按钮 */.r6o-editor .r6o-btn.outline {color: #7C9D96;background-color: transparent;}
</style><img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',locale: 'zh-CN' // 修改语言
})
</script>
上面这份代码选框的样式是从 Annotorious 官网教程搬过来的。
编辑器的样式我随便配了一下,工友们也可以打开浏览器控制台看 Elements 面板的 HTML 代码,根据结构去修改样式即可。
筛选功能
输入时需要快速添加预选项时,可以这样配置:

<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image',widgets: ['COMMENT',{ widget: 'TAG', vocabulary: [ '雷猴', '鲨鱼辣椒', '蝎子莱莱'] }]
})
</script>
多边形选框
使用 setDrawingTool(toolName) 方法可以设置不同的绘制工具。
如果需要讲选框设置成多边形,可以传入 'polygon'。

<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image'
})anno.setDrawingTool("polygon")
</script>
想要知道当前有哪些绘图工具,可以使用 anno.listDrawingTools() 方法查看

<img src="./img.jpg" id="my-image" /><script>
let anno = Annotorious.init({image: 'my-image'
})const toolNames = anno.listDrawingTools()
console.log(toolNames)
</script>
其他
除了上面介绍到的 API 外,Annotorious 还有很多玩法的,比如删除指定注释、清空所有注释等。
详情请看 《annotorious API文档》
插件
Annotorious 也有一些好玩的插件,有兴趣的可以看看 《Annotorious 插件推荐》。
推荐阅读
👍《提升日期处理效率:day.js 实战经验分享》
👍《OpenLayers.js 入门教程:打造互动地图的入门指南》
👍《物理世界的互动之旅:Matter.js入门指南》
👍《p5.js 光速入门》
👍《眨个眼就学会了Pixi.js》
👍《Fabric.js 从入门到膨胀》
点赞 + 关注 + 收藏 = 学会了 代码仓库
相关文章:
Annotorious入门教程:图片注释工具
本文简介 最近有工友问我前端怎么给图片做标注。使用 Fabric.js 或者 Konva.js 等库确实可以实现,但我又好奇有没有专门做图片标注的工具呢? 在网上搜了一下发现 Annotorious 可以实现这个功能。Annotorious 提供了图片注释和标注功能,而且…...
一台服务器是否能够安装多个SSL证书?
在今天的互联网世界中,网络安全是至关重要的,而SSL证书是为了保护网络通信安全而设计的加密协议。然而,对于一台服务器是否能够安装多个SSL证书这个问题,仍然存在一些疑问。本文将探讨这个问题,并提供一些相关的解析和…...
如何使用UDP打洞进行内网穿透
内网穿透是一种将局域网中的设备暴露到互联网上的技术,UDP打洞是内网穿透的一种方法。它允许您通过家庭网络中的NAT(网络地址转换)设备访问位于不同网络的设备,例如家庭服务器或物联网设备。本文将指导您如何使用UDP打洞实现内网穿…...
如何滴水不漏的学完C语言?
如何滴水不漏的学完C语言? 学习C语言需要掌握的知识点确实非常广泛。如果你觉得学校教学中所涉及的内容有所欠缺,可以有很多其他方式进行补充学习。最近很多小伙伴找我,说想要一些C语言资料,然后我根据自己从业十年经验ÿ…...
数据库深入浅出,数据库介绍,SQL介绍,DDL、DML、DQL、TCL介绍
一、基础知识: 1.数据库基础知识 数据(Data):文本信息(字母、数字、符号等)、音频、视频、图片等; 数据库(DataBase):存储数据的仓库,本质文件,以文件的形式将数据保存到电脑磁盘中 数据库管理系统(DBMS)&…...
拓世大模型 | 立足行业所需,发力终端,缔造智能无限可能
蒸汽机的发明为人类工业革命揭开序幕,引领了近现代产业变革。众所周知,而今AI技术的革命性突破,站在了时代舞台的中心,特别是大模型的崛起,无疑是第四次产业革命的焦点,它的地位可与当年的“蒸汽机”相提并…...
NEFU数字图像处理(3)图像分割
一、图像分割的基本概念 1.1专有名词 前景和背景 在图像分割中,我们通常需要将图像分为前景和背景两个部分。前景是指图像中我们感兴趣、要分割出来的部分,背景是指和前景不相关的部分。例如,对于一张人物照片,人物就是前景&…...
图论问题建模和floodfill算法
目录 引入:leetcode695.岛屿的最大面积 分析与转换 一维二维转换 四联通 完整代码解答: 1)显示的创建图解决问题的代码 2)不显示的创建图解决此问题的代码 floodfill算法 定义 引入:leetcode695.岛屿的最大面…...
MySQL - 库的操作
目录 1.库的操作1.1创建数据库1.2创建数据库案例 2.字符集和校验规则3.操纵数据库4.备份和恢复5.查看连接情况 1.库的操作 1.1创建数据库 语法: CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specifica…...
多次kerberos认证服务超时
调整 /var/kerberos/krb5kdc/kdc.conf 文件,有则修改,无则添加 [kdcdefaults] kdc_tcp_listen_backlog 7调整 /etc/krb5.conf [dbmodules] disable_last_success true调整 /etc/sysconfig/krb5kdc KRB5KDC_ARGS‘-w 48’ #增大kdc的进程数量生效上述配…...
Vuex源码-各原理简单总结
1,简单总结 Vuex就是一个构造函数,他拥有install方法和Store类这两个属性。在vue初始化调用new Vue的时候,将store作为参数传入,然后调用Vue.use()实际是调用install方法将store这个实例挂载到全局,从而可以保证全局只…...
vcpkg 使用 cmake 编译C/C++工程代码时指定使用静态库链接编译
参考文献: CMake 项目中的 vcpkg | Microsoft Learn c - Using static Boost libraries with vcpkg and CMake - Stack Overflow Vcpkg updates: Static linking is now available - C Team Blog (microsoft.com) microsoft/vcpkg: C Library Manager for Windo…...
FlinkCDC系列:数据同步对部分字段的处理,只更新部分字段
在flinkCDC源数据配置中,只对表中的部分字段关注,通过监控部分字段进行数据更新或者不更新,对数据进行同步。主要通过以下两个参数: column.exclude.list 默认: 空字符串 一个可选的、以逗号分隔的正则表达式列表,与…...
Linux 包操作 (rpm)
目录 1. rpm 包1.1. 提取和安装 rpm 包1.2. 查看一个 rpm 包中的文件安装到那里去 1. rpm 包 rpm --version1.1. 提取和安装 rpm 包 使用以下命令来解压 rpm 包: rpm2cpio package.rpm | cpio -idmv其中, package.rpm 是你要解压的 rpm 包的文件名。这个命令将会将 rpm 包解…...
Docker中OceanBase挂载过后,删除再启动无限重启的解决办法
ob-compose.yml文件如下: version: 3 services:oceanbase1:image: oceanbase/oceanbase-ce:latestcontainer_name: oceanbase1hostname: oceanbase1ports:- 2881:2881restart: alwaysprivileged: truevolumes:#- //d/obdata/ob:/root/ob#- //d/obdata/obd:/root/.o…...
react中的forwardRef 和memo的区别?
文章目录 前言介绍forwardRefmemo适用场景优点缺点后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:前端面试 🐱👓博主在前端领域还有很多知识和技术需要掌握,正在不断努力填补技术短板。(如果出现错…...
偶数矩阵判断【C语言作业】
题目 若一个布尔矩阵所有行和所有列的和都是偶数,则称为偶数矩阵。请编写一个程序,判断一个布尔矩阵是否是偶数矩阵。 要求: (1)输入:首先输入一个正整数n(n<100),代表该矩阵的大小,接下来是n行n列的矩…...
stable-diffusion 电商领域prompt测评集合
和GhostReivew一个思路,还是从比较好的图片或者是civitai上找一些热门的prompt,从小红书上也找到了不少的prompt,lexica.art上也有不少,主要是为了电商场景的一些测评: 小红书、civitai、Lexica、Liblib.ai、 depth o…...
协方差矩阵
将变量两两之间的协方差排成矩阵的形式,就是协方差矩阵。 用个例子来说明下,帮助理解。 下面这组数据有三个变量:身高x、体重y、年龄z,每个变量都有5个取值: 身高x(cm)体重y(kg&a…...
0基础学习VR全景平台篇第117篇:利用插件地拍补地 - PS教程
上课!全体起立~ 大家好,欢迎观看蛙色官方系列全景摄影课程! 嗨,大家好,今天我们来介绍【PS利用插件地拍补地】。 之前已经教给大家补地插件的安装方法,今天我们教给大家如何使用这个插件进行补地。 首…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
