使用svg在元素直接绘制连线箭头

news2024/11/19 3:48:29

注意:svg的图形绘制的点位置坐标是基于画布的位置坐标,相当于从左上角的点为起点。

先来个简单示例:

在点与点之间绘制连线箭头

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <svg width="300" height="300" style="background: #efefef">
    <path d="M 100 50 L 50 100" stroke="url(#grad1)" stroke-width="2" marker-end="url(#arrowhead)" />
    <path d="M 100 50 L 100 100" stroke="black" stroke-width="2" marker-end="url(#arrowhead)" />
    <path d="M 100 50 L 150 100" stroke="black" stroke-width="2" marker-end="url(#arrowhead)" />
    <defs>
      <marker id="arrowhead" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
        <path d="M 0 0 L 10 5 L 0 10 z" />
      </marker>
      <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
        <stop offset="0%" stop-color="#ff0000" />
        <stop offset="100%" stop-color="#00ff00" />
      </linearGradient>
    </defs>
  </svg>
</body>
</html>

在这里插入图片描述
上面示例中可以看到,svg画布的位置在哪,path中点的坐标就从哪里开始,默认是从浏览器可视窗口的左上角开始。那么我们只要知道点的坐标就能绘制箭头了。
接下来,就是获取dom元素的位置坐标,使用到Element.getBoundingClientRect()
在这里插入图片描述
比如,我们现在需要在1个顶点,2个终点之间设置连线,由于path的点坐标是基于svg的画布位置,所以我们可以把画布的位置基于元素定位,画布的宽为3个元素之间最大-宽度最小的值,高为高度最高-高度最低的值。粉色框为svg画布的位置,框1、框2、框3表示顶点元素可能出现的位置。
在这里插入图片描述

代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div style="width: 50px;height: 50px;position: absolute;left: 55px;top: 160px;background: red;" id="box1"></div>
  <div style="width: 50px;height: 50px;position: absolute;left: 55px;top: 530px;background: blue;" id="box2"></div>
  <div style="width: 50px;height: 50px;position: absolute;left: 925px;top: 530px;background: green;" id="box3"></div>
  <script>
    // 获取元素
    const box1 = document.querySelector('#box1')
    const box2 = document.querySelector('#box2')
    const box3 = document.querySelector('#box3')
    // 获取坐标
    const coordinate1 = getPosition(box1, 'bottom')
    const coordinate2 = getPosition(box2, 'top')
    const coordinate3 = getPosition(box3, 'top')

    // 动态创建svg
    let svgWidth = 0
    let pointStart = 0
    // 获取svg的宽度
    let coordinateArr = [coordinate1[0], coordinate2[0], coordinate3[0]].sort((a, b) => b - a);
    // 如果开始元素宽度最大,则设为svg的宽
    if (coordinate1[0] > coordinate2[0] && coordinate1[0] > coordinate3[0]) {
      svgWidth = coordinate1[0] - 75; // 50 + 25,需要减去最左侧盒子的left+width/2
      // 第二个箭头的结束点位置
      pointStart = coordinate3[0] - 75;
    } else {
      // 用最大宽度-最小宽度
      svgWidth = coordinateArr[0] - coordinateArr[2];
      pointStart = svgWidth - 5;
    }
    const svgHeight = coordinate3[1] - coordinate1[1]
    const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    // 如果只有一个分支可以考虑设置svg的宽高直接为10,然后左上角坐标 = (start元素底部中心点横坐标 - 5, start元素底部中心点纵坐标)
    // 默认是设置宽高为10的原因在于画布需要有空间,这样箭头才能正常显示

    // 以下是针对有两个分支的情况
    // svg的顶部中心位置就是起始坐标
    // svg的左右两个角落位置就是两个结束点坐标
    const start = `${coordinate1[0] - coordinate2[0]}`
    svgEl.innerHTML = `
      <path d="M ${start} 0 L 0 ${svgHeight}" stroke="url(#grad1)" stroke-width="2" marker-end="url(#arrowhead)" />
      <path d="M ${start} 0 L ${pointStart} ${svgHeight}" stroke="url(#grad1)" stroke-width="2" marker-end="url(#arrowhead)" />
      <defs>
        <marker id="arrowhead" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
          <path d="M 0 0 L 10 5 L 0 10 z" fill="red" />
        </marker>
        <linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stop-color="#ff0000" />
          <stop offset="100%" stop-color="#00ff00" />
        </linearGradient>
      </defs>
    `
    svgEl.setAttribute('width', svgWidth)
    svgEl.setAttribute('height', svgHeight)
    svgEl.style.cssText = `background: #ddd; position: absolute;left: ${coordinate2[0]}px;top: ${coordinate1[1]}px;`
    document.body.appendChild(svgEl)


    function getPosition (el, direction) {
      const rect = el?.getBoundingClientRect()
      const x = (rect.right - rect.left) / 2 + rect.left
      const y = rect[direction]
      return [x, y]
    }
  </script>
