轻量应用服务器做网站免费查权重工具
需求
- 后面返回解析后的文本和四角坐标,在图片上渲染成框,并且可复制。
- 图片还可以缩放、拖拽
实现
这里要重点讲下关于OCR文本框的处理:
因为一些文字可能是斜着放的,所有我们要特殊处理,根据三角函数来计算出它的偏转角度,从而旋转,所有下面的 handleStyle
函数有点复杂,不说明怕你看不懂😂
<template><div class="preview-wrap" @mousewheel="handerMousewheel"><div class="preview"><divclass="preview-content":style="{top: imgConfig.imgTop + 'px',left: imgConfig.imgLeft + 'px',transform: `scale(${imgConfig.imgScale}) rotateZ(${imgConfig.imgRotate}deg)`,}"ref="previewContentRefs"@mousedown="handleMoveStart"><img :src="src" @load="onImageLoaded($event)" /><!-- OCR 识别框 --><template v-if="imgConfig.width && imgConfig.height && ocrInfo?.length"><divclass="ocr-box"v-for="item in ocrInfo":style="handleStyle(item[2])">{{ item[0] }}</div></template></div></div></div>
</template><script setup lang="ts">
import { reactive, ref } from 'vue';
interface Props {src: string;ocrInfo?: OCRInfoItem[];
}
const props = defineProps<Props>();
const imgConfig = reactive({width: 0, // 图片原始宽度height: 0, // 图片原始高度wrapWidth: 0, // 图片容器宽度wrapHeight: 0, // 图片容器高度startPageX: 0, // 按下鼠标时当前鼠标所在位置xstartPageY: 0, // 按下鼠标时当前鼠标所在位置yimgTop: 0, // 图片定位置topimgLeft: 0, // 图片定位置leftimgScale: 1, // 图片缩放imgRotate: 0, // 图片旋转
});
const previewContentRefs = ref<HTMLElement | null>(null);const handleStyle = (axis: any) => {
// 这里为什么要处理三角形:因为一些文字是偏转的,需要特殊处理角度。// 处理偏斜的文字// 三角形的高const triangleY = axis[0][1] - axis[1][1];// 三角形的底const triangleX = axis[1][0] - axis[0][0];// 三角形的斜边const triangle = Math.sqrt(Math.abs(triangleY * triangleY) + Math.abs(triangleX * triangleX),);// sinA = 对边 / 斜边const sinA = triangleY / triangle;// 旋转角度 = asin(sinA) / π * 180let rotate = Math.asin(sinA) / (Math.PI / 180);return {width: ((axis[1][0] - axis[0][0]) / imgConfig.width) * 100 + '%',height: ((axis[3][1] - axis[0][1]) / imgConfig.height) * 100 + '%',top: (axis[0][1] / imgConfig.height) * 100 + '%',left: (axis[0][0] / imgConfig.width) * 100 + '%',fontSize:((axis[3][1] - axis[0][1]) / imgConfig.height) * imgConfig.wrapHeight +'px',// 注意旋转正负 三角形的高大于0 旋转角度为负数transform: `rotate(${triangleY > 0 ? '-' : ''}${rotate}deg)`,};
};// 鼠标滚轮缩放图片
const handerMousewheel = (e: any) => {// 鼠标没有在图片区域内就不缩放(解决多列表下拉问题)if (e.target.className !== 'preview') {// 火狐浏览器为e.detail 其他浏览器均为e.wheelDeltaif ((e.wheelDelta > 0 || e.detail > 0) && imgConfig.imgScale < 4) {imgConfig.imgScale += 0.1;} else if ((e.wheelDelta < 0 || e.detail < 0) && imgConfig.imgScale > 0.5) {imgConfig.imgScale += -0.1;}// 阻止浏览器默认滚动事件e.preventDefault();}
};const onImageLoaded = (event: any) => {if (previewContentRefs.value) {// 图片加载完成后获取图片容器的宽高imgConfig.wrapWidth = previewContentRefs.value.clientWidth;imgConfig.wrapHeight = previewContentRefs.value.clientHeight;}// 获取图片的原始宽高const { naturalWidth, naturalHeight } = event.target;imgConfig.width = naturalWidth;imgConfig.height = naturalHeight;
};// 按下鼠标开始移动图片
const handleMoveStart = (e: any) => {// 如果不是图片就不拖动if (e.target.tagName !== 'IMG') {return;}const { pageX, pageY } = e;imgConfig.startPageX = pageX - imgConfig.imgLeft;imgConfig.startPageY = pageY - imgConfig.imgTop;document.addEventListener('mousemove', handleMore, false);document.addEventListener('mouseup', clearEvent, false);e.preventDefault();
};// 移除事件
const clearEvent = () => {document.removeEventListener('mousemove', handleMore, false);
};// 按住鼠标移动时的处理
const handleMore = (e: any) => {const { pageX, pageY } = e;imgConfig.imgTop = pageY - imgConfig.startPageY;imgConfig.imgLeft = pageX - imgConfig.startPageX;e.preventDefault();
};
</script><style scoped lang="scss">
.preview-wrap {width: 100%;height: 100%;overflow: hidden;li {list-style: none;}.preview {position: relative;display: flex;align-items: center;justify-content: center;width: 100%;height: 100%;&-content {position: relative;transition: 0.2s transform;height: 100%;> img {width: auto;height: 100%;// 禁止图片拖动-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;// 禁止拖拽-webkit-user-drag: none;-moz-user-drag: none;-ms-user-drag: none;}.ocr-box {position: absolute;left: 0;top: 0;background-color: rgba(255, 240, 108, 0.3);color: transparent;box-sizing: border-box;overflow: hidden;line-height: 1;text-align: justify; // 两端对齐text-align-last: justify; // 两端对齐&::selection {background-color: rgba(49, 140, 238, 0.5);}}}&-footer {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);&-tools {display: flex;justify-content: center;li {margin-right: 10px;padding: 10px;border-radius: 50%;background: rgba(110, 110, 110, 0.7);cursor: pointer;> img {display: block;width: 30px;height: 30px;}&:hover {i {color: #ef544e;}}}}&-thumbs {margin-top: 20px;max-width: 700px;overflow-x: auto;white-space: nowrap;.thumb-item {padding: 10px;margin-right: 10px;display: inline-block;background: rgba(102, 102, 102, 0.7);border-radius: 5px;cursor: pointer;img {width: 60px;height: 60px;object-fit: cover;}&.active {background: rgba(239, 84, 78, 0.7);}}&::-webkit-scrollbar {height: 10px;}&::-webkit-scrollbar-thumb {border-radius: 10px;-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);background: #d2d2d2;cursor: pointer;}&::-webkit-scrollbar-track {border-radius: 10px;background: #fff;}}}.close-icon {padding: 10px;position: absolute;top: 30px;right: 30px;border-radius: 50%;background: rgba(110, 110, 110, 0.7);cursor: pointer;> img {display: block;width: 30px;height: 30px;}}}
}
</style>