Vue & Nuxt Tips
Reactive SVGs met Vue 3
Waarom Reactive SVGs gebruiken?
- Flexibiliteit: Met Vue 3 kun je bijna elk SVG-attribuut eenvoudig binden aan de gegevens van je component.
- Complexiteit: SVG's kunnen complexere vormen weergeven dan eenvoudige CSS en Vue 3's reactiviteit maakt het eenvoudig om ze te animeren.
- LOC's: Deze methode vereist minder code dan je zou verwachten, waardoor je SFC's lichter worden.
Voorbeeld uit de echte wereld: Een halfronde gestippelde statusbalk
Deze statusbalk bestaat uit twee SVG cirkels: één voor de achtergrond om het totaal aantal stappen weer te geven, en een andere om de huidige voortgang weer te geven. Vue 3 werkt de lijnarray van de voortgangscirkel dynamisch bij om de huidige voortgang of status weer te geven. De SVG zorgt voor een duidelijke en interactieve visualisatie van de voortgang met heel weinig code en logica. De SVG's zijn inline en Vue 3 controleert alle relevante gegevens. Bekijk de broncode om te begrijpen hoe het werkt.
<template>
<div class="progress">
<div class="progress__circle">
<svg :width="widthPixels" :height="heightPixels">
<circle :r="radius" :cy="centerPixel" :cx="centerPixel" :stroke-width="strokeWidth"
class="progress__circle__dashes progress__circle__dashes--background"> </circle>
<circle :r="radius" :cy="centerPixel" :cx="centerPixel" :stroke-width="strokeWidth"
class="progress__circle__dashes progress__circle__dashes--progress"> </circle>
</svg>
</div>
<div class="progress__labelslot">
<slot></slot>
</div>
</div>
</template>
<style>
.progress {
display: flex;
position: relative;
justify-content: center;
align-items: center;
}
.progress__labelslot {
position: absolute;
font-size: 2rem;
font-weight: bold;
user-select: none;
}
.progress circle {
fill: transparent;
user-select: none;
pointer-events: none;
}
/* NOTE v-bind in CSS - new in Vue 3 */
/* https://vuejs.org/api/sfc-css-features.html#v-bind-in-css */
.progress__circle {
transform: rotate(v-bind(startAngleString));
}
.progress__circle__dashes {
stroke-linecap: butt;
}
/* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray */
.progress__circle__dashes--background {
stroke-dasharray: v-bind(baseDashes);
stroke: #ecf0f444;
}
.progress__circle__dashes--progress {
stroke-dasharray: v-bind(progressDashes);
stroke: #42b883;
filter: drop-shadow(0px 0px 10px #42b883);
}
</style>
<script lang="ts" setup>
const props = defineProps({
numberOfStepsTotal: {
type: Number,
default: 5
},
currentProgress: {
type: Number,
default: 0
},
});
const widthPixels = 300;
const heightPixels = widthPixels;
const strokeWidth = 45;
const gapSize = 15;
const dashStoppingDistance = 1_000;
const startAngle = 140;
const startAngleString = `${startAngle}deg`;
const centerPixel = Math.round(widthPixels / 2);
const radius = Math.floor(widthPixels / 2 - strokeWidth);
// * 0.725 we got a semi circle
const circumference = 2 * Math.PI * radius * 0.725;
/**
* returns an array which contains
* pixel length for dashes and gaps and a long gap at the end
* @param {number} numberOfSteps - number of dashes
* @returns {number[]} [25,15,25,15,25,1000], [gap, pause, gap, pause, gap, long pause]
*/
const getBaseDashArray = (numberOfSteps: number): number[] => {
numberOfSteps = Math.max(numberOfSteps, 1);
let arr: number[] = [];
const pixelsForGaps = (numberOfSteps - 1) * gapSize;
const pixelPerDash = (circumference - pixelsForGaps) / numberOfSteps;
// -1 to push the last step after the for loop with a long dash pause
for (let i = 0; i < numberOfSteps - 1; i++) {
arr.push(pixelPerDash, gapSize);
}
arr.push(pixelPerDash, dashStoppingDistance);
return arr;
};
const baseDashes = computed<number[]>(() => {
return getBaseDashArray(props.numberOfStepsTotal);
});
// returns a slice of baseDashes
const progressDashes = computed<number[]>(() => {
let dashes: number[];
if (props.currentProgress <= 0) {
dashes = [0];
} else {
// *2 to capture every dash with it's following dash pause
// -1 to ignore the last pause - which gets replaced by the long pause
const stepsToSlice = Math.min(
props.currentProgress * 2 - 1,
props.numberOfStepsTotal * 2 - 1
);
dashes = baseDashes.value.slice(0, stepsToSlice);
}
// add the dash stopping distance like we did in getBaseDashArray
dashes.push(dashStoppingDistance);
return dashes;
});
</script>
Andere toepassingen
Deze techniek is niet beperkt tot statusbalken, maar kan worden uitgebreid naar verschillende soorten visualisaties die profiteren van reactiviteit, zoals mascotte-animaties, laadindicatoren, tutorial-pijlen/aanwijzers, educatieve animaties, gamification-assets, enz. De combinatie van Vue 3 en SVG's biedt een krachtige maar ondergewaardeerde toolset voor het maken van interactieve en visueel aantrekkelijke webcomponenten.