</body>
</html>

在这里插入图片描述

注意:当点与点之前的x坐标相同,如果填充色是渐变色的话,会存在path的箭头变为0的情况。我们可以设置点的位置偏差来避免这种情况;还有部门三角形箭头被遮挡的情况,可以把点的y坐标适当减个5的值,避免这种情况

优化代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
<!--可以修改left的值-->
  <div style="width: 50px;height: 50px;position: absolute;left: 55px;top: 160px;background: red;" id="box1"></div>
  <div style="width: 50px;height: 50px;position: absolute;left: 55px;top: 530px;background: blue;" id="box2"></div>
  <div style="width: 50px;height: 50px;position: absolute;left: 925px;top: 530px;background: green;" id="box3"></div>

  <script>
    // 获取元素
    const box1 = document.querySelector('#box1')
    const box2 = document.querySelector('#box2')
    const box3 = document.querySelector('#box3')
    // 获取坐标
    const coordinate1 = getPosition(box1, 'bottom')
    const coordinate2 = getPosition(box2, 'top')
    const coordinate3 = getPosition(box3, 'top')
    console.log(coordinate1)
    console.log(coordinate2)
    console.log(coordinate3)

    // 动态创建svg
    const pad = 10 // 偏移量,避免箭头宽度为0
    let svgWidth = 0
    let pointStart = 0
    // 获取svg的宽度
    let coordinateArr = [coordinate1[0], coordinate2[0], coordinate3[0]].sort((a, b) => b - a);
    // 如果开始元素宽度最大,则设为svg的宽
    if (coordinate1[0] > coordinate2[0] && coordinate1[0] > coordinate3[0]) {
      svgWidth = coordinate1[0] - 75 + pad; // 50 + 25,需要减去最左侧盒子的left+width/2
      // 第二个箭头的结束点位置
      pointStart = coordinate3[0] - 75 + pad;
    } else {
      // 用最大宽度-最小宽度
      svgWidth = coordinateArr[0] - coordinateArr[2] + pad;
      pointStart = svgWidth - 5;
    }
    const svgHeight = coordinate3[1] - coordinate1[1]
    const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    // 如果只有一个分支可以考虑设置svg的宽高直接为10,然后左上角坐标 = (start元素底部中心点横坐标 - 5, start元素底部中心点纵坐标)
    // 默认是设置宽高为10的原因在于画布需要有空间,这样箭头才能正常显示

    // 以下是针对有两个分支的情况
    // svg的顶部中心位置就是起始坐标
    // svg的左右两个角落位置就是两个结束点坐标
    const start = `${coordinate1[0] - coordinate2[0] + 8}`
    const pointEnd = svgHeight - 5
    svgEl.innerHTML = `
      <path d="M ${start} 0 L ${pad} ${pointEnd}" stroke="url(#grad1)" stroke-width="2" marker-end="url(#arrowhead)" />
      <path d="M ${start} 0 L ${pointStart} ${pointEnd}" stroke="url(#grad1)" stroke-width="2" marker-end="url(#arrowhead)" />
      <defs>
        <marker id="arrowhead" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
          <path d="M 0 0 L 10 5 L 0 10 z" fill="red" />
        </marker>
        <linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stop-color="#ff0000" />
          <stop offset="100%" stop-color="#00ff00" />
        </linearGradient>
      </defs>
    `
    svgEl.setAttribute('width', svgWidth)
    svgEl.setAttribute('height', svgHeight)
    // 这个地方left - 10是为了避免开始点和结束点的x点坐标一样的时候,使用渐变色填充宽度消失的问题,将画布左移10px避免偏差
    svgEl.style.cssText = `background: #ddd; position: absolute;left: ${coordinateArr[2] - pad}px;top: ${coordinate1[1]}px;`
    document.body.appendChild(svgEl)


    function getPosition (el, direction) {
      const rect = el?.getBoundingClientRect()
      const x = (rect.right - rect.left) / 2 + rect.left
      const y = rect[direction]
      return [x, y]
    }
  </script>
