图片热区功能

一、需求描述及效果图

1.需求描述:

根据后端返回的坐标及人员信息,在图片上的相应位置添加图片热区功能,点击可展示出对应的人员信息。
图片可进行缩放

2.示例:

(定位是随便写的,仅做示例)
鼠标悬浮到坐标位置上会出现水波纹的效果,点击定位处出现信息框来描述定位位置的信息。
image.png

二、思路

1.使用 <map> <area>结合<img>设置图片热区

官网地址:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area

代码示例:
image.png

2.数据结构
{"code": 0,"data": {"picture": "base64字符串","personnel": [{"id": 1,"personCode": "code_9527","personName": "nameA","personCard": "320123199901011234","personPhone": "13777777777","personPosition": "position01","personTail": 175,"personWeight": 70,"registerCamera": "Y","imgPath": "/home/picData/test_9527.jpg","coordinate": {"x": 10,"y": 20,"width": 100,"height": 100}},{"id": 2,"personCode": "code_10000","personName": "nameB","personCard": "320123199901022234","personPhone": "13777777777","personPosition": "position01","personTail": 190,"personWeight": 120,"registerCamera": "Y","imgPath": "/home/picData/test_10000.jpg","coordinate": {"x": 10,"y": 20,"width": 100,"height": 100}}]},"message": "请求成功!","time": 0
}
  • props.imageUrl是父组件传递过来的图片地址,上面数据结构中的picture
  • state.areaMap是人员信息数组,上面数据结构中的personnel
  • 数据结构中的coordinate是坐标信息,x和y是热区左上角的坐标;因为热区是矩形的,需要知道左上角和右下角的坐标,需根据热区的宽高来计算出右下角的坐标(热区形状根据需求来定,不唯一)
3.计算坐标
3.1 图片原始尺寸和渲染尺寸

需要获取到图片的原始固定尺寸和渲染的尺寸,从而计算出宽高的比例来正确定位
image.png

  • 图片原始尺寸:
// 图片原始尺寸
const naturalWidth = ref(0);
const naturalHeight = ref(0);
// 获取el-image组件实例的根DOM元素,也就是<img>元素,从而获取到图片的原始宽高
const imgEl = imageRef.value?.$el.querySelector('img');if (imgEl) {naturalHeight.value = imgEl.naturalHeight;naturalWidth.value = imgEl.naturalWidth;}

获取el-image组件实例的根DOM元素,元素,从而获取到图片的原始宽高

const imgEl = imageRef.value?.$el.querySelector('img');

注意:确保在组件已正确渲染并挂载到DOM树上之后再尝试获取img元素,以避免在组件尚未准备好时获取null值

  • 图片渲染尺寸:
  const renderWidth = imageRef.value?.$el.clientWidth;const renderHeight = imageRef.value?.$el.clientHeight;
3.2 根据缩放比例重新计算坐标

根据图片的原始尺寸和渲染尺寸计算出缩放比

  // 计算比例const ratioWidth = renderWidth / naturalWidth.value;const ratioHeight = renderHeight / naturalHeight.value;

根据缩放比重新计算热区

const imageRef = ref<any>(null);
const naturalWidth = ref(0);
const naturalHeight = ref(0);// 计算图片缩放比例
function ratioPic() {// 获取图片的原始尺寸const imgEl = imageRef.value?.$el.querySelector('img');console.log('imgEl', imageRef.value, imgEl.value)if (imgEl) {naturalHeight.value = imgEl.naturalHeight;naturalWidth.value = imgEl.naturalWidth;}// 图片渲染大小const renderWidth = imageRef.value?.$el.clientWidth;const renderHeight = imageRef.value?.$el.clientHeight;// 计算宽高缩放比例const ratioWidth = renderWidth / naturalWidth.value;const ratioHeight = renderHeight / naturalHeight.value;// 重新计算热区state.areaMap = [];state.initAreaMap.map((item) => {const obj = {...item,coordinate: {...item.coordinate,width: Math.round(item.coordinate.width * ratioWidth),height: Math.round(item.coordinate.height * ratioHeight),x: Math.round(item.coordinate.x * ratioWidth),y: Math.round(item.coordinate.y * ratioHeight),},};state.areaMap.push(obj);});
}

