Vue & Nuxt Tipps

Vue.js Logo

Reaktive SVGs mit Vue 3

Warum Reactive SVGs verwenden?

  • Flexibilität: Mit Vue 3 können Sie fast jedes SVG-Attribut einfach an die Daten Ihrer Komponente binden.
  • Complexity: SVGs können komplexere Formen darstellen als einfaches CSS und die Reaktivität von Vue 3 macht es einfach, sie zu animieren.
  • LOCs: Diese Methode erfordert weniger Code als Sie vielleicht erwarten, was Ihre SFCs leichter macht.

Real-World-Beispiel: Eine halbkreisförmig gestrichelte Statusleiste

Diese Statusleiste besteht aus zwei SVG-Kreisen: einer für den Hintergrund, um die Gesamtzahl der Schritte anzuzeigen, und ein weiterer, um den aktuellen Fortschritt anzuzeigen. Vue 3 aktualisiert dynamisch das Linienfeld des Fortschrittskreises, um den aktuellen Fortschritt oder Status wiederzugeben. Das SVG bietet eine klare und interaktive Visualisierung des Fortschritts mit sehr wenig Code und Logik. Die SVGs sind inlined und Vue 3 steuert alle relevanten Daten. Sehen Sie sich den Quellcode an, um zu verstehen, wie es funktioniert.

2 / 10
<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 Verwendungen

Diese Technik ist nicht auf Statusleisten beschränkt, sondern kann auf verschiedene Arten von Visualisierungen ausgeweitet werden, die von Reaktivität profitieren, wie Maskottchen-Animationen, Ladeanzeigen, Tutorial-Pfeile/Zeiger, Bildungsanimationen, Gamification-Assets usw. Die Kombination von Vue 3 und SVGs bietet ein mächtiges, aber unterschätztes Toolset für die Erstellung interaktiver und visuell ansprechender Webkomponenten.

Es gibt bestimmte Pakete, die ich in fast jedem Projekt verwende. Hier sind einige davon:Ebenfalls sehr nützliche Pakete:
  • Nuxt i18n - Internationalisierung für Nuxt-Projekte.
  • Vite PWA - Zero-Config PWA-Plugin für Nuxt 3.
  • Vuefire - Vue-Plugin um Firebase-Daten an Vue-Komponenten zu binden.
  • Nuxt gtag - Google Tag Integration mit Unterstützung für Google Analytics 4, Google Ads und mehr.
  • Ionic - Ionic ist ein vollständiges Open-Source-SDK für die Entwicklung hybrider mobiler Apps.

Wie kann ich Ihnen bei Ihrem Projekt helfen?