Annotorious入门教程:图片注释工具

news2024/11/16 16:20:21

本文简介

最近有工友问我前端怎么给图片做标注。使用 Fabric.js 或者 Konva.js 等库确实可以实现,但我又好奇有没有专门做图片标注的工具呢?

在网上搜了一下发现 Annotorious 可以实现这个功能。Annotorious 提供了图片注释和标注功能,而且用法很简单。

file


本文分为 【快速入门】和【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步:

  1. 在html部分插入图片
  2. 初始化 Annotorious,并绑定图片元素(元素的ID或者元素本身)

file


CDNNPM 在初始化时的用法稍微有点不同。

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()

file

<button οnclick="save()">保存</button>
<img src="./44.jpg" id="img" />

<script>
  let anno = null

  onload = 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) 方法把数据渲染出来即可。

file

<button οnclick="load()">加载</button>
<img src="./44.jpg" id="img" />

<script>
let anno = null

onload = 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 = null

onload = 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 是根据浏览器的设置来确定使用哪种语言。

file


如果需要修改 Annotorious 使用的语言,可以在初始化时配置一下 locale 字段。

比如配置简体中文可以用 zh-CN,配置繁体中文可以用 zh-TW

file

<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>

如果同时配置了 localemessages ,会优先使用 message 的值。


空注释 allowEmpty

默认情况下,如果框选后没输入标签或者评论就按确定是不会保存选框的。

file

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

file

<img src="./img.jpg" id="my-image" />

<script>
let anno = Annotorious.init({
  image: 'my-image',
  allowEmpty: true // 允许空注释
})
</script>

框选辅助线 crosshair

有些鼠标指针可能并不是那么标准,会影响你框选的准确性。

如果需要非常准确去框选,可以开启辅助线功能,只需将 crosshair 设置为 true 即可。

file

<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

如果只需要画框框,不需要写注释,可以将 disableEditorallowEmpty 同时设置为 true

file

<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

箭头所指的就是手柄。

file

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

file

<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》。

file

<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 代码,根据结构去修改样式即可。


筛选功能

输入时需要快速添加预选项时,可以这样配置:

file

<img src="./img.jpg" id="my-image" />

<script>
let anno = Annotorious.init({
  image: 'my-image',
  widgets: [
    'COMMENT',
    { widget: 'TAG', vocabulary: [ '雷猴', '鲨鱼辣椒', '蝎子莱莱'] }
  ]
})
</script>

多边形选框

使用 setDrawingTool(toolName) 方法可以设置不同的绘制工具。

如果需要讲选框设置成多边形,可以传入 'polygon'

file

<img src="./img.jpg" id="my-image" />

<script>
let anno = Annotorious.init({
  image: 'my-image'
})

anno.setDrawingTool("polygon")
</script>

想要知道当前有哪些绘图工具,可以使用 anno.listDrawingTools() 方法查看

file

<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 从入门到膨胀》


点赞 + 关注 + 收藏 = 学会了 代码仓库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1142843.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UVa10976 Fractions Again?!(分数拆分)

1、题目 2、题意 输入正整数 k k k&#xff0c;找到所有正整数 x ≥ y x \ge y x≥y&#xff0c;使得 1 k 1 x 1 y \frac{1}{k} \frac{1}{x} \frac{1}{y} k1​x1​y1​。 3、分析 既然要求找出所有的 x , y x,y x,y&#xff0c;枚举对象自然是 x , y x,y x,y了。可…

​Vue2【双向数据绑定/响应式原理】

目录 初始化 initProps()&#xff1a;父组件传的 props 列表&#xff0c;proxy() 把属性代理到当前实例上 vm._props.xx 变成 vm.xx initData()&#xff1a;判断data和props、methods是否重名&#xff0c;proxy() 把属性代理到当前实例上 this.xx observe()&#xff1a;给…

c++实现dijskstra算法

图 我一开始写成了最小生成树的代码&#xff0c;最小生成树一直在选最小的那条边&#xff0c;对每个节点来说&#xff0c;它到原点的距离不一定是最近的。 代码 #include<iostream> using namespace std; #include<list> #include<vector>class Node { pub…

RocketMQ事务消息 超时重发还是原来的消息吗?

以下面的一个demo例子来分析一下&#xff0c;探索RocketMQ事务消息原理。 public static final String PRODUCER_GROUP "tran-test";public static final String DEFAULT_NAMESRVADDR "127.0.0.1:9876";public static final String TOPIC "Test&qu…

如何理解Quadratic Weighted Kappa?

Motivation 假定我们现在有 N N N个作文样例&#xff0c;以及它们对应的人类评分和GPT评分。评分一共有 C C C个互斥类别&#xff0c;分别是{0,1,2,3}。现在我们要衡量人类评分和GPT评分的一致性。 一个很直观的想法是&#xff0c;画出混淆矩阵&#xff0c;然后将对角线上的值…

Linux Centos7安装后,无法查询到IP地址,无ens0,只有lo和ens33的解决方案

文章目录 前言1 查看network-scripts目录2 创建并配置 ifcfg-ens33 文件3 禁用NetworkManager4 重新启动网络服务总结 前言 在VMware中&#xff0c;安装Linux centos7操作系统后&#xff0c;想查询本机的IP地址&#xff0c;执行ifconfig命令 ifconfig结果如下&#xff1a; 结…

