Die Verwaltung einer benutzerdefinierten Icon-Sammlung in einer Vue-App kann manchmal eine Herausforderung sein. Eine Icon-Schriftart ist einfach zu verwenden, aber für die Anpassung müssen Sie sich auf Drittanbieter-Font-Generatoren verlassen, und Merge-Konflikte können schmerzhaft zu lösen sein, da Schriftarten Binärdateien sind.
Die Verwendung von SVG-Dateien anstelle dessen kann diese Schmerzpunkte beseitigen, aber wie können wir sicherstellen, dass sie genauso einfach zu verwenden sind und es gleichzeitig einfach ist, Icons hinzuzufügen oder zu entfernen?
Hier ist, wie mein ideales Icon-System aussieht
- Um Icons hinzuzufügen, legen Sie sie einfach in einen dafür vorgesehenen
icons-Ordner. Wenn Sie ein Icon nicht mehr benötigen, löschen Sie es einfach. - Um das rocket.svg Icon in einer Vorlage zu verwenden, ist die Syntax so einfach wie
<svg-icon icon="rocket" />. - Die Icons können mit den CSS-Eigenschaften
font-sizeundcolorskaliert und eingefärbt werden (genau wie bei einer Icon-Schriftart). - Wenn mehrere Instanzen desselben Icons auf der Seite erscheinen, wird der SVG-Code nicht jedes Mal dupliziert.
- Keine Bearbeitung der Webpack-Konfiguration erforderlich.
Dies werden wir aufbauen, indem wir zwei kleine, einzelne Komponenten schreiben. Es gibt ein paar spezifische Anforderungen für diese Implementierung, obwohl ich sicher bin, dass viele von Ihnen Magiern da draußen dieses System für andere Frameworks und Build-Tools umarbeiten könnten
- webpack: Wenn Sie Vue CLI verwendet haben, um Ihre App zu erstellen, dann verwenden Sie bereits webpack.
- svg-inline-loader: Dies ermöglicht es uns, den gesamten SVG-Code zu laden und Teile zu bereinigen, die wir nicht wollen. Führen Sie
npm install svg-inline-loader --save-devim Terminal aus, um zu beginnen.
Die SVG-Sprite-Komponente
Um unsere Anforderung zu erfüllen, SVG-Code nicht für jede Instanz eines Icons auf der Seite zu wiederholen, müssen wir ein SVG "Sprite" erstellen. Wenn Sie noch nie von einem SVG-Sprite gehört haben, stellen Sie es sich als ein verstecktes SVG vor, das andere SVGs beherbergt. Überall, wo wir ein Icon anzeigen müssen, können wir es aus dem Sprite kopieren, indem wir die ID des Icons in einem <use>-Tag referenzieren, wie hier
<svg><use xlink:href="#rocket" /></svg>
Dieser kleine Codeausschnitt ist im Wesentlichen, wie unsere <SvgIcon>-Komponente funktionieren wird, aber lassen Sie uns zuerst die <SvgSprite>-Komponente erstellen. Hier ist die gesamte SvgSprite.vue-Datei; ein Teil davon mag auf den ersten Blick einschüchternd wirken, aber ich werde alles aufschlüsseln.
<!-- SvgSprite.vue -->
<template>
<svg width="0" height="0" style="display: none;" v-html="$options.svgSprite" />
</template>
<script>
const svgContext = require.context(
'!svg-inline-loader?' +
'removeTags=true' + // remove title tags, etc.
'&removeSVGTagAttrs=true' + // enable removing attributes
'&removingTagAttrs=fill' + // remove fill attributes
'!@/assets/icons', // search this directory
true, // search subdirectories
/\w+\.svg$/i // only include SVG files
)
const symbols = svgContext.keys().map(path => {
// get SVG file content
const content = svgContext(path)
// extract icon id from filename
const id = path.replace(/^\.\/(.*)\.\w+$/, '$1')
// replace svg tags with symbol tags and id attribute
return content.replace('<svg', `<symbol id="${id}"`).replace('svg>', 'symbol>')
})
export default {
name: 'SvgSprite',
svgSprite: symbols.join('\n'), // concatenate all symbols into $options.svgSprite
}
</script>
Im Template hat unser einziges <svg>-Element seinen Inhalt an $options.svgSprite gebunden. Falls Sie mit $options nicht vertraut sind, enthält es Eigenschaften, die direkt an unsere Vue-Komponente angehängt sind. Wir hätten svgSprite an die data unserer Komponente anhängen können, aber wir brauchen Vue nicht wirklich, um Reaktivität dafür einzurichten, da unser SVG-Loader nur beim Erstellen unserer App ausgeführt wird.
In unserem Skript verwenden wir require.context, um alle unsere SVG-Dateien abzurufen und sie dabei zu bereinigen. Wir rufen svg-inline-loader auf und übergeben ihm mehrere Parameter mit einer Syntax, die den Query-String-Parametern sehr ähnlich ist. Ich habe diese zur besseren Verständlichkeit in mehrere Zeilen aufgeteilt.
const svgContext = require.context(
'!svg-inline-loader?' +
'removeTags=true' + // remove title tags, etc.
'&removeSVGTagAttrs=true' + // enable removing attributes
'&removingTagAttrs=fill' + // remove fill attributes
'!@/assets/icons', // search this directory
true, // search subdirectories
/\w+\.svg$/i // only include SVG files
)
Was wir hier im Grunde tun, ist, die SVG-Dateien zu bereinigen, die sich in einem bestimmten Verzeichnis (/assets/icons) befinden, damit sie in einem guten Zustand sind, um sie überall dort zu verwenden, wo wir sie brauchen.
Der Parameter removeTags entfernt Tags, die wir für unsere Icons nicht benötigen, wie z.B. title und style. Insbesondere möchten wir title-Tags entfernen, da diese unerwünschte Tooltips verursachen können. Wenn Sie hartkodierte Stile in Ihren Icons beibehalten möchten, fügen Sie removingTags=title als zusätzlichen Parameter hinzu, damit nur title-Tags entfernt werden.
Wir weisen unseren Loader auch an, fill-Attribute zu entfernen, damit wir unsere eigenen fill-Farben später mit CSS festlegen können. Es ist möglich, dass Sie Ihre fill-Farben beibehalten möchten. In diesem Fall entfernen Sie einfach die Parameter removeSVGTagAttrs und removingTagAttrs.
Der letzte Loader-Parameter ist der Pfad zu unserem SVG-Icon-Ordner. Wir geben require.context dann zwei weitere Parameter mit, damit es Unterverzeichnisse durchsucht und nur SVG-Dateien lädt.
Um alle unsere SVG-Elemente in unser SVG-Sprite zu verschachteln, müssen wir sie von <svg>-Elementen in SVG <symbol>-Elemente umwandeln. Dies ist so einfach wie das Ändern des Tags und das Geben jedem eine eindeutige id, die wir aus dem Dateinamen extrahieren.
const symbols = svgContext.keys().map(path => {
// extract icon id from filename
const id = path.replace(/^\.\/(.*)\.\w+$/, '$1')
// get SVG file content
const content = svgContext(path)
// replace svg tags with symbol tags and id attribute
return content.replace('<svg', `<symbol id="${id}"`).replace('svg>', 'symbol>')
})
Was machen wir mit dieser <SvgSprite>-Komponente? Wir platzieren sie auf unserer Seite vor allen Icons, die von ihr abhängen. Ich empfehle, sie ganz oben in der App.vue-Datei hinzuzufügen.
<!-- App.vue -->
<template>
<div id="app">
<svg-sprite />
<!-- ... -->
Die Icon-Komponente
Lassen Sie uns nun die SvgIcon.vue-Komponente erstellen.
<!-- SvgIcon.vue -->
<template>
<svg class="icon" :class="{ 'icon-spin': spin }">
<use :xlink:href="`#${icon}`" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
icon: {
type: String,
required: true,
},
spin: {
type: Boolean,
default: false,
},
},
}
</script>
<style>
svg.icon {
fill: currentColor;
height: 1em;
margin-bottom: 0.125em;
vertical-align: middle;
width: 1em;
}
svg.icon-spin {
animation: icon-spin 2s infinite linear;
}
@keyframes icon-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
</style>
Diese Komponente ist viel einfacher. Wie bereits erwähnt, nutzen wir das <use>-Tag, um eine ID in unserem Sprite zu referenzieren. Diese id stammt von der icon-Prop unserer Komponente.
Ich habe dort eine spin-Prop hinzugefügt, die eine .icon-spin-Klasse umschaltet, als optionales Animationselement, falls wir es jemals brauchen sollten. Dies könnte zum Beispiel für ein Lade-Spinner-Icon nützlich sein.
<svg-icon v-if="isLoading" icon="spinner" spin />
Je nach Ihren Bedürfnissen möchten Sie vielleicht zusätzliche Props hinzufügen, wie z.B. rotate oder flip. Sie könnten die Klassen auch einfach direkt an die Komponente anheften, ohne Props zu verwenden, wenn Sie möchten.
Der größte Teil des Inhalts unserer Komponente ist CSS. Abgesehen von der Dreh-Animation wird der meiste davon verwendet, um unser SVG-Icon wie eine Icon-Schriftart wirken zu lassen¹. Um die Icons an die Textgrundlinie auszurichten, habe ich festgestellt, dass die Anwendung von vertical-align: middle zusammen mit einem unteren Rand von 0.125em in den meisten Fällen funktioniert. Wir setzen auch den Wert des fill-Attributs auf currentColor, was es uns ermöglicht, das Icon wie Text einzufärben.
<p style="font-size: 2em; color: red;">
<svg-icon icon="exclamation-circle" /><!-- This icon will be 2em and red. -->
Error!
</p>
Das ist alles! Wenn Sie die Icon-Komponente überall in Ihrer App verwenden möchten, ohne sie in jede Komponente importieren zu müssen, die sie benötigt, stellen Sie sicher, dass Sie die Komponente in Ihrer main.js-Datei registrieren.
// main.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon.vue'
Vue.component('svg-icon', SvgIcon)
// ...

