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

注意: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.mfbz.cn/a/19820.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT学习-如何向ChatGPT提问

​ 最近在学习chatGPT,怎么样的提问是一个好的提问。通过网上找资料肯定不是最好的方法&#xff0c;我想起一句话&#xff0c;“不识庐山真面目&#xff0c;只缘身在此山中”。最好的老师就是chatGPT&#xff01; 下面先展示下提问成果&#xff0c;我通过xmind生成了思维导图 一…

科思转债上市价格预测

科思转债 基本信息 转债名称&#xff1a;科思转债&#xff0c;评级&#xff1a;AA-&#xff0c;发行规模&#xff1a;7.249178亿元。 正股名称&#xff1a;科思股份&#xff0c;今日收盘价&#xff1a;67.1元&#xff0c;转股价格&#xff1a;53.03元。 当前转股价值 转债面值…

电脑断电文件丢失如何找回?给你支几招!

电脑断电文件丢失如何找回&#xff1f;我好不容易熬夜加班做的活动方案&#xff0c;正当将U盘文件转移到笔记本电脑的时候&#xff0c;没有注意笔记本的电量&#xff0c;在转移数据的过程中突然断电了。我的电脑一下子就“熄”了&#xff0c;方案都没来得及保存。这真是一个悲剧…

MySQL主从复制与读写分离

目录 一、mysql主从复制原理1.1 mysql的复制类型1.2 mysql主从复制的工作原理 二、mysql读写分离原理2.1 读写分离的意义2.2 常见的两种mysql读写分离2.2.1.基于程序代码内部实现2.2.2.基于中间代理层实现2.2.3 amoeba 2.3 mysql读写分离原理 三、mysql数据库四种同步方式3.1 异…

MySQL视图详解

我写本文主要目的&#xff0c;是在网上看见了 所以&#xff0c;文本主要探讨的问题如下&#xff0c;验证结果在最后面 一、修改视图&#xff0c;基表会跟着改吗&#xff1f;答案&#xff1a;会改变 二、修改基表&#xff0c;视图会变化吗&#xff1f;答案&#xff1a;会改变 …

Nevron Open Vision for .NET 2022.3 Crack

Nevron Open Vision for .NET 适用于 Blazor、WPF、WinForms 和 Xamarin.Mac 的领先用户界面组件 Nevron Open Vision for .NET 是一套高级 UI 组件&#xff0c;可帮助您从单个代码库开发功能丰富的 Web &#xff08;Blazor WebAssembly&#xff09; 和桌面 &#xff08;WinFor…

手搓GPT系列之 - 通过理解LSTM的反向传播过程,理解LSTM解决梯度消失的原理 - 逐条解释LSTM创始论文全部推导公式,配超多图帮助理解(上篇)

1. 前言 说起RNN和LSTM&#xff0c;就绕不过Sepp Hochreiter 1997年的开山大作 Long Short-term Memory。奈何这篇文章写的实在是太劝退&#xff0c;整篇论文就2张图&#xff0c;网上很多介绍LSTM的文章都对这个模型反向传播的部分避重就轻&#xff0c;更少见&#xff08;反正…

照片尺寸怎么调整大小?三个方法,高效、快捷、安全!

照片尺寸怎么调整大小&#xff1f;照片是我们在日常生活和办公中经常会使用的文件类型之一。在制作各种文件、讲义、PPT、视频等内容时&#xff0c;图片都会成为重要的一部分。不同的图片格式和大小各有特点&#xff0c;有些图片虽然比较大但画质清晰&#xff0c;有些则方便传输…

网络安全公司Dragos披露网络安全事件

工业网络安全公司 Dragos 披露了它所称的“网络安全事件”&#xff0c;此前一个已知的网络犯罪团伙试图突破其防御并渗透到内部网络以加密设备。 虽然 Dragos 表示威胁行为者没有破坏其网络或网络安全平台&#xff0c;但他们可以访问公司的 SharePoint 云服务和合同管理系统。…

Windows系统下Chromedriver.exe安装及配置

Windows系统下Chromedriver.exe安装及配置 在利用selenium工具进行Web自动化测试时&#xff0c;必须先要安装浏览器驱动&#xff0c;通常比较常用的是谷歌浏览器和火狐浏览器。 一、浏览器驱动下载地址 1.浏览器驱动官网&#xff1a;http://chromedriver.storage.googleapis…