initAreaMap 存放的时候从接口获取的图片热区坐标数据
areaMap 存放经过缩放比计算后的图片热区新坐标(计算时注意要置空)

3.3 监听窗口尺寸变化,实时调整热区
onMounted(async () => {// 添加窗口尺寸变化的监听以实时调整热区window.addEventListener('resize', handleResize);
});onUnmounted(() => {window.removeEventListener('resize', handleResize);
});// 监听屏幕变化重新计算图片热区宽高
function handleResize() {// 在这里获取图片当前的实际尺寸,并重新计算热区坐标ratioPic();
}
3.4 处理矩形坐标: 左上角 ,右下角
function handleCoordinate(coordinate: Coordinate) {const x2 = coordinate.x + coordinate.width;const y2 = coordinate.y + coordinate.height;return `${coordinate.x},${coordinate.y},${x2},${y2}`;
}

因为后端返回的坐标数据是图片热区的左上角坐标和宽高,在使用时需计算出右下角坐标
矩形热区需要坐标:(左上角x,y 右下角x,y)
coords=“x1, y1, x2, y2”

以上,浏览器自带的缩放功能可正确渲染出热区; 如果需要对图片进行放大和缩小功能,对热区坐标的处理请看下面第4点【图片缩放功能】

4.图片缩放功能

Element Plus的组件可通过 previewSrcList 开启预览大图功能可对图片进行放大缩小
image.png但是这个功能并没有提供方法进行其他操作,所以根据需求我放弃使用previewSrcList,重写了一个图片预览缩放功能。
image.png

4.1 遮罩层及图片缩放组件

通过对scaleVisible的控制来打开/关闭遮罩层和图片预览
是图片缩放组件的内容,将图片地址及人员信息传递过去
image.png
样式设置:
注意层级关系

// 遮罩层样式
.mask {// 相对于浏览器窗口进行定位,全屏遮住position: fixed; top: 0;left: 0;width: 100%;height: 100%;background-color: #808080;opacity: 0.5;z-index: 8888;
}
// 遮罩层上的关闭按钮
.mask_close {position: fixed;right: 40px;top: 40px;width: 50px;height: 50px;cursor: pointer;z-index: 10000;
}
// 图片缩放组件
.pic_scale {position: fixed;top: 0;left: 0;width: 100%;height: 100%;z-index: 9999;display: flex;justify-content: center;align-items: center;
}
4.2 缩放工具栏

image.png