</body>
</html>

在这里插入图片描述

注意:在创建之前需要判断当前文档是否存在svg元素,如果存在需要先删除再创建,避免存在多个svg元素

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cookie、session、token的区别是什么

前言 今天就来说说session、cookie、token这三者之间的关系&#xff01;最近这仨玩意搞得头有点大&#x1f923; 1.为什么会有它们三个&#xff1f; 我们都知道 HTTP 协议是无状态的&#xff0c;所谓的无状态就是客户端每次想要与服务端通信&#xff0c;都必须重新与服务端链接…

JUC并发编程18 | AQS分析

尚硅谷&#xff08;140-155&#xff09; 18 AQS 前置知识 公平锁和非公平锁可重入锁自旋思想LockSupport双向链表设计模式——模块设计 18.1 AQS入门级别理论知识 AQS一般指的是 AbstractQueuedSynchronized AQS 是用来实现锁或者其他同步器组件的公共基础部分的抽象实现…

【企业信息化】第3集 世界排名第一的免费开源ERP: Odoo 16 POS终端管理系统

文章目录 前言一、概览二、硬件三、使用功能 前言 世界排名第一的免费开源ERP: Odoo 16 POS终端管理系统。几分钟内完成设置&#xff0c;几秒内完成销售。 一、概览 Odoo POS 基于智能界面&#xff0c;任何零售公司均可毫不费力地使用 因为其极具灵活性&#xff0c;您可配置 …

普通的项目非分布式项目中的技术点思考(学习随记)

学习路线 在学习Java的路程中&#xff0c;最开始学习JavaSe&#xff0c;在Java基础学完后&#xff0c;开始接触JavaWeb&#xff0c;开始接触框架&#xff0c;Spring框架&#xff0c;SpringBoot框架、数据库框架、在学习一下中间件&#xff0c;就可以完成工作中crud的基础操作&…

聚观早报|谷歌:全新大模型赋能「全家桶」;阿里巴巴取消CTO职位

今日要闻&#xff1a;谷歌 I/O&#xff1a;全新大模型赋能「全家桶」&#xff1b;阿里巴巴取消CTO职位&#xff1b;马斯克打造「美国微信」&#xff1b;奔驰将召回部分进口CLA汽车&#xff1b;奔驰将召回部分进口CLA汽车 谷歌 I/O&#xff1a;全新大模型赋能「全家桶」 北京时…

大模型也内卷,Vicuna训练及推理指南,效果碾压斯坦福羊驼

2023开年以来&#xff0c;大模型进入疯狂内卷状态&#xff0c;大模型的发布都要以“天”为单位进行迭代。 之前&#xff0c;尝试了从0到1复现斯坦福羊驼&#xff08;Stanford Alpaca 7B&#xff09; &#xff0c;下面我们来尝试从0到1复现Vicuna训练及推理。 Vicuna简介 继斯坦…

AOP深度学习

代理模式 静态代理&#xff1a;静态代理确实实现了解耦&#xff0c;但是由于代码都写死了&#xff0c;完全不具备任何的灵活性。就拿日志功能来说&#xff0c;将来其他地方也需要附加日志&#xff0c;那还得再声明更多个静态代理类&#xff0c;那就产生了大量重复的代码&#…

wps js宏编辑器案例2-单元格读写-随机选人

本案例讲述某企业的一个真实案例&#xff0c;该企业每周二早上有安全宣贯会议&#xff0c;差不多10来分钟左右&#xff0c;每次安全会上人事部门都会点名&#xff0c;那么问题来了&#xff0c;点名的名单哪儿来&#xff1f;为此&#xff0c;编写了一个简单js宏应用&#xff0c;…

易基因:DNA甲基化和转录组分析揭示野生草莓干旱胁迫分子调控机制|植物抗逆

大家好&#xff0c;这里专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 干旱胁迫是对农业生产产生不利影响的关键环境因素。为此&#xff0c;植物发展出各种响应机制&#xff08;干旱逃逸、避免、耐受和回复&#xff09;&#xff0c;以通过进化增强抗旱性&#…

2022年下半年软件设计师下午试题

