大致的流程是这样的:实现这个需求是首先通过一个工具(drawio)去自定义绘制图形,然后导出一个svg格式的文件,后端搞了一下drawio工具的源码,在导出的时候,为绘制的图形上每个节点都去绑定了一个id(具体是怎么搞得我一个前端也不是太清楚),最后导出,然后前端需要去加载svg,然后每个节点都会有对应的数据(就是根据绑定的id去获取),然后有些节点上还可能有图片,还需要替换,还可能为每个节点去绑定事件做一些事情。然后还使用了d3实现了让svg可以缩放拖拽。大概是这个亚子

先上一张自己测试用的svg

Vue中动态加载SVG文件并绑定事件、修改节点数据_vue.js 如果使用 img标签加载出来的就是上图的效果,仅仅是一张静态图片,这样是无法实现咱们的需求的。
当时看到类似这样的需求的时候,一脸懵,之前也没做过类似的功能,不知道从何下手。。
没办法只能去找一些相关的资源、案例、百度等等,找了好多,最终还是搞出来了。

首先说下思路:

1. 首先咱们要通过XMLHttpRequest的方式去加载svg,因为这样才能去操作svg中的dom元素

2. 接下来就是拿到里面的dom元素集合,遍历为某些节点(当然也可以为整个svg)绑定事件,注意:这里绑定的事件是在window层上,我们要向window上挂载绑定的方法

3. 然后就可以把svg挂载到容器dom上,这里注意:不能通过DOM.appendChild的方式去追加,要先转成虚拟dom并挂载,否则绑定的事件失效

4. 挂载dom上之后就能看到正常的我们加上绑定事件的svg了,这时候也在dom上了,咱们可以去获取里面的dom,循环遍历去获取每个节点的id,并找到里面对应的font标签并将其数据进行修改,除了font标签外、如果有图片还会有image标签

然后下面是完整的代码,代码注释还是蛮详细的,希望可以帮到大家

