Instantly share code, notes, and snippets.

Embed
What would you like to do?
SVG Icon Vue Component
<template>
<div class="inline-block" v-html="require('icon-' + this.icon + '.svg')"></div>
</template>
<style module>
.svg {
fill: currentColor;
height: 1em;
margin-top: -4px;
vertical-align: middle;
width: 1em;
}
</style>
<script>
export default {
props: ['icon'],
mounted() {
this.$el.firstChild.classList.add(this.$style.svg)
this.$el.firstChild.removeAttribute('height')
this.$el.firstChild.removeAttribute('width')
}
}
</script>
// Here is the custom code for my webpack.mix.js file:
// Exclude svg icons
Mix.listen('configReady', function (config) {
const rules = config.module.rules;
const targetRegex = /(\.(png|jpe?g|gif)$|^((?!font).)*\.svg$)/;
for (let rule of rules) {
if (rule.test.toString() == targetRegex.toString()) {
rule.exclude = /\.svg$/;
break;
}
}
});
// Use a custom loader for inline icons
mix.webpackConfig({
module: {
rules: [{
test: /\.svg$/,
use: [{
loader: 'html-loader',
options: {
minimize: true
}
}]
}]
},
resolve: {
modules: [ path.resolve(__dirname, 'resources/assets/svg') ]
}
});
@fgilio

This comment has been minimized.

fgilio commented Feb 25, 2018

Awesome, thanks!
The link to the post in case someone finds this first: http://calebporzio.com/using-inline-svgs-in-vue-compoments/

@beijer

This comment has been minimized.

beijer commented Apr 14, 2018

Awesome!

A tip though, if you change your require to require('!!html-loader!./../../svg/' + this.icon + '.svg') (notice the !! in the beggining) then you can skip the whole webpack configuration.

@blueoctopuswebdesigns

This comment has been minimized.

blueoctopuswebdesigns commented Jun 30, 2018

I ended up shrinking it a bit thanks to @beijer.
Also changed the javascript to specifically select svg due to some of my svg files having comments at the top of the file.

<template>
    
      <div v-html="require('!!html-loader!./../../svg/zondicons/' + this.icon + '.svg')"></div>

</template>

<script>
export default {
    props: ['icon','addClass'],
    mounted() {
        this.$el.querySelector("svg").setAttribute("class", this.addClass)
    }
}
</script>

and to implement inside your vue component

<svg-icon 
       icon="fa-address-book"
       add-class="w14px h14px f-grey-light">
 </svg-icon>

Should output

<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg" class="w14px h14px f-grey-light">
<path d="M1092 644q0 107-76.5 183t-183.5 76-183.5-76-76.5-183q0-108 76.5-184t183.5-76 183.5 76 76.5 184zm-48 220q46 0 82.5 17t60 47.5 39.5 67 24 81 11.5 82.5 3.5 79q0 67-39.5 118.5t-105.5 51.5h-576q-66 0-105.5-51.5t-39.5-118.5q0-48 4.5-93.5t18.5-98.5 36.5-91.5 63-64.5 93.5-26h5q7 4 32 19.5t35.5 21 33 17 37 16 35 9 39.5 4.5 39.5-4.5 35-9 37-16 33-17 35.5-21 32-19.5zm684-256q0 13-9.5 22.5t-22.5 9.5h-96v128h96q13 0 22.5 9.5t9.5 22.5v192q0 13-9.5 22.5t-22.5 9.5h-96v128h96q13 0 22.5 9.5t9.5 22.5v192q0 13-9.5 22.5t-22.5 9.5h-96v224q0 66-47 113t-113 47h-1216q-66 0-113-47t-47-113v-1472q0-66 47-113t113-47h1216q66 0 113 47t47 113v224h96q13 0 22.5 9.5t9.5 22.5v192zm-256 1024v-1472q0-13-9.5-22.5t-22.5-9.5h-1216q-13 0-22.5 9.5t-9.5 22.5v1472q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5-9.5t9.5-22.5z"/></svg>

This is so useful because you can now see what the svg instead of having preview it in the browser

@macku99

This comment has been minimized.

macku99 commented Jun 30, 2018

Thank you guys for these useful examples. @blueoctopuswebdesigns you can simplify your usage a tad more.

...
mounted() {
    this.$el.removeAttribute('class');
    this.$el.firstChild.classList.add(...this.$vnode.data.staticClass.split(' '));
}
...

<svg-icon class="fill-current w-4 h-4" icon="fa-address-book" />

Hope this helps!

@pawelmysior

This comment has been minimized.

pawelmysior commented Aug 7, 2018

Thanks for this gist and great comments guys.

I also wanted to remove the container div and just render a <svg> tag. Here's a neat little trick to achieve that: this.$el.outerHTML = this.$el.innerHTML :)

I also added a v-once directive, because it's a static component. Here's the code I'm using:

<template>
    <div v-html="require('!!html-loader!./../../../svg/zondicons/' + this.icon + '.svg')" v-once></div>
</template>

<script>
    export default {
        props: ['icon'],
        mounted() {
            this.$el.firstChild.classList.add(...this.$el.className.split(' '));
            this.$el.firstChild.classList.add('fill-current');
            this.$el.outerHTML = this.$el.innerHTML;
        },
    };
</script>

Quick note: I'm using Zondicons that don't have the height and width attributes, that's why I don't have the lines that remove those attributes.

@bivainis

This comment has been minimized.

bivainis commented Sep 20, 2018

@calebporzio great article and nice solution!

A few things I've changed:

  • removed fill: currentColor; because in my case Material Icons have svg with nested paths, where fill is set instead. In this case, I added fill: currentColor to relevant paths in that svg.
  • instead of modifying vue.config.js (I'm working on vue-cli based project where webpack files aren't exposed), I've changed my require param to require(`!!html-loader!@/assets/icons/${icon}-icon.svg`), as mentioned by @beijer. This allows me to only use html-loader for icons, and leave img tags with svg sources alone.
  • I'm getting a Vue warning ([Vue warn]: Error in mounted hook: "TypeError: Cannot read property 'add' of undefined") when using firstChild, therefore changed svg selection to this.$el.querySelector('svg')
  • I've set <svg> style to display: block, which removes invisible margins, and svg icon fills parent element fully, and therefore there's no need to mess with alignment.
  • Added dynamic style prop to div wrapper: :style="{'font-size': size + 'px'}"

Hope this helps someone.

Btw, in case anyone wants to modify vue.config.js in order to configure webpack to use html-loader for svg files on vue-cli based project, instead of using in require('!!html-loader!...):

// vue.config.js
module.exports = {
  chainWebpack: (config) => {
    const svgRule = config.module.rule('svg');
    svgRule.uses.clear();
    svgRule
      .use('html-loader')
      .loader('html-loader')
      .options({
        minimize: true,
      });
  },
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment