歡迎關注[前端小謳的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>
複製代碼