歡迎關注[前端小謳的github],閱讀更多原創技術文章
1.頁面展現svg內容
2.監聽svg內部的點擊事件
3.動態改變svg內部元素的屬性和值javascript
經屢次實驗,用embed、img等標籤改變src屬性的方式,均沒法實現上述所有功能(尤爲是svg內部點擊事件),最終採用Vue.extend()方法完整實現,代碼也較爲簡潔,html結構以下:html
<template> <div> <div id="svgTemplate"></div> </div> </template>
直接將svg文件的內容複製粘貼到.vue文件裏,是能夠在標籤內直接添加@click事件完成需求的,方式簡單但會形成文件過長,本文很少陳述
1.建立xhr對象前端
const xhr = new XMLHttpRequest(); this.svgUrl = ...; // svg的絕對地址,在瀏覽器中打開能看到的那個 xhr.open("GET", this.svgUrl, true); xhr.send();
2.監聽xhr對象(獲取svg的dom -> 添加事件 -> 修改dom -> 轉成虛擬dom並掛載)vue
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 事件)調用java
async mounted() { window["handleClick"] = () => { this.takePhoto(); }; }, methods:{ takePhoto(){ ... } }
到這裏就基本完成需求:動態渲染了svg、用js操做dom的語法修改svg部件的屬性和值、給svg部件動態添加了事件 handleClick,最後將 takePhoto() 事件綁定給了 window 對象的 handleClick,能夠放心大膽的在 takePhoto() 裏寫你要執行的內容了!node
將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 的屬性:git
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>