吴恩达《机器学习》1-5:模型描述

一、单变量线性回归 单变量线性回归是监督学习中的一种算法&#xff0c;通常用于解决回归问题。在单变量线性回归中&#xff0c;我们有一个训练数据集&#xff0c;其中包括一组输入特征&#xff08;通常表示为&#x1d465;&#xff09;和相应的输出目标&#xff08;通常表示为…

UVa140 Bandwidth(带宽)

1、题目 2、题意 给出一个 n &#xff08; n ≤ 8 &#xff09; n&#xff08;n≤8&#xff09; n&#xff08;n≤8&#xff09;个结点的图G和一个结点的排列&#xff0c;定义结点 i i i 的带宽 b ( i ) b(i) b(i) 为 i i i 和相邻结点在排列中的最远距离&#xff0c;而所…

Ansible上通过roles简化playbook演示介绍

目录 一.roles介绍 1.作用 2.role的目录结构 3.role和tasks的执行优先级顺序 二.自定义一个httpd的角色 1.完整目录结构展示 2.主要的各个目录配置 &#xff08;1&#xff09;vars目录和templates目录 &#xff08;2&#xff09;tasks目录和handlers目录 &#xff08…

操作系统中套接字和设备独立性软件的关系

网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下&#xff0c;我们只需要考虑如何编写传输软件。操作系统提供了名为“套接字”&#xff0c;套接字是网络传输传输用的软件设备。 这是对软件设备的解释&#xff1a; 在操作系统中&#…

Unity ScrollView最底展示

Unity ScrollView最底展示 问题方案逻辑 问题 比如在做聊天界面的时候我们肯定会使用到ScrollView来进行展示我们的聊天内容&#xff0c;那么这个时候来新消息的时候就需要最底展示&#xff0c;我认为这里有两种方案&#xff1b; 一种是通过算法每一条预制体的高度*一共多少…

轮转数组(Java)

大家好我是苏麟 , 这篇文章是凑数的 ... 轮转数组 描述 : 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 题目 : 牛客 NC110 旋转数组: 这里牛客给出了数组长度我们直接用就可以了 . LeetCode 189.轮转数组 : 189. 轮…

Python---break关键字对for...else结构的影响

for循环中添加else结构 循环可以和else配合使用&#xff0c; else下方缩进的代码指的是当循环正常结束之后要执行的代码。 强调&#xff1a; 循环 正常结束&#xff0c;else之后要执行的代码。 非正常结束&#xff0c;其else中的代码是不会执行的。&#xff08;如遇到br…

类和对象(1):类,对象,this指针

面向过程和面向对象初步认识&#xff1a; C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出问题求解的步骤&#xff0c;用函数调用逐步解决。C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完成。…

【.NET Core】创建一个在后台运行的控制台程序(ConsoleApp)

文章目录 1. 添加Nuget包2. 修改Program.cs3. 添加TestService 借助.NET的通用主机&#xff08;IHostBuilder&#xff09;可以轻易创建一个可以执行后台任务的程序 1. 添加Nuget包 Microsoft.Extensions.Hosting 2. 修改Program.cs 通过Host获取IHostService&#xff0c;然…

SSD: Single Shot MultiBox Detector(2016.11)

文章目录 AbstractIntroduction此前本文贡献总结如下: The Single Shot Detector (SSD)SSD ModelMulti-scale feature maps for detectionConvolutional predictors for detectionDefault boxes and aspect ratiosTrainingMatching strategyTraining objectiveChoosing scales …

python---for循环结构中的else结构(是同级关系)

为什么需要在for循环中添加else结构 循环可以和else配合使用&#xff0c; else下方缩进的代码指的是当循环正常结束之后要执行的代码。 强调&#xff1a; 循环 正常结束&#xff0c;else之后要执行的代码。 非正常结束&#xff0c;其else中的代码是不会执行的。&#xf…

GienTech动态|入选软件和信息技术服务名牌企业;荣获城市数字化转型优秀案例;参加第四届深圳国际人工智能展

中电金信入选“2023第二届软件和信息技术服务名牌企业” 近日&#xff0c;中国电子信息行业联合会发布了“2023第二届软件和信息技术服务名牌企业”名单&#xff0c;中电金信入选。此名单发布原则&#xff0c;重点突出技术创新力。突出市场影响力&#xff0c;品牌建设良好&…

Leetcode刷题笔记--Hot81--90

1--打家劫舍III 主要思路&#xff1a; 基于从下到上的 dp 回溯法&#xff0c;每一个节点只有两种状态&#xff0c;dp[0]表示被打劫&#xff0c;dp[1]表示不被打劫&#xff1b; 当前节点被打劫时&#xff0c;其孩子只能都不被打劫&#xff1b;dp[0] left[1] right[1] cur->…

redis集群理论和搭建

目录 环境 一&#xff0c;安装和部署redis 1&#xff0c;安装 2&#xff0c;部署 ​编辑 3&#xff0c;允许非本机连接redis 二、主从模式 主从模式搭建&#xff1a; 三&#xff0c;哨兵模式 哨兵模式搭建 四&#xff0c;集群模式 架构细节: 心跳机制 集群模式搭建&#xff1a…