Abschließende Gedanken
Hier sind ein paar Ideen für Verbesserungen, die ich absichtlich weggelassen habe, um diese Lösung zugänglich zu halten
- Skalieren Sie Icons mit nicht-quadratischen Abmessungen, um ihre Proportionen beizubehalten
- Injizieren Sie das SVG-Sprite auf die Seite, ohne eine zusätzliche Komponente zu benötigen.
- Lassen Sie es mit vite funktionieren, einem neuen, schnellen (und webpack-freien) Build-Tool von Vue-Erfinder Evan You.
- Nutzen Sie die Vue 3 Composition API.
Wenn Sie diese Komponenten schnell ausprobieren möchten, habe ich eine Demo-App erstellt, die auf der Standard-Vue-CLI-Vorlage basiert. Ich hoffe, dies hilft Ihnen bei der Entwicklung einer Implementierung, die den Bedürfnissen Ihrer App entspricht!
¹ Wenn Sie sich fragen, warum wir SVG verwenden, obwohl es sich wie eine Icon-Schriftart verhalten soll, dann schauen Sie sich den klassischen Beitrag an, der die beiden gegeneinander ausspielt.
Gibt es einen Grund,
rotate(359deg)anstelle von360degin der Animation icon-spin zu verwenden?Wenn Sie dem Icon eine Drehung von
360deggeben, ist es am Ende im Grunde wieder bei0deg, sodass es den ersten Keyframe jeder nachfolgenden Schleife nicht drehend verbringt.Wir hatten etwas Ähnliches, mussten aber wieder auf Icon-Fonts zurückgreifen. Es scheint, dass das Rendern von SVGs im Jahr 2020 auf Handys immer noch ein Performance-Problem darstellt, insbesondere auf Safari. Wenn Sie Icons wiederholt anzeigen müssen, führt dies zu einem ruckeligen Scroll-Erlebnis. Die Verwendung von SVG-Sprites hilft nicht, denn im Hintergrund werden immer noch die Knoten des Quell-SVGs geklont und neu erstellt. Traurige neue Welt, in der wir manchmal leben…
Danke für den Hinweis! Wir haben dort, wo wir das hier bei mir auf der Arbeit verwendet haben, keine Scroll-Lags gesehen oder davon gehört (zumindest noch nicht). Wir rendern wahrscheinlich nicht genug Icons gleichzeitig, um den Performance-Einbruch zu sehen, aber gut zu wissen.
Es ist der 29. Juli, fünf Tage nach Ihrem Beitrag, und ich habe das wirklich, wirklich gebraucht. :) Ich arbeite an einer App, die Electron, Vue, Ionic und Cordova/Capacitor verwendet, um eine plattformübergreifende App zu erstellen. Ionic's SVG-Icons funktionierten auf iOS oder Android nicht. Mit Ihren Komponenten und der Bearbeitung von stroke:#000 aus ihren SVGs habe ich jetzt funktionierende Icons.
Danke!
Danke für einen großartigen und zeitnahen Artikel @kevinleedrum! Falls Sie es noch nicht getan haben, schauen Sie sich SVG Spritemap Webpack Plugin für die Generierung von Sprite-Sheets an.
Kevin, es sieht so aus, als ob vue-svg-loader nicht mehr gepflegt wird. Sie schlagen eine Kombination aus raw-loader und image-minimizer-webpack-plugin vor. Mich würde nur interessieren, ob Sie (oder jemand anderes) das schon ausprobiert haben.
Danke für einen prägnanten, nützlichen Artikel.
Könnten Sie bitte eine Demo mit "Injizieren Sie das SVG-Sprite auf die Seite, ohne eine zusätzliche Komponente zu benötigen." teilen?
Es scheint, als würde Webpack 5
svg-inline-loadernicht mehr unterstützen. Ich versuche herauszufinden, wie ich die gleiche SVG-Bereinigung mit anderen Loadern erreichen kann, habe aber keinen Erfolg!