<template>
<div>
<div id="svgTemplate" ref="svg"></div>
</div>
</template>
<script>
import wftSvg from "@/assets/img/wft.svg";
import Vue from 'vue/dist/vue.esm.js'
import * as d3 from "d3"; //在vue文件里面引入d3
export default {
data() {
return {
svgDom: null,
allDom: null
};
},
created() {
this.getSvg();
},
mounted() {
// svg 点击事件
window['handleClick'] = function(e,currNodeId) {
let tag = e.srcElement || e.target;
console.log(e, '点击----->>>')
},
// svg 鼠标滚动事件
window['havcZooming'] = (e) => {
console.log(e, 'havcZooming----->>>')
this.zoomimg();
}
},
methods: {
getSvg() {
const xhr = new XMLHttpRequest();
xhr.open ("GET", wftSvg, true);
xhr.send();
xhr.addEventListener("load", () => {
const resXML = xhr.responseXML // this.stringToXml(xhr.responseXML)
this.svgDom = resXML.documentElement.cloneNode(true); // 克隆节点
// console.log(this.svgDom, '----->>>')
// 为 svg - dom 设置宽高边框样式
this.svgDom.style.width = "100%";
this.svgDom.style.height = "80vh";
this.svgDom.style.border = "1px solid yellow";
// 为svg添加鼠标滚动缩放事件
this.svgDom.setAttribute("v-on:mousewheel", "this.havcZooming($event)");
// svg - a
let adomNodeAll = this.svgDom.getElementsByTagName("a");
// 循环修改节点样式 添加事件
for(let i = 0; i < adomNodeAll.length; i++) {
adomNodeAll[i].style.cursor = 'pointer' // 修改节点样式
let currNodeId = adomNodeAll[i].getAttribute('id')
adomNodeAll[i].setAttribute("v-on:click", "this.handleClick($event, '"+ currNodeId +"')"); // 为每个节点绑定点击事件
let currNode = this.svgDom.getElementById(currNodeId )
}
// 设置 id 属性
let gtag = this.svgDom.getElementsByTagName("g");
gtag[0].setAttribute("id", "svgcanvas");
/* 将svgDom对象转换成vue的虚拟dom */
var oSerializer = new XMLSerializer();
var sXML = oSerializer.serializeToString(this.svgDom);
var Profile = Vue.extend({
template: "<div>" + sXML + '</div>'
});
// 创建实例,并挂载到元素上
new Profile().$mount('#svgTemplate');
// document.getElementById('svgTemplate').appendChild(this.svgDom)
// svg - g
let svgcanvasDom = document.getElementById("svgcanvas");
this.allDom = svgcanvasDom.getElementsByTagName("a");
for(let i = 0; i < this.allDom.length; i++) {
// console.log(this.allDom[i].childNodes,'childNodes');
let curraNodeId = this.allDom[i].id // 当前a标签节点的id,我们可以根据id获取对应节点数据并将其渲染对应的font标签上
// console.log(curraNodeId, '当前a标签下的节点id----->>>')
if(this.allDom[i].childNodes.length) { // 如果a标签下还有子元素
// 修改节点数据(a 标签里面有 font 标签 即节点的数据)
let fontDom = this.allDom[i].getElementsByTagName("font");
if(fontDom.length) {
// 可以根据 curraNodeId 获取该节点对应的数据并渲染到font标签上
fontDom [0].innerHTML = 'self'
}

// 修改图片(a 标签中有 image 标签 , 即节点图片)
let imageDom = this.allDom[i].getElementsByTagName("image");
if(imageDom.length) {
imageDom[0].attributes['xlink:href'].value = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAADUElEQVQ4jY2VzW8bRRiHn9kZ7/o7thPbbVGT8NUIQSDtja+ey4EiJFSJSnBBoP4LqBd6QhyQuFW5cUKCAwgu7V/AASkUVDWQFiq3gbYhdrCdxPs5M2gXg8ohZN/L7L6jffTO+/72N8JaS564dPEiFW/DdrtdavU6SZygtUUIwWg85NbNdWK7IlQuGtDvDxjbHicebfH8i08z2hmxs9UnSiyjwRa/3d1EVY6TGyiVhw0trhLMtZtIY/B3HmCMRSkH1y3ilhs4eYGN5jKIEmEUE4UxxgoMkrRlQRggC3VmWyv5gZX6C0wmitFoTBgESOkgpYu2DqPRgCSpUy6fRImvX89HXIJ31gRxWp3WIARCOBgjiKMYbRwQ5f9UuABcBu4A4XS9PM1nYW2EcJzsmFiNMSk8xqJIkj5R9MO/wDPAdeACMA+40/XCNH/m7KcPbLniY4WTkkFYtA6JIx/pCOYaksnwS5tOeRH4HKgdcNg0/8UTKy3mmVB0BUkcopSHEG7WyyOdOnNNhxs/Xctk8z5QP6SDtdunupyvHENIRWwsjhXUGm1UsUqr3UFYn+0/dzPgK3lmsuZannvpXSwGaywCSa1ts36qgkK6mrX1rQzYzQPcivZQ7hHAADod0UO7MstZqzLgFnD8MOAskp+/W8VY8zdCuVidQtJnhUkCere+z4BXgPcOAz65s8+3N69grINyXBCSKA4QQKlUJYp8fr93PwN+CLz5P1NOY/eZ69u1XuKQaloVPDy3iDapFjVuweD7Y4JQZTrsAefSjw6CpfvD3szoxsaQPwYBnaNHWXrqBJ12m0QX+KW3zY/rmwi1cOkfYV8FloFVYBOIpuvqNH/1s7fKDWPKPL44y9lXT3P65WVOLc9z8tlFqhWHMCrSfeTcBw/b153pn3FgtJsl5lplZuoVUq8pepLZRoV2q8R2E2q1x1D2ta8Om0cWH3/0DRveJ5lLR1FMQTqZF6bvUMAk9wj8a/kNtqJuQ6GAUi46FbbVGSzFeZ6H60Iw+TW/H+6Ot3GjPjMztQzq+wGJtpkGq9UyjmPZ2x/nrzC7lKymWCpQb1TRsZ8WjCckUgqSJEJYm7/CzrE3CPdb7N0fM7w7IByE6DHEg4igP8STS8wvvM1fZW5twKdzQs0AAAAASUVORK5CYII='
}

// 设置支路的移动动画 (有 getTotalLength 即为线节点)
if(this.allDom[i].childNodes[0].getTotalLength) {
// 使不同长度线路动画速度一致
let length = this.allDom[i].childNodes[0].getTotalLength()
let duration = length / 50
let animationString = duration.toFixed(0) + "s " + "linear infinite hacvRun";
// console.log(length, "length");
this.allDom[i].childNodes[0].style.strokeDashoffset = length;
this.allDom[i].childNodes[0].style.animation = animationString;
}
}
}
});
},
zoomimg(x, y) {
// 放大缩小
// 缩放事件绑定给svg,缩放结果设置给svg内部的g标签
if (!x) {
x = 0;
}
if (!y) {
y = 0 ;
}
const svg = d3.select("svg");
const g = d3.select("#svgcanvas");
// console.log(svg, g, "in havcZooming");
//节点的缩放
function zoomActions() {
// console.log(d3.event, '----->>>') // undefind
// g.attr("transform", d3.event.transform);
g.attr("transform", d3.zoomTransform(svg.node()));
}
let zoomHandler = d3.zoom().on("zoom", zoomActions).scaleExtent([0.5, 40]);

// zoomHandler(svg)
svg.call(zoomHandler);
svg.transition().duration(750).call(zoomHandler.transform, d3.zoomIdentity.translate(-x, -y).scale(2));
// // 点击按钮定位
// d3.select("#reset").on("click", function () {
// svg
// .transition()
// .duration(750)
// .call(zoomHandler.transform, d3.zoomIdentity);
// });
// d3.select('#pos1').on('click',function(){
// svg.transition().duration(750).call(zoomHandler.transform, d3.zoomIdentity.translate(-10,-1500).scale(2));
// });
// d3.select('#pos2').on('click',function(){
// svg.transition().duration(750).call(zoomHandler.transform, d3.zoomIdentity.translate(-1200,-10).scale(2));
// });
},
stringToXml(xmlString) {
let xmlDoc = null
if (typeof xmlString == "string") {
//FF
if (document.implementation.createDocument) {
var parser = new DOMParser();
xmlDoc = parser.parseFromString(xmlString, "text/xml");
} else if (window.ActiveXObject) {
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.loadXML(xmlString);
}
}else {
xmlDoc = xmlString;
}
return xmlDoc;
}
}
};
</script>
<style>
@keyframes dash {
to {
stroke-dashoffset: 0;
}
}

@keyframes run {
from {
stroke-dasharray: 10, 5;
}
to {
stroke-dasharray: 40, 5;
}
}

@keyframes hacvRun {
from {
stroke-width: 6;
/* stroke-dashoffset: 0; */
stroke-dasharray: 10, 8;
}
to {
stroke-width: 6;
stroke-dashoffset: 0;
stroke-dasharray: 10, 8;
}
}
</style>

最后是搞完之后的效果:

Vue中动态加载SVG文件并绑定事件、修改节点数据_d3_02

补充:

上面代码通过

var Profile = Vue.extend({
template: "<div>" + sXML + '</div>'
});
// 创建实例,并挂载到元素上
new Profile().$mount('#svgTemplate');

这个方式去挂载,如果使用错误警告或者有问题可以看下我上篇博文:

​【解决】You are using the runtime-only build of Vue where the template compiler is not available​

上面使用了d3,记得npm 装一下呢

npm install d3