Reputation: 364
I need to load an SVG file into a Vue template. It must be loaded in such a way that I can access the internal classes with js and css, so presumably I'm looking for an <object>
tag and not an <img>
tag.
The SVG is located on an external server, not a part of my project. Vue-Svg-Loader works just fine if I have the svg as part of my project, but doesn't seem to work when the SVG isn't available until runtime.
I've tried the following
<template>
<div ref="floorplan" id="floorplan-frame">
<object
type="image/svg+xml"
id="floorplan"
:data="svgPath"
></object>
</div>
</template>
<script>
export default {
data() {
return {
svgPath: 'http://test-mc4/floorplan.svg',
};
},
};
</script>
Unfortunately it doesn't work. If I replace the <object>
tag with <img :src="svgPath" />
it does show the SVG, but as a static image where the internal css classes are not available. It does show me that my path is correct and the file is actually available, but it doesn't explain why the object tag is just empty when I use it.
I've searched extensively and I can figure out how to load it as an Object if it's internal, or how to load it External as long as it's an image. I just can't seem to figure out how to do both.
Upvotes: 2
Views: 2271
Reputation: 2099
In order to access the elements within an <object>
, you'd need to wait until it was loaded (load
event) and then access the child SVG document by its contentDocument
property.
Unfortunately, this won't work in your case because the SVG files are coming from a different origin. The same-origin policy will block access to the contentDocument
. Here is an example, which also fails (logs null
) because a data:
URL is a different origin:
const svgPath = 'data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDIwIDIwIj4NCiAgICA8Y2lyY2xlIGN4PSIxMCIgY3k9IjEwIiByPSIxMCIgZmlsbD0icmVkIi8+DQo8L3N2Zz4=';
const app = new Vue({
el: '#app',
data: {
svgPath,
},
methods: {
svgLoaded() {
setTimeout(() => {
console.log(this.$refs.object.contentDocument);
}, 1000);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<object
ref="object"
:data="svgPath"
width="100"
height="100"
v-on:load="svgLoaded"
></object>
</div>
The only way to load an SVG from a different origin and then access its internal structure with your JS and CSS would be to fetch
it and then load it into your component as v-html
. Note that this opens significant XSS vulnerabilities; you should be wary of this option and only use it with external servers you trust. In any case, here's a working example:
const svgPath = 'data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDIwIDIwIj4NCiAgICA8Y2lyY2xlIGN4PSIxMCIgY3k9IjEwIiByPSIxMCIgZmlsbD0icmVkIi8+DQo8L3N2Zz4=';
const app = new Vue({
el: '#app',
data: {
svgData: '',
},
async mounted() {
const svgResponse = await fetch(svgPath);
this.svgData = await svgResponse.text();
await Vue.nextTick();
// SVG is present in the DOM at this point
const svg = this.$refs.drawing.firstElementChild;
console.log(svg.outerHTML);
// DOM manipulations can be performed
const circle = svg.querySelector('circle');
circle.setAttribute('fill', 'blue');
circle.setAttribute('r', '6');
}
});
.drawing {
width: 100px;
height: 100px;
}
/* SVG can be styled as part of the main document */
.drawing circle {
stroke: cyan;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div
class="drawing"
ref="drawing"
v-html="svgData"
></div>
</div>
Upvotes: 4