试题四&#xff08;共15分&#xff09; 排序是将一组无序的数据元素调整为非递减顺序的数据序列的过程&#xff0c;堆排序是一种常用的排序算法。用顺序存储结构存储堆中元素。非递减堆排序的步骤是&#xff1a; (1)将含n个元素的待排序数列构造成一个初始大顶堆&#xff0c;…

种棉12载的他,为何最终选择千耘导航?

边休息边种地&#xff0c;每天还能提升近四十亩作业量&#xff0c;是怎么做到的&#xff1f; 种地十二三年&#xff0c;为何最终选择了千耘农机导航&#xff1f; 千耘导航使用前后的工作状态究竟相差了多少&#xff1f; 让我们走进新疆阿克苏&#xff0c;听一听任师傅的“种…

【WebGIS实例】(8)MapboxGL绘制闪烁的点

官网示例&#xff1a; Add an animated icon to the map | Mapbox GL JS 实现 示例数据 const sampleData {"type": "FeatureCollection","features": [{"type": "Feature","properties": {},"geometry&q…

5G干扰排查优化方案介绍!

干扰成因 干扰源的发射信号&#xff08;阻塞信号、加性噪声信号&#xff09;从天线口被放大发射出来后&#xff0c;经过了空间损耗L&#xff0c;最后进入被干扰接收机。如果空间隔离不够的话&#xff0c;进入被干扰接收机的干扰信号强度够大&#xff0c;将会使接收机信噪比恶化…

【教程】安装VSCode-Server

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 前景提要 jupyter虽然好用&#xff0c;但他只能运行ipynb文件&#xff0c;对于py文件还是只能通过命令行来运行&#xff0c;不是很方便。 因此&#xff0c;通过安装vscode来运行py文件。而vscode-server支持像jup…

Centos-7安装步骤教程

提示&#xff1a; 鼠标移动到虚拟机内部单击或者按下Ctrl G&#xff0c;鼠标即可移入到虚拟机中&#xff0c;按下Ctrl Alt&#xff0c;鼠标即可移出虚拟机 目录 一、虚拟机的创建 1、创建新的虚拟机 2、选择典型&#xff0c;也可以自定义 3、安装程序光盘映像文件&#x…

618大促即将来临,速卖通、Lazada等平台如何快速提高排名和转化率?

速卖通每年三大促&#xff0c;328、618、双11。618马上就要来临&#xff0c;卖家朋友们都准备好了吗&#xff1f;今天陈哥就和大家聊聊怎么快速提高产品转化率。转化率是卖家在分析复盘时非常关键的因素&#xff0c;转化率的高低直接影响着卖家目前的关键词listing或者商品描述…

4.5 队列实现及其应用(上)

目录 顺序队列 创建空队列&#xff1a; 判断队列空&#xff1a; 入队&#xff1a; 队列 队列是限制在两端进行插入操作和删除操作的线性表 允许进行存入操作的一端称为“队尾” 允许进行删除操作的一端称为“队头” 当线性表中没有元素时&#xff0c;称为“空队” 特点 &am…

监控室值班人员脱岗识别算法 opencv

监控室值班人员脱岗识别算法模型通过pythonopencv网络深度学校模型技术&#xff0c;监控室值班人员脱岗识别算法模型实现企业总监控室值班人员脱岗、睡岗、玩手机等场景的AI识别&#xff0c;不需人为干预全天候自动识别。OpenCV的全称是Open Source Computer Vision Library&am…

git码云的使用-创建项目仓库-ssh协议配置步骤

目录 1、创建仓库 1.1 只填入仓库名即可-提交 1.2 本地项目上传到远程仓库 2、提交仓库 2.1 选择HTTPS协议 2.2 选择ssh协议 3、ssh协议配置步骤 3.1 打开 Git Bash 3.2 生成公钥&#xff1a;$ cd ~/.ssh &#xff08;可忽略&#xff09; 3.3 生成密钥 3.4 添加公钥…

【零基础QQ机器人开发三】程序上云篇

前言&#xff1a;本文为大家带来QQ机器人程序上云的教程&#xff0c;环境搭建请参考下面链接 【0基础QQ机器人开发】基于go-cqhttp的QQ机器人开发教程,仅供自学 【零基础QQ机器人开发二】服务器篇 文章目录 程序Logger类StatuStore类MultiFunc类QQBot类main.py 前言&#xff1a…