欢迎关注[前端小讴的github],阅读更多原创技术文章javascript
经屡次实验,用embed、img等标签改变src属性的方式,均没法实现上述所有功能(尤为是svg内部点击事件),最终采用**Vue.extend()**方法完整实现,代码也较为简洁,html结构以下:html
<template>
<div>
<div id="svgTemplate"></div>
</div>
</template>
复制代码
直接将svg文件的内容复制粘贴到.vue文件里,是能够在标签内直接添加@click事件完成需求的,方式简单但会形成文件过长,本文很少陈述前端
1.建立xhr对象vue
const xhr = new XMLHttpRequest();
this.svgUrl = ...; // svg的绝对地址,在浏览器中打开能看到的那个
xhr.open("GET", this.svgUrl, true);
xhr.send();
复制代码
2.监听xhr对象(获取svg的dom -> 添加事件 -> 修改dom -> 转成虚拟dom并挂载)java
xhr.addEventListener("load", () => {
// ① 获取svg的dom
const resXML = xhr.responseXML;
this.svgDom = resXML.documentElement.cloneNode(true); // console.log(this.svgDom);
// ② 添加click事件
let btn = this.svgDom.getElementById("...");
btn.setAttribute("v-on:click", "this.handleClick()");
// ↑↑↑ 此处注意:原生事件handleClick此时在window层,解决办法见后文
// ③ 修改 dom
this.svgDom.getElementById("...").childNodes[0].nodeValue = ...
this.svgDom.getElementById("...").setAttribute("style",
`....; fill:${this.photoResult.resultColor}; ...`);
// ↑↑↑ 用js操做dom的语法,动态设置svg部件的属性和值
// ④ 将svgDom对象转换成vue的虚拟dom,建立实例并挂载到元素上
var oSerializer = new XMLSerializer();
var sXML = oSerializer.serializeToString(this.svgDom);
var Profile = Vue.extend({
template: "<div id='svgTemplate'>" + sXML + "</div>"
});
new Profile().$mount("#svgTemplate");
});
复制代码
3.将methods里要执行的事件绑定到window下面,供外部(刚添加的 handleClick 事件)调用node
async mounted() {
window["handleClick"] = () => {
this.takePhoto();
};
},
methods:{
takePhoto(){ ... }
}
复制代码
到这里就基本完成需求:动态渲染了svg、用js操做dom的语法修改svg部件的属性和值、给svg部件动态添加了事件 handleClick,最后将 takePhoto() 事件绑定给了 window 对象的 handleClick,能够放心大胆的在 takePhoto() 里写你要执行的内容了!git
给svg的dom部件添加事件时: 1.经屡次尝试,只有 setAttribute + v-on:click 写法有效 2.setAttribute 不支持 @click(非原生事件),会报语法错误 3.addEventListener 和 onclick 均会被 vue 拦截github
将svgDom对象转换成vue的虚拟dom时: 1.若是报错以下浏览器
import Vue from "vue"
改成
import Vue from "vue/dist/vue.esm.js"
其缘由及其余解决办法本文不作探讨可自行百度。 2.vue.extend() 方法是 vue 的一个构造器,用来动态建立 vue 实例,
template 组件模板只能有一个根元素 3.$mount 手动挂载到 id 为 svgTemplate的 元素上,
挂载后将替换本来的dom(替换本来的
<div id="svgTemplate"></div>
)。因为每次更新 svg 都要从新挂载,没有找到 dom 元素是没法挂载的,所以
template 里面最外层的 div 也要加上 id 的属性:
var Profile = Vue.extend({
template: "<div id='svgTemplate'>" + sXML + "</div>"
// ↑↑↑ 最外层的 id 不能省略,不然首次渲染后找不到 #svgTemplate
});
new Profile().$mount("#svgTemplate");
// ↑↑↑ 本来的 #svgTemplate 将被替换成 Profile 的 template
复制代码
<template>
<div>
<div id="svgTemplate"></div>
</div>
</template>
复制代码
<script>
import Vue from "vue/dist/vue.esm.js";
// window.handleClick = () => {
// 本来的 handleClick 事件是 window 的
// };
export default {
name: "svg-drawing",
data() {
return {
/* 全局 */
svgUrl: "", // svg的url
svgDom: null, // 获取到的svg元素
/* svg的变量 */
photoResult: {
resultVal: 0, // 测试结果 - 值
resultMsg: "未检测", // 测试结果 - 字段
resultColor: "#dcdee2" // 测试结果 - 字段背景色
}
};
},
async mounted() {
// 将takePhoto方法绑定到window下面,提供给外部调用
window["handleClick"] = () => {
this.takePhoto();
};
},
created() {
this.getSvg();
},
methods: {
// 初始化svg
getSvg() {
/* 建立xhr对象 */
const xhr = new XMLHttpRequest();
this.svgUrl = this.baseUrl + "/svgs/" + "test.svg";
xhr.open("GET", this.svgUrl, true);
xhr.send();
/* 监听xhr对象 */
xhr.addEventListener("load", () => {
/* 1. 获取 dom */
const resXML = xhr.responseXML;
this.svgDom = resXML.documentElement.cloneNode(true);
/* 2.SVG对象添加click事件 */
let btnTakePhotoDom = this.svgDom.getElementById("...");
btnTakePhotoDom.setAttribute("v-on:click", "this.handleClick()");
/* 3. 修改 dom */
this.svgDom.getElementById("...").childNodes[0].nodeValue = ...;
this.svgDom.getElementById("...").setAttribute("style",
`....; fill:${this.photoResult.resultColor}; ...`);
/* 4.将svgDom对象转换成vue的虚拟dom */
var oSerializer = new XMLSerializer();
var sXML = oSerializer.serializeToString(this.svgDom);
var Profile = Vue.extend({
template: "<div id='svgTemplate'>" + sXML + "</div>"
});
// 建立实例,并挂载到元素上
new Profile().$mount("#svgTemplate");
});
},
// 事件
takePhoto() { ... },
},
beforeDestroy() {
this.svgDom = null;
},
watch: {
photoResult: {
handler(newVal, oldVal) {
this.getSvg();
},
deep: true
}
}
};
</script>
复制代码