1. 基于Vue3+LeaderLine实现画线测距及线条自由调整
先看下效果:我们画线后可以根据比例关系自动计算距离,并且线条不对可以自由调整
<template>
<div id="image-detail">
<el-image :src="myImageUrl" style="height: auto; width: 800px;" fit="scale-down" ref="canvasRef" @mousedown="startDrawing" @mousemove="drawLine" @mouseup="stopDrawing">
<template #error>
<div>
<el-icon></el-icon>
</div>
</template>
</el-image>
</div>
</template>
<script>
import LeaderLine from 'leader-line'
import $ from 'jquery'
export default {
...
methods: {
startDrawing(event) {
console.log("当前坐标点:" + event.offsetX + " " + event.offsetY)
if (this.checkIfAiPoint(event)) return
},
drawLine(event) {
const self = this
if (self.ai.isAiMoving) {
self.aiMoving(event)
return false
}
},
stopDrawing(event) {
if (this.checkIfAiPoint(event)) return
},
// AI结果画图
markPointPlus(offsetX, offsetY, tag, nameIndex) {
const self = this
if (document.getElementById("image-detail") === null) {
return
}
// 点击多个点,生成多个值
let div = document.createElement("div");
div.className = "marker";
div.id = "marker" + nameIndex + 'ai' + tag;
div.style.width = 0
div.style.height = 0
// 红点颜色
div.style.backgroundColor = "red";
div.style.zIndex = 999;
div.style.left = offsetX + "px";
let imageHeight = $('#image-detail').height()
div.style.top = (offsetY - imageHeight) + "px";
div.style.position = "relative";
// 边框半径百分比
div.style.borderRadius = "50%";
// 该位置监听的偏移量有问题,需计算
// div.onclick = function (event) {
// // 计算出相对图片的偏移量
// console.log($('#image-detail .el-image__inner').left)
// console.log($('#image-detail .el-image__inner').top)
// const offsetX = event.clientX - $('#image-detail .el-image__inner').left
// const offsetY = event.clientY - $('#image-detail .el-image__inner').top
// if (self.ai.isAiMoving) {
// // 停止移动
// self.stopAiMoving(offsetX, offsetY, nameIndex, tag)
// } else {
// // 开始移动
// self.startAiMoving(offsetX, offsetY, nameIndex, tag)
// }
// },
document.getElementById("image-detail").appendChild(div);
self.aiLineStartMiddleInfo[nameIndex + tag] = {'offsetX': offsetX, 'offsetY': offsetY}
},
startAiMoving(offsetX, offsetY, nameIndex, tag) {
const self = this
// 说明鼠标刚刚从原来位置up,不需要开始
if (offsetX === self.lastAiX && offsetY === self.lastAiY) {
return false
}
self.ai.isAiMoving = true
self.ai.nameIndex = nameIndex
self.ai.tag = tag
self.lastAiX = offsetX
self.lastAiY = offsetY
},
aiMoving(event) {
const self = this
const nameIndex = self.ai.nameIndex
let imageHeight = $('#image-detail').height()
if (!this.ai.isAiMoving) return;
// const len = Object.keys(self.aiDrawInfo).length
// 由于之前在开始结束位置新增了点的宽高均为4导致
// 解决开始端移动点上移问题,原因未知, 由多点规律推断而来
// if (self.ai.tag === 'start') {
// $('#marker' + nameIndex + 'ai' + self.ai.tag).css({
// 'left': event.offsetX,
// 'top': (event.offsetY + 4 * (len - nameIndex) * 2 - imageHeight)
// })
// } else {
// $('#marker' + nameIndex + 'ai' + self.ai.tag).css({
// 'left': event.offsetX,
// 'top': (event.offsetY + 4 * ((len - nameIndex) * 2 - 2) - imageHeight)
// })
// }
$('#marker' + nameIndex + 'ai' + self.ai.tag).css({
'left': event.offsetX,
'top': (event.offsetY - imageHeight)
})
const lineRef = self.aiDrawInfo['marker' + nameIndex + 'ai']
lineRef.position()
},
stopAiMoving(currOffsetX, currOffsetY, nameIndex, tag) {
const self = this
nameIndex = Number(nameIndex)
// 说明鼠标刚刚从原来位置up,不需要结束
if (currOffsetX === self.lastAiX && currOffsetY === self.lastAiY) {
return false
}
self.ai.isAiMoving = false
self.ai.nameIndex = 0
self.ai.tag = ''
let offsetX
let offsetY
if (tag === 'start') {
offsetX = self.aiLineStartMiddleInfo[nameIndex + 'middle']['offsetX']
offsetY = self.aiLineStartMiddleInfo[nameIndex + 'middle']['offsetY']
} else {
offsetX = self.aiLineStartMiddleInfo[nameIndex + 'start']['offsetX']
offsetY = self.aiLineStartMiddleInfo[nameIndex + 'start']['offsetY']
}
const currDistance = Math.sqrt((currOffsetX - offsetX) ** 2 + (currOffsetY - offsetY) ** 2)
let currRealDistance = (self.rulerRealDistance * 100 / self.rulerDistance * currDistance / 100).toFixed(1)
// 判断是否为标尺移动
if (nameIndex === 0) {
self.rulerDistance = currDistance
// 重新计算其他所有的画线距离
this.recalculateAiByRuler()
} else {
const lineRef = self.aiDrawInfo['marker' + nameIndex + 'ai']
lineRef.setOptions({'endLabel': currRealDistance + 'mm'})
}++-
// 同时更新坐标信息
self.aiLineStartMiddleInfo[nameIndex + tag]['offsetX'] = currOffsetX
self.aiLineStartMiddleInfo[nameIndex + tag]['offsetY'] = currOffsetY
self.lastAiX = currOffsetX
self.lastAiY = currOffsetY
},
// 重新计算其他所有的画线距离
recalculateAiByRuler() {
const self = this
for (let nameIndex = 0; nameIndex < Object.keys(this.aiLineStartMiddleInfo).length / 2; nameIndex++) {
// 标尺信息不重新计算
if (nameIndex === 0 || nameIndex === 5) {
continue
}
const middleOffsetX = this.aiLineStartMiddleInfo[nameIndex+'middle']['offsetX']
const middleOffsetY = this.aiLineStartMiddleInfo[nameIndex+'middle']['offsetY']
const startOffsetX = this.aiLineStartMiddleInfo[nameIndex+'start']['offsetX']
const startOffsetY = this.aiLineStartMiddleInfo[nameIndex+'start']['offsetY']
const currDistance = Math.sqrt(( middleOffsetX - startOffsetX) ** 2 + (middleOffsetY - startOffsetY) ** 2)
let currRealDistance = ( this.rulerRealDistance * 100 / this.rulerDistance * currDistance / 100).toFixed(1)
const lineRef = self.aiDrawInfo['marker' + nameIndex + 'ai']
lineRef.setOptions({'endLabel': currRealDistance + 'mm'})
}
},
drawAiLine(color, x1, y1, x2, y2, index, tag, rulerRealDistance) {
const self = this
const nameIndex = self.aiIndex
// 由于图片自适应,等比例缩放
let imageHeight = $('#image-detail').height()
let imageWidth = $('#image-detail .el-image__inner').width()
const widthRatio = self.other.aiWidth / imageWidth
const heightRatio = widthRatio
x1 /= widthRatio
y1 /= heightRatio
x2 /= widthRatio
y2 /= heightRatio
this.markPointPlus(x1, y1, 'start', nameIndex)
this.markPointPlus(x2, y2, 'middle', nameIndex)
const dom31 = document.getElementById('marker' + nameIndex + 'ai' + "start")
const dom32 = document.getElementById('marker' + nameIndex + 'ai' + "middle")
let lineRef3 = new LeaderLine(dom31, dom32, {'color': color, 'path': 'straight'});
lineRef3.show()
self.aiDrawInfo['marker' + nameIndex + 'ai'] = lineRef3
const currDistance = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
const currRealDistance = (rulerRealDistance * 100 / self.rulerDistance * currDistance / 100).toFixed(1)
lineRef3.setOptions({'endLabel': currRealDistance + 'mm'})
self.$emit('updateResult', {'index': index, 'tag': tag, 'value': currRealDistance})
},
drawByAi() {
...
}
}
}
</script>
<style lang="scss" scoped>
.line-block {
height: 90vh;
.one {
display: flex;
justify-content: space-around;
// height: 100px;
}
.two {
margin-top: 100px;
display: flex;
justify-content: space-around;
}
.iframe-block {
margin-top: 50px;
}
.target {
display: inline-block;
background-color: #9ee7ea;
padding: 12px;
}
.block-one {
border: 1px solid blue;
padding: 10px;
}
.block-two {
border: 1px solid blue;
padding: 10px;
}
.block-three {
border: 1px solid blue;
padding: 10px;
}
.block-four {
border: 1px solid blue;
padding: 10px;
}
}
</style>
2. 基于Vue3+LeaderLine实现画线时对应点放大精确选点功能
我们在画线时,可能开始结束位置选择不准,导致测量结果会有偏差,所以新增放大功能,如下所示
核心源码:
<template>
...
<el-col :span="2" v-if="largeImageSelected">
<!-- 显示在右侧的放大之后的区域 -->
<div class="large" v-if="selectedInsoleUrl" :style="[{'backgroundImage':'url('+selectedInsoleUrl+')'}, {'background-position-x': + -positionX*(800/400)+200 + 'px'}, {'background-position-y': + -positionY*(1066/711)+210 +'px'}]" style="text-align: center; color: red; vertical-align: middle">
<div style="margin-top:200px">+</div>
</div>
</el-col>
...
</template>
<script>
import $ from 'jquery'
import {useMouseInElement} from "@vueuse/core";
export default {
...
setup () {
const curIndex = ref(0)
function mouseenter (index) {
curIndex.value = index
}
const target = ref(null)
const { elementX, elementY, isOutside } = useMouseInElement(target)
// 监听
const left = ref(0)
const top = ref(0)
// 定位位置
const positionX = ref(0)
const positionY = ref(0)
watch([elementX, elementY], () => {
positionX.value = elementX.value
positionY.value = elementY.value
})
return {
curIndex,
mouseenter,
target,
left,
top,
isOutside,
positionX,
positionY
}
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss">
.large {
position: absolute;
top: 0;
right: 12px;
width: 400px;
height: 400px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-repeat: no-repeat;
background-size: 1600px 1600px;
background-color: #f8f8f8;
}
.el-upload-dragger {
height: 100%;
}
</style>
- 其中使用useMouseInElement来实时获取当前鼠标位置并返回坐标点positionX、positionY
- 对于位置映射如-positionY*(1066/711)+210,则可以点击原图Y开始结束位置打印当前坐标点计算距离,然后根据原图结束位置,在实际放大后显示的位置反找原图位置即可
3. 基于Vue3+LeaderLine实现鼠标点击移动画线效果
3.1 实现效果如图所示
3.2 技术栈
Vue3 + LeaderLine
3.3 主要代码
<template>
<div id="image-detail">
<el-image :src="imageUrl" style="height: 800px; width: 600px" ref="canvasRef" @mousedown="startDrawing" @mousemove="drawLine" @mouseup="stopDrawing"/>
</div>
</template>
<script>
import LeaderLine from 'leader-line'
import $ from 'jquery'
export default {
components: {},
data() {
return {
imageUrl: 'https://picsum.photos/800/600',
isDrawing: false,
lastX: '',
lastY: '',
lineRef: null,
lines: [],
index: 0
}
},
methods: {
markPoint(offsetX, offsetY, tag) {
let nameIndex = this.index;
// 点击多个点,生成多个值
let div = document.createElement("div" + nameIndex + tag);
div.className = "marker";
div.id = "marker" + nameIndex + tag;
if (tag !== 'middle') {
// 红点的宽
div.style.width = "5px";
// 红点的高
div.style.height = "5px";
} else {
div.style.width = 0
div.style.height = 0
}
// 红点颜色
div.style.backgroundColor = "red";
div.style.left = offsetX + "px";
div.style.top = offsetY + "px";
div.style.position = "absolute";
// 边框半径百分比
div.style.borderRadius = "50%";
document.getElementById("image-detail").appendChild(div);
},
startDrawing(event) {
if (this.isDrawing) return;
this.isDrawing = true;
this.lastX = event.clientX;
this.lastY = event.clientY;
this.markPoint(this.lastX, this.lastY, 'start')
},
drawLine(event) {
if (!this.isDrawing) return;
let nameIndex = this.index
let dom1 = document.getElementById('marker' + nameIndex + 'start')
let dom2 = document.getElementById('marker' + nameIndex + "middle")
if (dom2 === null) {
this.markPoint(event.clientX, event.clientY, 'middle')
dom2 = document.getElementById('marker' + nameIndex + "middle")
this.lineRef = new LeaderLine(dom1, dom2,
{'color': 'red', 'path': 'straight'});
this.lineRef.show()
} else {
$('#marker' + nameIndex + "middle").css({'left': event.clientX, 'top': event.clientY})
this.lineRef.position()
}
},
stopDrawing(event) {
if (!this.isDrawing) return;
let nameIndex = this.index
$('#marker' + nameIndex + "middle").css({'left': event.clientX, 'top': event.clientY})
// TODO 计算距离
this.lineRef.setOptions({'endLabel': 'end'})
this.lineRef.position()
this.isDrawing = false;
if (this.lineRef) {
this.lines.push(this.lineRef);
}
this.index++
}
}
}
</script>
<style lang="scss" scoped>
</style>
3.4 针对报错处理
vue.config.js中配置如下
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: config => {
let path = require('path')
config.module.rules.push({
test: path.resolve(__dirname, 'node_modules/leader-line/'),
use: [
{
// 解决leader-line报错问题
loader: 'skeleton-loader',
options: {
procedure: content => `${content}export default LeaderLine`
}
}
]
})
}
})
欢迎关注公众号算法小生与我沟通交流
欢迎关注公众号 算法小生