【Midjourney】Midjourney 的 Prompt 指令类型 ( 画风指令 | 人物细节指令 | 灯光镜头指令 | 艺术家风格指令 )

文章目录 一、Midjourney 的 Prompt 详细指令规则二、Midjourney 的画风指令关键词1、超现实主义2、注重细节描写3、Artstation 画风4、数字绘画风格5、漫画风格6、线条艺术 三、Midjourney 的人物细节描写关键词1、面部特征描写2、身体描写3、生成示例 14、生成示例 2 四、Mid…

(MIT6.045)自动机、可计算性和复杂性-DFA和NFA

毕业论文写完了。找点事干干。 佛系更新。 这是一门讲述 什么是计算&#xff1f;什么能被计算&#xff1f;怎么高效计算&#xff1f; 的哲学、数学和工程问题的课程。 主要包括&#xff1a; 有限状态机&#xff08;Finite Avtomata&#xff09;&#xff1a;简单的模型。 可…

在Linux中进行Jenkins部署(maven-3.9.1+jdk11)

Jenkins部署在公网IP为x.x.x.x的服务器上 maven-3.9.1要安装在jdk11环境中 环境准备 第一步&#xff0c;下载jdk-11.0.19_linux-x64_bin.tar.gz安装包。 登录地址&#xff1a;Java Downloads | Oracle 下载jdk-11.0.19_linux-x64_bin.tar.gz安装包&#xff0c;然后使用Win…

C++11

目录 一、C11的诞生 二、initializer_list 1.统一的初始化方案 2.initializer_list 三、五个关键字 1.auto 2.decltype 3.nullptr 4.final 5.override 四、STL的新容器 1.array 2.forward_list 3.unordered_map与unordered_set 4.新增成员函数 五、右值引用和移…

OPNET Modeler 例程——ALOHA和CSMA的性能对比

文章目录 概述一、创建 ALOHA 协议模型二、创建 CSMA 协议模型三、创建收信机进程和节点模型四、创建总线型链路模型五、创建网络模型六、查看仿真结果总结 概述 本例程以以太网为例论述总线型网络的建模方法&#xff0c;对数据链路层的 MAC 技术进行建模分析&#xff0c;并进…

VRRP高级特性——管理VRRP

目录 管理VRRP备份组与业务VRRP备份组 管理VRRP备份组的两种实现方式 配置管理备份组 当在设备上配置了多个VRRP备份组时&#xff0c;为了减少设备间交互大量的VRRP协议报文&#xff0c;可以将其中一个VRRP备份组配置为管理VRRP备份组&#xff08;mVRRP&#xff09;&#xf…

sort命令 uniq命令 tr命令 cut命令

sort命令 ——以行为单位对文件内容进行排序&#xff0c;也可以根据不同的数据类型来排序 比较原则是从首字符向后&#xff0c;依次按ASCII码值进行比较&#xff0c;最后将他们按升序输出 语法格式&#xff1a; sort [选项] 参数 cat file | sort 选项 -n按照数字进行排序…

【JAVA】黑马程序员JAVA教程笔记 基础篇 Day 1

常用命令行DOS命令 Path环境变量 用途 1. 可以理解为系统中的一个大管家&#xff0c;记录了很多软件的完整路径。 2. 将常用的软件都交给Path环境变量&#xff0c;便于用命令行打开。 设置步骤 复制要使用的软件的存储地址右键点击 此电脑&#xff0c;打开属性点击 高级系统…

x509证书中的Issuer和Subject

在x509标准中的位置 Issuer 颁发者字段标识已签署和颁发证书的实体。 颁发者字段必须包含一个非空的可分辨名称 (DN)。 颁发者字段定义为 X.501 类型名称 [X.501]。 名称由以下 ASN.1 结构定义&#xff1a; Name 描述了一个由属性组成的分层名称&#xff0c;例如国家名称&…

多目标优化算法求解无人机三维路径规划

一、无人机模型 无人机三维路径规划是无人机在执行任务过程中的非常关键的环节&#xff0c;无人机三维路径规划的主要目的是在满足任务需求和自主飞行约束的基础上&#xff0c;计算出发点和目标点之间的最佳航路。 1.1路径成本 无人机三维路径规划的首要目标是寻找起飞点和目…
最新文章