-
-
Notifications
You must be signed in to change notification settings - Fork 7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature Request] Iconify Support #7821
Comments
This looks like it would cause the same problems as FontAwesome: #3505 (comment) We have support for svg icons so you can already import only what's being used, albeit manually. |
Iconify is open source, so maybe a custom implementation that actually would work for Vuetify with the use of their API? And then also adding a Vuetify option to change the API URL (if someone wanted to host their own iconify) |
Author of Iconify here. I'd love to make Iconify work with Vue and solve any issues Vue users might have. Looks like main issue is that Iconify messes with DOM. However it doesn't really need to. It messes with DOM only if it sees icon placeholder, such as But there are other ways to handle icons. Iconify script has many functions that can be used to render SVG without Iconify messing with DOM. Iconify exposes Using those functions you can retrieve full SVG code and render it instead of using placeholder. For example let icon1 = Iconify.getSVG('mdi:home');
let icon2 = Iconify.getSVG('fa-regular:home', {'data-rotate': '90deg'});
// ... render those icons That's the basic version. However in reality its a bit more complex than that because icons need to be loaded first. Usually Iconify looks for icon placeholders, then requests icon data from API (or preloads them from localStorage if they were requested on previous page load), only then replaces placeholders with SVG. So rendering is asynchronous. Iconify exposes functions to help with that:
With combination of those functions and event, you can build your own code to load icons, be notified when icons have loaded and render them. Another solution is to bundle icons with package instead of loading icons from API. I've built React component that works like that: https://github.com/iconify/iconify-react so something like that might be built for Vue too. I'm open to any suggestions and ideas. My goal is to build universal framework for icons and make it available on as many platforms as possible, so designers and users would have huge choice of icons on whatever platform they choose, including Vue. |
Thanks @cyberalien I think I might try to make a super basic Vue component (as a separate project) to see how viable it truly is using the functions you provided, if I get that working hopefully whatever I create can be ported for Vuetify (or just be used with Vuetify) |
So I've got it working as basic component. Since I've spent only 1 day with Vue, this component is far from optimal and possibly has some bad coding, but it does work. Requires Iconify 1.0.3 that has been released today. It fixes bug that prevented import from working properly and adds new function getSVGObject() that instead of rendering SVG returns attributes and content, so it could be used to generate component. Component: <script>
import Iconify from '@iconify/iconify';
// This is for debug - disables caching, so on each reload new API request would be sent
Iconify.setConfig('localStorage', false);
Iconify.setConfig('sessionStorage', false);
Iconify.setConfig('defaultAPI', 'https://api.iconify.design/{prefix}.js?t=' + Date.now() + '&icons={icons}');
/**
* List of component properties to map to data- attributes
*
* @type {Array}
*/
const dataAttributes = ['inline', 'width', 'height', 'rotate', 'flip', 'align'];
/**
* Array of components to update when icon has been loaded
*
* @type {Array}
*/
let listeners = [];
/**
* Listen to IconifyAddedIcons that is fired whenever new icons are loaded from API
*/
document.addEventListener('IconifyAddedIcons', function() {
listeners = listeners.filter(item => {
if (Iconify.iconExists(item.icon)) {
item.instance.iconLoaded();
return false;
}
return true;
});
});
/**
* Export component
*/
export default {
name: 'Icon',
render: function(createElement) {
// Check if icon exists, render span if not
if (!Iconify.iconExists(this.name)) {
return createElement('span', {
attrs: {
style: 'display: inline-block; width: 1em;'
}
});
}
// Convert component properties to Iconify properties
let props = {};
if (this.color !== void 0) {
props.style = 'color: ' + this.color + ';';
}
// All optional properties
dataAttributes.forEach(key => {
if (this[key] !== void 0) {
props['data-' + key] = this[key];
}
});
// Get SVG attributes and body
let icon = Iconify.getSVGObject(this.name, props);
return createElement('svg', {
attrs: icon.attributes,
domProps: {
innerHTML: icon.body
}
});
},
props: {
name: {
type: String,
required: true
},
// If one dimension is missing, it will be generated using width/height ratio of icon.
// By default height is '1em', width is calculated from icon's width/height ratio.
width: String,
height: String,
// If true, icon will have vertical alignment, so it renders similar to glyph font.
// If false, icon will not have vertical alignment, so it renders as image above text baseline.
// Use false for decorations, true when converting from legacy font code.
// Default value is true
inline: Boolean,
// Color string, optional. If missing, currentColor is used
color: String,
// Rotation. Values are '90deg', '180deg', '270deg'. Rotation is done by rotating SVG content, not CSS rotation.
rotate: String,
// Flip. Values are 'horizontal', 'vertical' or 'horizontal,vertical' (last one is identical to 180deg rotation)
flip: String,
// Alignment. Used only if setting custom width and height that do not match icon's width/height ratio.
// Value is comma separated list of alignments:
// Horizontal: left, center, right
// Vertical: top, middle, bottom
// Crop: slice, meet
align: String
},
beforeMount: function() {
// Status of icon loading. false = not loading, string = icon name
this._loadingIcon = false;
this.loadIcon();
},
beforeUpdate: function() {
// Try to load different icon if name property was changed
this.loadIcon();
},
methods: {
/**
* Load icon from API
*/
loadIcon: function() {
if (this._loadingIcon !== this.name && !Iconify.iconExists(this.name)) {
if (this._loadingIcon !== false) {
// Already loading with different icon name - remove component with old icon name from listeners list
this.removeListener();
}
// Add to queue
this._loadingIcon = this.name;
listeners.push({
instance: this,
icon: this._loadingIcon
});
// Add to Iconify loading queue
// Iconify will execute queue on next tick, so its safe to add icons one by one
Iconify.preloadImages([this.name]);
}
},
/**
* Remove component from Iconify event listener
*/
removeListener: function() {
listeners = listeners.filter(item => item.instance !== this);
},
/**
* Icon has loaded. Force component update
*/
iconLoaded: function() {
this._loadingIcon = false;
this.$forceUpdate();
}
},
beforeDestroy: function() {
if (this._loadingIcon !== false) {
this.removeListener();
}
}
}
</script> Test app, based on default App.vue: <template>
<v-app>
<v-content>
<div style="font-size: 24px; box-shadow: 0 0 2px #ccc">Few icons from same collection to test Iconify loading queue: <Icon name="uil-bug" /> <Icon name="uil-cloud-slash" /> <Icon name="uil-exclude" /></div>
<div style="font-size: 24px; box-shadow: 0 0 2px #ccc">Icon: <Icon v-bind="icon" /></div>
icon: <a v-on:click="icon.name = 'ant-design:home-outline'">ant-design:home-outline</a>, <a v-on:click="icon.name = 'mdi-home'">mdi-home</a>, <a v-on:click="icon.name = 'fa-solid:home'">fa-solid:home</a>, <a v-on:click="icon.name = 'icomoon-free:home'">icomoon-free:home</a>, <a v-on:click="icon.name = 'dashicons:admin-home'">dashicons:admin-home</a>, <a v-on:click="icon.name = 'flat-color-icons:home'">flat-color-icons:home</a>, <a v-on:click="icon.name = 'octicon-home'">octicon-home</a>, <a v-on:click="icon.name = 'uil-home'">uil-home</a><br />
inline: <a v-on:click="icon.inline = true">inline</a> (has vertical alignment, like glyph font), <a v-on:click="icon.inline = false">block</a> (no vertical alignment, like image)<br />
color: <a v-on:click="icon.color = 'red'">red</a>, <a v-on:click="icon.color = '#4a4'">green</a><br />
rotate: <a v-on:click="icon.rotate = '0'">0deg</a>, <a v-on:click="icon.rotate = '90deg'">90deg</a>, <a v-on:click="icon.rotate = '180deg'">180deg</a>, <a v-on:click="icon.rotate = '270deg'">270deg</a><br />
flip: <a v-on:click="icon.flip = ''">none</a>, <a v-on:click="icon.flip = 'horizontal'">horizontal</a>, <a v-on:click="icon.flip = 'vertical'">vertical</a>, <a v-on:click="icon.flip = 'horizontal,vertical'">both</a><br />
</v-content>
</v-app>
</template>
<script>
import Icon from './components/Icon';
export default {
name: 'App',
components: {
Icon
},
data: function() {
return {
icon: {
name: 'mdi-home',
color: '',
inline: true,
rotate: '',
flip: ''
}
};
}
}
</script> |
This is something we could consider adding, but it's probably more suitable as a standalone vue component that we can offer integration with like we do |
@cyberalien I've taken your code and converted it into a full proper Vue plugin, I still need to write the proper documentation and some test for it but I can confirm it works properly. You can find it here I think this may be the best way to get it implemented for Vue in general. |
Adds support for iconify closes #7821
Adds support for iconify closes #7821
Forgot about this issue, got reminded after receiving notification about issue being closed. A while ago I've created Vue components for Iconify: Vue 2 documentation: https://docs.iconify.design/implementations/vue2/ |
I've created this little handy helper for Vuetify 3 custom icon set: import { h } from "vue";
import type { IconSet, IconProps, IconAliases } from "vuetify";
import { Icon } from "@iconify/vue";
export const iconify: (set: string) => IconSet = (set) => ({
component: (props: IconProps) =>
h(Icon, {
icon: `${set}:${props.icon}`,
disabled: props.disabled,
})
}); Which can be used as: createVuetify({
icons: {
sets: {
flags: iconify('flags')
}
}
}) And in template as: <template>
<v-icon>flag:nl-4x3</v-icon>
</template> Also using the amazing https://marketplace.visualstudio.com/items?itemName=antfu.iconify&ssr=false#qna vscode extension which also gives intellisense with this syntax: |
@ThaDaVos, Excellent! I'm working with a Vuetify theme that has ton |
I'm testing this solution for adding iconify mdi set as a vuetify default one, so I don't need to import material CSS: import { h } from "vue";
import type { IconSet, IconProps } from 'vuetify';
import { aliases } from 'vuetify/iconsets/mdi';
import { Icon } from "@iconify/vue";
// in case of using nuxt-icon:
// import { Icon } from '#components';
const iconifyMdi: IconSet = {
component: (props: IconProps) => h(
'i',
{},
[
h(Icon, {
name: props.icon.replace('mdi-', 'mdi:'),
disabled: props.disabled,
}),
]),
};
createVuetify({
icons: {
defaultSet: 'iconifyMdi',
aliases,
sets: {
iconifyMdi,
},
},
}) Seems to work just fine |
I recommend using |
Problem to solve
Currently, the icons supported require loading the full CSS for icons, although this certainly works it adds quite a bit of bloat to the loading times and on very slow mobile networks can cause serious performance impacts. Generally speaking this the way everyone has always loaded icons and I say that continue to do so. However, we also need an alternative that allows loading the SVG icons.
Proposed solution
I am proposing support for https://iconify.design/ this would allow for potentially much faster load times (only load the icons on the page) and it would allow for the use of many different font packs without loading in any extra CSS or any bloat. The JS that runs it is also very small and in my own testing results in much better icon load times.
I propose that we continue to use the
icon=""
prop. Maybe in the same way we currently handle Material Design icons and font awesome we could use something likeicon="iconify-mdi:google"
whereiconify-
informs the vuetify icon render to use the iconify format. and themdi:google
gets passed to thedata-icon=""
part of the iconify format.Another option would be to completely replace the current icon system and completely replace it with iconify as iconify already includes the Google Material Design Icons (whats currently default) as well as font-awesome, MDI and many more. Plus it could potentially actually decrease the amount of code logic required to create the icon HTML.
The text was updated successfully, but these errors were encountered: