惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

元视角

Terraform 极简入门:从 AWS-CLI 到基础设施即代码(IaC) - 元视角 .NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 盛世幻象:从荔枝看盛唐的兴衰 - 元视角 观影 - 元视角 听歌 - 元视角 个人作品 - 元视角 站点统计 - 元视角 读书 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 留言 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 命运、偏见与自由:《魔童之哪吒闹海》的终极抗争 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 走走停停,允许一切发生 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 从抖音看见世界的参差 - 元视角 俯仰之间:五一小长假出行记 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 《你想活出怎样的人生》与宫崎骏的自我和解 - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 前端视频播放技术概览 - 元视角 你好,千寻小姐 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 杂感·七月寄望 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 在 Vue.js 中使用 Mock.js 实现接口模拟 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 读《一个叫欧维的男人决定去死》 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 烟波梦影,从天国王朝到刺客信条 - 元视角 关于 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 写在冬阳升起以前 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 从《失控玩家》中得到的启示 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 夕雾花园:从建筑中读出的爱情和美学 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 洗衣随想曲 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 浪客剑心:一曲幕末时代的挽歌 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的负载均衡 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 假如时间有温度 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 浅议 EF Core 分库分表及多租户架构的实现 - 元视角
基于 SVG 的图形交互方案实践 - 元视角
2023-08-20 · via 元视角

不知道从什么时候起,人们开始喜欢上数字大屏这种“花里胡哨”的东西,仿佛只要用上“科技蓝”这样神奇的色调,就可以让一家公司焕然一新,瞬间变得科技感满满。不管数字大屏的实际意义,是用来帮助企业监控和决策,还是为了方便领导参观和视察,抑或是为了向外界展示和宣传。总之,自从数字大屏诞生之后,它始终就没能摆脱其前任“中国式报表”那种大而全的宿命。追随着 EChartsSupersetFineBIDataEase 等数据可视化产品的身影一路走来,你会发现人们在追求“花里胡哨”这件事情上永无止境。如今的数据大屏,元素多(表格、视频、2D/2.5D/3D地图)、种类多(图表、报表、流程图)、媒介多(PC、平板、电视、LED),主打的就是一个眼花缭乱。

某数据中心设备运行监控示意图 某数据中心设备运行监控示意图

当数字大屏的这股时尚潮流涌向物联网和工业互联网领域以后,就不可避免地催生出像上面这样的“数字大屏”需求,请原谅我使用如此模糊的措辞,因为我实在难以给它一个准确的定义,工艺流程图、设备运行监控图、组态图、SCADA…。也许,这些名称不见得都能做到全面概括,可这些东西的确具备了数字大屏的特征,哪怕这些设备元件、管道阀门在科技蓝配色下违和感十足。作为一位低调的程序员,我一向不喜欢这种粉饰太平的面子工程,所以,当设计师同事带着设计图来找我时,我当时内心是拒绝的:

基于 HTML5 图片热区特性实现交互的思路 基于 HTML5 图片热区特性实现交互的思路

也许,此时你的内心深处会闪过一丝蔑视,认为这有什么难度呢?我只需要在图片上叠加若干个透明的 div,这样不就可以实现图片特定区域的交互逻辑啦!我承认,这是一个非常好的思路,但是在实践过程中你就会发现,div 的交互区域通常都是一个标准的矩形,而设计师同事常常使用圆角矩形和不规则图形来增强设计感。因此,在交互方面可能会存在一些缺陷,尤其是在 2.5D 的图片设计稿中,交互区域实际上是一个多边形。接下来,我将介绍一种基于 HTML5 图片热区特性来实现交互的思路:

<div class="container">
    <img src="Demo-01.jpg" usemap="#imageMap" style="width: 600px; height: 315px">
    <map name="imageMap"></map>
</div>

首先,准备一张图片以及一个 map 标签,并且这个 map 标签通过 usemap 属性与这张图片进行了关联。参照上面的示意图,我们定义了两个可交互的区域。其中,区域1是矩形区域,区域2是圆形区域:

const areas = [{
    key: '半泽直树',
    shape: 'rect',
    coords: [0, 0, 308.5, 315]
}, {
    key: '大和田',
    shape: 'circle',
    coords: [418, 134, 157.5]
}]

因为 area 标签需要搭配 map 标签来使用,所以,我们将通过下面的代码来动态地创建区域,同时为每个区域绑定相应的事件:

const popup = document.getElementById('popup');
const imageMap = document.getElementsByName('imageMap')[0];

areas.forEach(area => {

    // 创建区域
    let ele = document.createElement('area');
    ele.shape = area.shape;
    ele.coords = area.coords.join(',')
    ele.setAttribute('data-key', area.key);

    // 绑定事件
    ele.onclick = function (e) {
        alert(e.target.dataset.key);
    };
    ele.onmousemove = function (e) {
        popup.innerHTML = `<div class="content">${e.target.dataset.key}</div>`;
        popup.style.left = `${e.x - 75}px`;
        popup.style.top = `${e.y - 45}px`;
        popup.style.display = 'block'
    };
    ele.onmouseover = function (e) {
        popup.style.display = 'display';
    };
    
    // 添加到map标签
    imageMap.appendChild(ele);
})

此时,当我们鼠标移动到指定的区域时,就可以触发对应的气泡提示,如下图所示:

See the Pen Marker-Clusterer by qinyuanpei (@qinyuanpei) on CodePen.

这个方案相对于纯 div 标签的思路要稍微好上一点点,因为 area 标签里的 shape 属性支持多边形,这意味着不规则区域的交互可以继续进行下去。可这种方案,本质上并没有摆脱“手工标注”,你不得不为每一个区域标注好坐标,这对于没有设计感的程序员来说可能是一场折磨,更重要的是,一旦这个方案运用到数字大屏上面,你总要去解决屏幕尺寸变化、全屏/非全屏等一系列问题,显然,这个时候这些区域的坐标都需要重新计算。这个时候,博主就想到了 SVG 这种可缩放的矢量图形,这是一种自描述的标记语言,无论怎么缩放都不会失真。下面是一个简单的 SVG 图片示例,我们可以大致了解到其结构是一个 XML 文件:

See the Pen Marker-Clusterer by qinyuanpei (@qinyuanpei) on CodePen.

既然 SVG 中本身就自带着描述位置的坐标信息,那么,我们是不是可以基于 SVG 来实现相应的交互逻辑呢?下面我们以一张中国地图为例来验证这个想法:

OK,我们希望实现什么样的交互效果呢?当鼠标移动到指定的省份时,该省份会变成红色高亮状态,并且会在鼠标位置触发气泡提示。具体怎么做呢?首先,我们来准备下面的 HTML 结构:

<div id="popup" class="rectangle" style="display: none;"></div>
<div class="container"></div>

接下来,我们通过脚本来加载 SVG 图片,同时为其绑定相关事件:

const popup = document.getElementById('popup');

fetch('China.svg')
    .then(res => res.text())
    .then(text => {
        const container = document.getElementsByClassName('container')[0];
        container.innerHTML = text;

        // 允许SVG交互
        const svg = document.getElementsByTagName('svg')[0];
        svg.setAttribute('pointer-events', 'cursor');

        // 为每一个路径绑定事件
        const paths = svg.childNodes[0].childNodes;
        for (var i = 0; i < paths.length; i++) {
            paths[i].onclick = function (e) {
                alert(`${e.target.id}`)
            };
            paths[i].onmouseover = function (e) {
                e.target.setAttribute('fill', 'red');
                popup.innerHTML = `<div class="content">${e.target.id}</div>`
                popup.style.left = `${e.x - 75}px`;
                popup.style.top = `${e.y - 45}px`;
                popup.style.display = 'block'
            };
            paths[i].onmouseout = function (e) {
                e.target.setAttribute('fill', '#eee');
                popup.style.display = 'none'
            };
        }
    })

在这个示例中,每一个省份对应着 SVG 中的一个 path 节点,因此,我们只需要在加载完 SVG 以后再去遍历这些节点即可。可能大家会有疑问,为什么这里不用 imgembed 或者 object 这些标签来承载一个 SVG 图形呢?因为这样我们是没有办法访问 svg 标签及其子节点的。现在,我们就可以看到下面的效果:

See the Pen Marker-Clusterer by qinyuanpei (@qinyuanpei) on CodePen.

这两个方案,你更倾向于哪一个呢?从一个程序员的角度来看,我更喜欢使用 SVG,因为 XML 这种格式不管对人还是机器来说都非常友好。实际上,到目前为止,这篇博客里对方案可行性的探索业已完成,而在现实中,更多的挑战往往来自非技术因素。譬如,如何让设计师同事适应使用相对普通、朴素的矢量图格式。当然,从这篇文章的思路延伸出去,无论是复杂的数据大屏,还是布局编辑器/低代码、地图、流程图、工作流等问题,我们都无法摆脱 DOM、Canvas、WebGL、SVG 等知识体系。特别是当我们需要处理拖拽/平移、缩放、旋转等常规操作时,这些都是最具挑战性的部分,不是吗?