在一个图片中标记一个或者多个截图区域
在截图区域中标记一个或者多个子区域
如下图:
//main.ts
import 'img-mark/dist/style.css'
<template>
<div class="page-test-img-mark">
<div style="width: 50vw; height: 100vh; background: #ccc; box-sizing: border-box; flex-shrink: 0">
<ImgMark
ref="imgMarkRef"
:src="src"
v-model:mode="mode"
v-model:tagList="tagList"
v-model:cropList="cropList"
:enableDrawCropOutOfImg="false"
:enableDrawTagOutOfCrop="false"
:enableDrawTagOutOfImg="false"
></ImgMark>
</div>
<div class="info-panel">
<el-input v-model="src"></el-input>
<el-button type="primary" size="small" style="margin-top: 40px" @click="getGroupInfo()">getGroupInfo</el-button>
<el-alert
v-for="item in tagList"
@close="removeTag([item])"
@mouseenter="setHoverItem(item)"
@mouseleave="removeHoverItem(item)"
style="margin-top: 20px"
:key="uid(6)"
:title="JSON.stringify(item)"
type="warning"
>
</el-alert>
</div>
</div>
</template>
<script setup lang="ts">
import { ImgMark, Mode, BoundingBox } from 'img-mark'
import { uid } from 'uid'
let src = $ref('https://forza.ismcdn.jp/mwimgs/8/e/1774n/img_8e8307dc5355e41385fd3568ef95f233218536.jpg')
let mode = $ref<Mode>('crop')
let cropList = $ref<BoundingBox[]>([
{
startX: 0,
startY: 0,
endX: 1774,
endY: 100,
},
{
startX: 200,
startY: 200,
endX: 1000,
endY: 500,
},
])
let tagList = $ref<BoundingBox[]>([
{
startX: 50,
startY: 0,
endX: 100,
endY: 50,
isShow: true,
labelText: 'customTagLabelText',
},
{
startX: 0,
startY: 0,
endX: 1774,
endY: 100,
isShow: true,
},
])
let imgMarkRef = $ref<InstanceType<typeof ImgMark>>()
function removeTag(data?: BoundingBox[]) {
if (data) {
imgMarkRef.removeTagItems(data)
}
}
function cropChange() {
removeTag()
}
function setHoverItem(item: BoundingBox) {
item.showOutLine = true
}
function removeHoverItem(item: BoundingBox) {
item.showOutLine = false
}
function getGroupInfo() {
let groupInfo = imgMarkRef.getTagListGroupByCropIndex()
console.log(groupInfo)
}
</script>
<style scoped lang="scss">
.page-test-img-mark {
display: flex;
justify-content: space-between;
.info-panel {
width: 50vw;
box-sizing: border-box;
padding: 20px;
}
}
</style>
type ShortCutItem = 'ctrl+b' | 'space'
type Event = {
onClick?: (e: unknown, item: BoundingBox) => void //tag click事件
onDoubleClick?: (e: unknown, item: BoundingBox) => void //tag double click事件
}
type WH = {
width: number
height: number
}
type Hooks = {
shiftMode(): void //切换模式
shiftDrawSwitch(onOrOff: 'on' | 'off'): void //切换是否开始画的开关
init(): void //初始化组件
resize(): void //resize后重新初始化组件
}
type BoundingBox = {
startX: number
endX: number
startY: number
endY: number
isShow?: boolean //tag是否显示
showOutLine?: boolean //tag是否边框是否高亮
labelText?: string //tag的标签文字
tagConfig?: TagConfig //单独定义某个tagConfig
cropConfig?: CropConfig //单独定义某个cropConfig
} & Event
type Dot = {
x: number
y: number
raduis: number
__scale?: number
__index?: number
__isHover?: boolean
dotConfig?: DotConfig
} & Event
type CropConfig = {
lineDash?: number[]
strokeStyle?: string
lineWidth?: number
customDraw?: CustomDraw
}
export type DaubConfig = {
strokeStyle?: string
lineWidth?: number
}
type LayerConfig = {
fillStyle?: string
}
export type CustomDraw = (
ctx: CanvasRenderingContext2D,
info: {
target: BoundingBox | undefined
positions: Rect
}
) => void
type TagConfig = {
fontSize?: number //px单位,默认20
showText?: boolean //是否展示tag index
fillStyle?: string
textFillStyle?: string
hoverStrokeStyle?: string
hoverLineWidth?: number
hoverLineDash?: number[]
highlightStrokeStyle?: string
highlightLineWidth?: number
highlightLineDash?: number[]
customDraw?: CustomDraw
}
type Point = {
x: number
y: number
}
type DaubPoint = Point & {
lineWidth?: number
strokeStyle?: string
}
type DotConfig = {
lineDash?: number[]
strokeStyle?: string
fillStyle?: string
hoverFillStyle?: string
lineWidth?: number
radius?: number
}
type ResizeEmitType = {
index: number
box: BoundingBox
}
type TagListGroupByCropIndex = {
[key: number]: BoundingBox[]
undefined: BoundingBox[]
}
type MouseOverInfoEmitType = {
canvas: Point | null
img: Point | null
}
type OnLoadImageEmitType = {
status: 'loading' | 'success' | 'error'
msg?: string
}
type TagListChangeType = 'add' | 'delete' | 'statusChange'
type TagListChangeEmitRetunType = {
type: TagListChangeType
list: BoundingBox[]
parentCrop?: BoundingBox
}
type CropListChangeType = 'add' | 'delete' | 'resize'
type CropListChangeEmitType = {
type: CropListChangeType
list: BoundingBox[]
}
type BoundingBox2Rect = (boundingBoxList: BoundingBox[]) => Rect[]
type CustomDrawTopCtx = (ctx: CanvasRenderingContext2D, boundingBox2Rect: BoundingBox2Rect) => void
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
src | 图片地址, 必选参数 | string | —— | —— |
mode/v-model:mode | 模式 | string | crop/tag/daub/dot | crop |
mobileOperation/v-model:mobileOperation | 移动端单指在画布滑动是移动还是画 rect | string | move/draw | move |
cropList/v-model:cropList | 裁切区域集合,相对于 img 左上角开始定位 |
BoundingBox[] | —— | [] |
tagList/v-model:tagList | tag 区域集合,相对于 img 左上角开始定位 |
BoundingBox[] | —— | [] |
dotList/v-model:dotList | dot 集合,相对于 img 左上角开始定位 |
Dot[] | —— | [] |
daubStack/v-model:daubStack | 涂抹区域店集合,相对于 img 左上角开始定位 |
DaubPoint[][] | —— | [] |
precision | BoundingBox 精度 | number | —— | 0 |
drawingText | 画 tag 时候展示的文字 |
number | —— | 0 |
initScale | 是否自动缩放图片适应到画布大小 | boolean | true/false | true |
enableScale | 是否允许缩放画布 | boolean | true/false | true |
enableMove | 是否允许移动画布 | boolean | true/false | true |
enableDrawCrop | 是否允许在画布上画 crop |
boolean | true/false | true |
enableDrawTag | 是否允许在画布上画 tag |
boolean | true/false | true |
enableInteractiveTagChangeStatus | 是否允许交互改变 tag 状态 |
boolean | true/false | true |
enableInteractiveCropDelete | 是否允许交互删除 crop |
boolean | true/false | true |
enableDrawCropOutOfImg | 是否允许 crop 画到图片外 |
boolean | true/false | true |
enableDrawTagOutOfCrop | 是否允许 tag 画到 crop 外 |
boolean | true/false | true |
enableDrawTagOutOfImg | 是否允许 tag 画到图片外 |
boolean | true/false | true |
enableDotCenterOutOfImg | 是否允许 dot 画到图片外 |
boolean | true/false | false |
splitClickAndDoubleClickEvent | 是否分离单击和双击事件 | boolean | true/false | false |
disableDefaultShortcuts | 禁用默认快捷键 | ShortCutItem[] | —— | [] |
enableCropResize | 是否允许 crop 改变大小 |
boolean | true/false | true |
enableTagResize | 是否允许 tag 改变大小 |
boolean | true/false | false |
enableCropCross | 是否允许 crop 和其他 crop 相交,不允许后,如果相交,新画的 crop 会不添加,如果是 resize 操作相交以后,按照下方 handleResizeCropCross 属性去处理 |
boolean | true/false | true |
handleResizeCropCross | 当 enableCropCross 属性为 false,resize 操作相交后该如何处理进行 resize 操作的 crop |
string | delete/reset | reset |
isShowTip | 是否显示底部提示区域 | boolean | true/false | false |
isCropSingle | 是否单个 crop , 在添加 crop 的时候自动删除旧的 crop |
boolean | true/false | false |
isImgCrop | 是否在 cropList 为空时默认以图片大小为裁切区域 |
boolean | true/false | false |
layerConfig | 浮层样式 | LayerConfig | —— | —— |
cropConfig | crop 样式 |
CropConfig | —— | —— |
tagConfig | tag 样式 |
TagConfig | —— | —— |
daubConfig | daub 样式 |
DaubConfig | —— | —— |
dotConfig | dot 样式 |
DotConfig | —— | —— |
customDrawTopCtx | 自定义绘制 | CustomDrawTopCtx | —— | —— |
事件名 | 说明 | 参数 |
---|---|---|
tagListChange | 当添加或者删除或者改变组件状态 tag 项触发,parentCrop 只在 type 为 add 时候返回 |
data:TagListChangeEmitRetunType |
cropListChange | 当添加或者删除或者改变 crop 大小触发 |
{type:'add'/'delete'/'resize', list:BoundingBox[] } |
resizeStart | crop 开始 resize 触发 |
data:ResizeEmitType |
resizeEnd | crop 结束 resize 触发 |
data:ResizeEmitType |
delCrop | 删除 crop 触发 |
list:BoundingBox[] |
drawCropStart | 添加 crop 之前触发 |
—— |
drawTagStart | 添加 tag 之前触发 |
—— |
mouseOverInfo | 鼠标在组件上移动或者移除时候触发 | info:MouseOverInfoEmitType |
onLoadImage | 图片加载状态事件 | data:OnLoadImageEmitType |
对象 | 类型 | 说明 |
---|---|---|
hooks | Hooks | 调用组件抽象方法 |
scaleInfo | { scale:number zoomScale:number } |
scale 为初始缩放到容器时候的缩放值 zoomScale 为继续缩放的值 |
info | { imgWH:WH } |
一些常见的信息 |
方法 | 说明 | 参数 | 返回类型 |
---|---|---|---|
removeTagItems | 移除 tag 项 |
list:BoundingBox[] | void |
render | 重新渲染 | —— | void |
getTagListGroupByCropIndex | 获取 tagList 并按照 cropIndex 分组 |
type: 'startPoint'/'allIn' = 'startPoint' | TagListGroupByCropIndex |
getBase64ImageData | 获取原图大小绘制完各种元素后的 base64 图片 url | crossOrigin?: boolean | Promise<string> |
scrollIntoView | 讲某个区域滚动到 0,0 点 | box: BoundingBox | void |
插槽名 | 说明 |
---|---|
tip | 右下角提示自定义内容 |
方法 | 说明 | 参数 | 返回类型 |
---|---|---|---|
transformTagListBoxRelativeTo | 转换 tag list 基于什么去定位 |
type: 'img'/'crop', cropInfo: BoundingBox, tagList: BoundingBox[] | BoundingBox[] |
transformTagBoxRelativeTo | 转换单个 tag 基于什么去定位 |
type: 'img'/'crop', cropInfo: BoundingBox, tag: BoundingBox | BoundingBox |
boxIsAllInOtherBox | box 是否完全包含在另一个 box | box: BoundingBox, otherBox: BoundingBox | boolean |
按住鼠标左键拖动
按下空格 + 按住鼠标左键拖动
Ctrl + B
鼠标滚轮