.flow-toolbar-wrap {position: absolute;bottom: 30px;width: 100%;display: flex;align-items: center;justify-content: center;.flow-toolbar {display: flex;align-items: center;justify-content: center;gap: 10px;height: 40px;box-shadow: var(--el-box-shadow-light);background-color: #66686b;opacity: 0.8;border-radius: 20px;padding: 0 25px;z-index: 1;.toolbar-item {user-select: none;color: #fff;font-size: 20px;cursor: pointer;}}
}
4.4 计算缩放比
const state = reactive({areaMap: [] as PicInfo[],initAreaMap: [] as PicInfo[],scale: 1, // 图片缩放比例originWidth: 0,originHeight: 0,
});// 放大操作
function toolbarZoomIn() {state.scale = Number((state.scale + 0.1).toFixed(1));applyZoom();
}
// 缩小操作
function toolbarZoomOut() {if (state.scale === 0.7) return;state.scale = Number((state.scale - 0.1).toFixed(1));applyZoom();
}
// 1.图片加载完成之后再去计算宽高,避免网络请求被延迟或阻塞导致尺寸无法获取。
function imgOnLoad() {ratioPic();state.originWidth = imageRef.value?.$el.clientWidth;state.originHeight = imageRef.value?.$el.clientHeight;
}
// 计算经过缩放后的宽高 
function applyZoom() {// 2.整体放大图片外层盒子和图片const divEl = document.querySelector('.scaleImage') as HTMLElement;divEl.style.transform = `scale(${state.scale})`;divEl.style.width = `${state.originWidth * state.scale}px`;divEl.style.height = `${state.originHeight * state.scale}px`;// 3.重新计算热区,同前面的3.2的操作ratioPic();
}

注意点:

  1. 要在图片加载完成之后再去计算宽高,避免网络请求被延迟或阻塞导致尺寸无法获取
  2. 放大/缩小时要整体放大/缩小外层盒子和图片
4.5鼠标滚动缩放
onMounted(async () => {// 添加鼠标滚动缩放const mapEl = document.querySelector('.scaleImage') as HTMLElement;mapEl.addEventListener('mousewheel', (e) => {if (e instanceof WheelEvent) {e.preventDefault(); // 阻止默认的滚轮行为,如页面滚动const delta = Math.sign(e.deltaY);// 根据滚动方向和步长调整缩放比例if (delta > 0) {toolbarZoomIn();} else {toolbarZoomOut();}}});
});
5.添加热区样式及信息框样式
5.1 热区样式

设置鼠标悬浮到热区范围内时的样式:两个圈的水波纹效果
image.png
样式:

:deep(.areaHighlight) {/* 设置高亮区域的背景颜色和透明度等样式 */pointer-events: none; /* 防止覆盖原始交互 */width: 50px;height: 50px;position: relative;
}:deep(.areaHighlight)::before,
:deep(.areaHighlight)::after {position: absolute;content: '';width: 100%;height: 0;padding-bottom: 100%; /* 设置为宽度的百分比,实现宽高比为1:1 */top: 0;left: 0;background: #a5d7ff;border-radius: 50%;animation: animLoader 2s linear infinite;
}:deep(.areaHighlight)::after {animation-delay: 1s;opacity: 0.1;
}
// 水波纹动画
@keyframes animLoader {0% {transform: scale(0);opacity: 1;}100% {transform: scale(1);opacity: 0;}
}

利用鼠标的移入和移出事件来添加样式
image.png

// 热区高亮
function highlightArea(area: any) {const overlay = document.createElement('div');overlay.style.position = 'absolute';// 根据area.shape和area.coords计算并设置overlay的位置和尺寸overlay.id = `overlay-${area.personCode}`;overlay.style.left = `${area.coordinate.x}px`;overlay.style.top = `${area.coordinate.y}px`;overlay.style.width = `${area.coordinate.width}px`;overlay.style.height = `${area.coordinate.height}px`;overlay.classList.add('areaHighlight');mapContainer.value?.appendChild(overlay);
}
// 移除高亮
function removeHighlight() {const overlayDiv = document.querySelectorAll('.areaHighlight');overlayDiv.forEach((item) => {mapContainer.value?.removeChild(item);});
}
5.2 信息框动画效果
.model_scale {display: none;position: absolute;width: 35%;min-width: 300px;max-width: 350px;border-radius: 10px;z-index: 2;color: #fff;border: 2px solid gold;border-radius: 10px;background: #ffd700;transition: all 0.3s;
}
// 边框动画
.model_scale::before {content: '';position: absolute;top: -10px;left: -10px;right: -10px;bottom: -10px;border: 2px solid #ffd700;border-radius: 10px;animation: borderAni 3s infinite linear;
}.model_scale::after {content: '';position: absolute;top: -10px;left: -10px;right: -10px;bottom: -10px;border: 2px solid #ffd700;border-radius: 10px;animation: borderAni 3s infinite linear;
}@keyframes borderAni {0%,100% {clip-path: inset(0 0 98% 0);}25% {clip-path: inset(0 98% 0 0);}50% {clip-path: inset(98% 0 0 0);}75% {clip-path: inset(0 0 0 98%);}
}.model_scale::after {animation: borderAni 3s infinite -1.5s linear;
}
.model_close {position: absolute;top: -15px;right: -15px;width: 30px;height: 30px;cursor: pointer;z-index: 3;
}
.model_content {color: #000;font-size: 13px;
}

image.png

// 点击热区
function areaClick(area: any) {// 获取要展示的信息personnelInfo.forEach((item: InfoList) => {item.value = area[item.key] ?? '';});// 打开信息框,定位const modelEl = document.querySelector('.model_scale') as HTMLElement;modelEl.style.display = 'block';modelEl.style.left = area.coordinate.x + 100 + 'px';modelEl.style.top = area.coordinate.y + 50 + 'px';
}// 关闭信息框
function closeModel() {const modelEl = document.querySelector('.model_scale') as HTMLElement;modelEl.style.display = 'none';
}

三、遇到的问题

1.无法正确获取图片到宽高时,值为0

问题描述: 在浏览器中,通过JavaScript获取图片(元素)的宽高属性时,在某些情况下可能会获取不到正确的值或者得到0

  1. 异步加载:浏览器加载网页时,HTML文档结构优先于外部资源(如图片、样式表和脚本)。当JavaScript代码执行时,如果图片尚未完全加载完成,则其naturalWidth或clientWidth等尺寸属性可能还未被浏览器填充,因此返回值为0。
  2. 事件监听不足:为了确保能够获取到图片的真实尺寸,应当在图片加载完成后触发一个事件处理函数,比如使用onload事件来确保图片已经加载完毕。
  3. 调试器影响:虽然不常见,但在极少数情况下,打开浏览器调试器并刷新页面可能导致渲染过程中的细微差别,这可能是由于强制重新布局(relayout)或重绘(repaint)引起的。如果你是在DOMContentLoaded或load事件触发之前就尝试获取图片尺寸,并且同时打开了调试器,那可能由于网络请求被延迟或阻塞导致尺寸无法获取。
  4. 缓存问题:有时候,特别是开发环境下,浏览器缓存可能导致实际图片未被重新加载,因此onload事件没有触发,尺寸信息也就无法更新。

此处我是遇到了第三个问题:打开调试器时无法正确获取图片宽高。

解决方法:确保在图片加载完成后才去读取其尺寸属性,或者是使用Promise或者async/await方式来等待图片加载完成。

使用load方法
image.png
在图片加载完成之后再去操作

// 图片加载完成之后再去计算宽高,避免网络请求被延迟或阻塞导致尺寸无法获取。
function imgOnLoad() {ratioPic();state.originWidth = imageRef.value?.$el.clientWidth;state.originHeight = imageRef.value?.$el.clientHeight;
}
2.图片在缩放时,外层div无法和图片一样大导致信息框等样式产生定位错误

如果在缩放时只控制图片的放大和缩小,缩放到一定程度时图片外层的div无法和图片的DOM元素一样大,且基于外层div进行定位的元素定位会产生误差

解决方法:
1.计算图片元素和外层div缩放产生的误差值,在计算坐标时加上误差值
2.缩放时直接对外层div进行缩放,需让图片和外层div保持一样的宽高

这里我选择了第二种方式

function applyZoom() {/*   const imgEl = imageRef.value?.$el.querySelector('img');imgEl.style.transform = `scale(${state.scale})`;imgEl.style.width = `${state.originWidth * state.scale}px`;imgEl.style.height = `${state.originHeight * state.scale}px`; */// 整体放大图片外层盒子和图片const divEl = document.querySelector('.scaleImage') as HTMLElement;divEl.style.transform = `scale(${state.scale})`;divEl.style.width = `${state.originWidth * state.scale}px`;divEl.style.height = `${state.originHeight * state.scale}px`;ratioPic();
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索设计模式的魅力:为什么你应该了解装饰器模式-代码优化与重构的秘诀

设计模式专栏&#xff1a;http://t.csdnimg.cn/nolNS 开篇 在一个常常需要在不破坏封装的前提下扩展对象功能的编程世界&#xff0c;有一个模式悄无声息地成为了高级编程技术的隐形冠军。我们日复一日地享受着它带来的便利&#xff0c;却往往对其背后的复杂性视而不见。它是怎样…

Ingress

文章目录 环境准备什么是 Ingress认识 Ingress 资源Ingress 控制器(controller)Ingress 规则pathType 路径类型多重匹配Ingress 类TLS生成证书创建密钥 环境准备 下面的 yaml 文件内容&#xff0c;是使用 sts 创建两个 web 服务&#xff0c;并配置对应的 servcie。web 服务的首…

【MybatisPlus篇】查询条件设置(范围匹配 | 模糊匹配 | 空判定 | 包含性判定 | 分组 | 排序)

文章目录 &#x1f384;环境准备⭐导入依赖⭐写入User类⭐配置启动类⭐创建UserDao 的 MyBatis Mapper 接口&#xff0c;用于定义数据库访问操作⭐创建配置文件&#x1f6f8;创建测试类MpATest.java &#x1f354;范围查询⭐eq⭐between⭐gt &#x1f354;模糊匹配⭐like &…

基于flask的个人博客项目从0到1

项目展示(持续完善中…) 首页 文章时间线页面 笔记页面 留言页面 关于页面 后台页面-文章管理 后台页面-笔记页面 后台页面-分类 后台管理-新增标签 后台管理-标签页面 后台管理-新增标签 后台管理-关于页面 2.项目详述 该博客开源地址点击跳转&#xff0c;该项目已部署上…

flowable 设置自定义属性教程

概述 由于工作需要给flowable工作流设计器添加自定义属性&#xff0c;以满足功能实现。所以这篇文章介绍下用flowable 开源的的flowable-ui 前端添加自定义属性&#xff0c;后端解析属性值的例子。 技术栈 序号技术点名称版本1Flowable6.8.0 使用的是flowable6.8.0 版的代码…

如何在Windows部署GoLand并通过SSH远程连接Linux服务器

文章目录 1. 安装配置GoLand2. 服务器开启SSH服务3. GoLand本地服务器远程连接测试4. 安装cpolar内网穿透远程访问服务器端4.1 服务器端安装cpolar4.2 创建远程连接公网地址 5. 使用固定TCP地址远程开发 本文主要介绍使用GoLand通过SSH远程连接服务器&#xff0c;并结合cpolar内…

二叉树(1)

1 树概念及结构 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&a…

c++设计模式之适配器模式

作用 适配器模式的作用是可以让不兼容的接口在一起工作 案例 假如现在有一台电脑和一台投影仪&#xff0c;现在需要把电脑和投影仪进行连接 在此基础上我们再假设&#xff0c;电脑只能连接VGA接口&#xff0c;而投影仪的种类繁多&#xff0c;有VGA接口、HAMI接口等多种种类…

初探分布式链路追踪

本篇文章&#xff0c;主要介绍应用如何正确使用日志系统&#xff0c;帮助用户从依赖、输出、清理、问题排查、报警等各方面全面掌握。 可观测性 可观察性不单是一套理论框架&#xff0c;而且并不强制具体的技术规格。其核心在于鼓励团队内化可观察性的理念&#xff0c;并确保由…

低版本MATLAB打开高版本Simulink文件的方法

打开simulink&#xff0c;依次点击“建模”、“环境”、“simulink预设项”&#xff0c;如图所示&#xff1a; 然后在弹出的窗口中&#xff0c;点击“模型文件”&#xff0c;并取消勾选“不要加载用更新版本的simulink创建的模型”&#xff0c;接着点击“应用”即可。如图所示&…

跟着cherno手搓游戏引擎【19】抽象纹理

引入&#xff1a; 导入stb_image: GitHub - nothings/stb: stb single-file public domain libraries for C/C 下载复制stb_image.h的内容&#xff08;8000多行&#xff09;&#xff0c;然后粘到如图位置 stb_image.cpp: #include"ytpch.h" #define STB_IMAGE_IM…

Revit中使用依赖注入

依赖注入的技术已经很成熟&#xff0c;本文主要是说明一下Revit中的适用版本与介绍相关的开源项目。 版本问题 版本 目前的依赖注入包无法支持Revit 2020 以下的版本&#xff0c;原因是因为包中的依赖项与Revit本身的依赖项不一致导致的&#xff0c;所以说如果使用Revit DI…

题目:有1,2,3,4共四个数字,能组成多少个不相同而且无重复数字的三位数有多少个,都是多少?lua

这是作者的思路&#xff0c; 创建三个表&#xff0c; 第一个数是从四个数遍历&#xff0c; 第二个是数剔除第一个数进行遍历 第三个是剔除第一第二个数遍历 脚本如下 local a{1,2, 3, 4} local b{} local c{} local d{} local function copy(tbl) local ctbl{} for k,v in…

机器学习复习(4)——CNN算法

目录 数据增强方法 CNN图像分类数据集构建 导入数据集 定义trainer 超参数设置 数据增强 构建CNN网络 开始训练 模型测试 数据增强方法 # 一般情况下&#xff0c;我们不会在验证集和测试集上做数据扩增 # 我们只需要将图片裁剪成同样的大小并装换成Tensor就行 test_t…

nginx初学者指南

一、启动、停止和重新加载配置 前提&#xff1a;先要启动nginx 在Windows上启动nginx的步骤如下&#xff1a; 1. 下载并安装nginx。可以从nginx官网下载适合自己操作系统的版本&#xff0c;一般是zip压缩包&#xff0c;解压到指定目录中。 2. 进入nginx的安装目录&#xff…

LeetCode:138. 随机链表的复制之如何有效copy

自己复制的话&#xff0c;很容易写出来一个时间复杂度O&#xff08;n ^ 2&#xff09; 空O&#xff08;n&#xff09;的做法 我们可以参考基因的复制&#xff0c; 目录 题目&#xff1a; 实现思路&#xff08;基因复制式的copy&#xff09;&#xff1a; 官方快慢指针解法&…

基于Python的招聘网站爬虫及可视化的设计与实现

摘要&#xff1a;现在&#xff0c;随着互联网网络的飞速发展&#xff0c;人们获取信息的最重要来源也由报纸、电视转变为了互联网。互联网的广泛应用使网络的数据量呈指数增长&#xff0c;让人们得到了更新、更完整的海量信息的同时&#xff0c;也使得人们在提取自己最想要的信…

山东淄博刑侦大队利用无人机抓获盗窃团伙

山东淄博刑侦大队利用无人机抓获盗窃团伙 近期&#xff0c;山东淄博临淄区发生多起盗窃案件。通过视频追踪和调查访问&#xff0c;推断临淄区某村可能为嫌疑人藏匿地点。刑侦大队无人机应急小组迅速到达现场&#xff0c;经无人机高空侦查&#xff0c;发现并锁定了嫌疑人的藏匿…

【开源】SpringBoot框架开发城市桥梁道路管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥梁4.2 新增城市桥梁4.3 编辑城市桥梁4.4 删除城市桥梁4.5 查询单个城市桥梁 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的城市桥梁道路管理系统&#xff0c;支持…

灵伴科技(Rokid)借助 Knative 实现 AI 应用云原生 Serverless 化

作者&#xff1a;朱炜栋、元毅、子白 公司介绍 Rokid 创立于 2014 年&#xff0c;是一家专注于人机交互技术的产品平台公司&#xff0c;2018 年即被评为国家高新技术企业。Rokid 作为行业的探索者、领跑者&#xff0c;目前致力于 AR 眼镜等软硬件产品的研发及以 YodaOS 操作系…