如下图所示,在图片上做标记,如圆形、矩形等。
该demo实现画布在页面布局中缩放后居中显示,可拖拽、缩放、做标记说明。
项目下载地址:https://gitlab.com/zhangcw66/draw_mark
如粘贴以下代码,请安装zrender
<template>
<div class="draw-mark">
<div id="canvas" style="height:710px;background-color:#F5F5F5"></div>
</div>
</template>
<script>
import zrender from 'zrender'
export default {
name: 'DrawMark',
data() {
return {
imgSrc: require('@/assets/timg.jpg'),
markInfo: [[440, 260],[590, 360],[770, 370]],
isShowPoint: true,
zr: {},
// zrender group实例
group: {},
imgWidth: 0, // 大图宽度
imgHeight: 0, // 大图高度
whRatio: 0, // 大图比例
frameScale: 0, // 缩放比
subSet: [], // 存放已经绘画完成的矩形框信息
circle: {},
scale: 1,
scaleMax: 15, // 最大比例
scaleMin: 0.5 // 最小比例
}
},
mounted() {
this.initCanvas()
},
methods: {
// 初始化画布绘制图片
initCanvas() {
const vm = this
// 获取放置画布的元素
const container = document.getElementById('canvas')
// 初始化zr实例 zrender容器
vm.zr = zrender.init(container)
vm.group = new zrender.Group({
slient: true // 组内子孙元素是否响应鼠标事件
})
// 创建图片对象
const imgs = new Image()
// 图片的src等于大图的地址
imgs.crossOrigin = 'Anonymous'
imgs.setAttribute('crossOrigin', 'Anonymous')
imgs.src = this.imgSrc
imgs.onerror = _ => {
this.$message.error('图片加载失败!')
return false
}
// 图片加载成功后
imgs.onload = () => {
const canvasWidth = container.clientWidth
const canvasHeight = container.clientHeight
// 画布宽高比
const canvasRatio = canvasWidth / canvasHeight
// 图片的宽高比
vm.whRatio = imgs.width / imgs.height
const originalHeight = imgs.height
// 图片相对于画布的尺寸进行调整
// 如果图片的宽高比大于画布的宽高比
if (vm.whRatio > canvasRatio) {
imgs.width = canvasWidth
imgs.height = imgs.width / vm.whRatio
} else {
imgs.height = canvasHeight
imgs.width = imgs.height * vm.whRatio
}
// 对图片的宽高进行调整
vm.imgWidth = imgs.width
vm.imgHeight = imgs.height
vm.frameScale = originalHeight / vm.imgHeight
// 绘制图片
const myImg = new zrender.Image({
style: {
image: this.imgSrc,
x: 0,
y: 0,
width: vm.imgWidth,
height: vm.imgHeight
},
z: 1,
onmouseover: _ => {
document.body.style.overflowY = 'hidden'
},
onmousewheel: _ => {
document.body.style.overflowY = 'hidden'
},
onmouseout: item => {
document.body.style.overflowY = ''
}
})
// 将图片绘制至子节点中
vm.group.add(myImg)
// 调整图片在canvas中的位置
if (vm.zr.getWidth() > vm.imgWidth) {
vm.group.position[0] = (vm.zr.getWidth() - vm.imgWidth) / 2
}
if (vm.zr.getHeight() > vm.imgHeight) {
vm.group.position[1] = (vm.zr.getHeight() - vm.imgHeight) / 2
}
// 将子节点添加至画布中
vm.zr.add(vm.group)
// 绘制mark
this.markInfo.forEach((item, index) => {
const opt = {
stroke: '#f00',
pointsSet: this.AssemblyData(item),
id: index
}
vm.drawMark(opt)
})
// 调用鼠标的滚动事件
vm.handleZoom()
// 调用鼠标的拖拽事件
vm.handleDrop()
}
},
// 坐标框的转换
AssemblyData(item) {
let newArr = item
const arr = newArr.map(item => {
return Math.round(item /= this.frameScale)
})
return arr
},
// 画标记
drawMark(opt) {
const vm = this
// vm.subSet = []
vm.circle = new zrender.Circle({
style: {
stroke: opt.stroke || '#f00',
fill: opt.stroke || '#f00',
text: this.isShowPoint ? opt.id : null,
textHeight: 6,
textWidth: 6,
fontWeight: 700,
textPosition: [2, -11],
textLineWidth: 2,
textPadding: 1,
fontSize: 12,
textFill: opt.stroke || '#fff'
},
shape: {
cx: opt.pointsSet[0],
cy: opt.pointsSet[1],
r: 3
},
hoverable: true,
z: 2
})
vm.subSet.push(vm.circle)
vm.group.add(vm.circle)
},
// 滚动缩放
handleZoom() {
const vm = this
// 在画布子节点上监听鼠标滚轮事件 事件对象
vm.group.on('mousewheel', ev => {
// wheeldata 返回值说明:正值向上滚动,负值向下滚动 均为120的倍数 缩小20倍 结果可能是 +e.wheelDelta/20 或者是 -e.wheelData/20
// 缩小20倍数 调整为10
// 监听到鼠标再
const e = (ev || event).wheelDelta / 60
// scale原始的缩放比例是1 每次滚动在这个基础上加上或者减去滚轮缩放后的数据
vm.scale += e
// 调用缩放的函数 将缩放的比例传入
vm.setScale(vm.scale)
})
},
/** * 缩放 */
setScale(scale, item) {
if (scale > this.scaleMax) {
scale = this.scaleMax
} else if (scale < this.scaleMin) {
scale = this.scaleMin
}
this.scale = scale
this.group.attr({
scale: [this.scale, this.scale],
origin: [this.zr.getWidth() / 5, this.zr.getHeight() / 5]
})
},
// 拖拽
handleDrop() {
const vm = this
vm.zr.dragData = {
drag: false,
pos: [0, 0],
group: null,
target: null
}
vm.zr.on('mousedown', e => {
// 画布拖拽的起始位置是 事件对象中的画布的坐标位置
vm.zr.dragData.pos = [e.event.zrX, e.event.zrY]
// 画布的拖拽目标
vm.zr.dragData.target = e.target
if (e.target === undefined) {
// !!!
vm.zr.dragData.drag = false
} else if (e.target.parent && e.target.parent.type === 'group') {
vm.zr.dragData.drag = true
vm.zr.dragData.group = e.target.parent
}
})
// 鼠标抬起事件 关闭拖拽 将拖拽的目标元素设置为空
vm.zr.on('mouseup', e => {
vm.zr.dragData.drag = false
vm.zr.dragData.group = null
})
// 鼠标移出事件 关闭拖拽
vm.zr.on('mouseout', e => {
vm.zr.dragData.drag = false
vm.zr.dragData.group = null
})
// 鼠标移动事件
vm.zr.on('mousemove', e => {
if (vm.zr.dragData.drag !== true) return
const new_pos = [e.event.zrX, e.event.zrY]
if (vm.zr.dragData.group != null) {
var pos = [
new_pos[0] - vm.zr.dragData.pos[0],
new_pos[1] - vm.zr.dragData.pos[1]
]
vm.zr.dragData.group.children().forEach(x => {
x.position = [0, 0]
})
vm.zr.dragData.group.position[0] += pos[0]
vm.zr.dragData.group.position[1] += pos[1]
vm.zr.dragData.group.dirty()
} else {
// eslint-disable-next-line no-redeclare
var pos = [
new_pos[0] - vm.zr.dragData.pos[0],
new_pos[1] - vm.zr.dragData.pos[1]
]
vm.zr.storage.getDisplayList(true, true).forEach(x => {
x.position[0] += pos[0]
x.position[1] += pos[1]
x.dirty()
})
}
vm.zr.dragData.pos = [e.event.zrX, e.event.zrY]
})
}
}
}
</script>
<style scoped>
.draw-mark {
padding: 30px;
}
</style>