Fermer

juin 12, 2025

Un composant circulaire SVG pur

Un composant circulaire SVG pur


La création d’un composant SVG dynamique est plus facile que jamais dans votre cadre de choix. Après avoir maîtrisé quelques techniques de base, tout ce que vous pouvez imaginer peut être réalisé.

TL; Dr

Cet article vous apprend à créer un composant circulaire de progression SVG qui peut être utilisé dans des composants personnalisés comme les jeux, les dénombrements de personnages et les graphiques. Cet exemple utilise Vue et Nuxt, mais il pourrait être facilement modifié pour fonctionner avec n’importe quel cadre JS. Il utilise également le vent arrière, mais CSS standard fonctionne également.

image.png

Le composant réutilisable central

Nous permettons à l’utilisateur de personnaliser les dimensions des composants, de mettre un objet HTML à l’intérieur et de le maintenir réactif.

<script setup lang="ts">
const {
  size = 150,
  width = 20,
  trailColor = "gray",
  strokeColor = "black",
  progress,
} = defineProps<{
  size?: number
  width?: number
  trailColor?: string
  strokeColor?: string
  progress: number | string
}>();

const cy = size / 2
const r = cy - width / 2
const circumference = 2 * Math.PI * r

const dashOffset = computed(() => circumference * (1 - Number(progress) / 100))
</script>

<template>
  <svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size">
    <circle
      :cx="cy"
      :cy
      :r
      :stroke="trailColor"
      fill="none"
      :stroke-width="width"
    />
    <circle
      :cx="cy"
      :cy
      :r
      fill="none"
      :stroke="strokeColor"
      :transform="`rotate(-90 ${cy} ${cy})`"
      :stroke-dasharray="circumference"
      :stroke-dashoffset="dashOffset"
      :stroke-width="width"
    />
    <foreignObject :x="0" :y="0" :width="size" :height="size">
      <div class="size-full flex items-center justify-center">
        <slot />
      </div>
    </foreignObject>
  </svg>
</template>

  • Taille – la taille SVG
  • Largeur – La largeur de course SVG
  • Couleur du sentier – la couleur du cercle
  • Couleur de trait – la couleur de progression
  • Progrès – Le numéro de progrès (ou la chaîne) entre 1 et 100

Objet étranger

La bonne façon de mettre HTML à l’intérieur de SVG est d’utiliser un objet étranger. Pour rendre notre alignement correct, nous devons garder notre largeur et notre hauteur full en utilisant le vent arrière ou le CSS, mais cela pourrait être fait manuellement en dehors du composant.

Sélecteur de progrès

chrome_bett7aye7r.gif

Ce composant vous permet de sélectionner les progrès entre 1 et 100 avec un sélecteur de plage.

Composant de plage

Nous avons besoin de CSS personnalisés pour styliser le composant de la plage, car ce n’est pas standard en utilisant le vent arrière.

<script setup lang="ts">
const progress = useProgress()
</script>

<template>
  <div class="flex items-center justify-center text-center gap-5 mt-10">
    <label class="block mb-2 text-md font-medium text-gray-900" for="progress">
      Progress
    </label>
    <input
      v-model="progress"
      class="h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
      type="range"
      name="progress"
      min="0"
      max="100"
      step="5"
    >
  </div>
</template>

<style scoped>
input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  height: 1rem;
  width: 1rem;
  background-color: black;
  border-radius: 9999px;
  cursor: pointer;
}

input[type="range"]::-moz-range-thumb {
  height: 1rem;
  width: 1rem;
  background-color: black;
  border-radius: 9999px;
  cursor: pointer;
}
</style>

Progrès partagé

Nuxt a une fonctionnalité utile appelée useStatece qui vous permet de partager l’état entre les composants sans forage des accessoires. Vous pouvez partager manuellement l’état entre les composants à l’aide de fournisseurs, d’injecteurs ou de contexte, selon votre cadre.

**export const useProgress = () => {
    return useState('progress', () => 90);
};**

Avec ce code, nous pouvons utiliser useProgress dans n’importe quel composant, et l’état est partagé sur le composant.

Sélecteur de progrès

Nous utilisons le useProgress accrochez-vous et passez-le à notre <svg-circle /> Composant, ainsi que à l’intérieur de la fente pour afficher le pourcentage.

<script setup lang="ts">
const progress = useProgress()
</script>

<template>
  <div class="flex items-center justify-center text-center mt-10">
    <svg-circle :progress>
      <h1 class="text-2xl font-bold">{{ progress }}%</h1>
    </svg-circle>
  </div>
  <range-input />
</template>

Nous affichons le <range-input /> composant et signal de progression à l’intérieur du cercle SVG. Fonctionne comme un charme.

Note: Nous devons garder les articles dans flex-center pour un bon alignement.

Compteur de mots

chrome_wfx80bbewm.gif

Nous définissons une limite de caractères, calculons les caractères à gauche et affichons la progression en fonction de ce numéro. Avec notre composant Core SVG, cela devient extrêmement facile.

<script setup lang="ts">
const CHARACTER_LIMIT = 280

const content = ref("")

const progress = computed(
  () => ((content.value.length / CHARACTER_LIMIT) * 100) | 0
)

const display_characters = computed(
  () => CHARACTER_LIMIT - content.value.length
)

const color = computed(() =>
  display_characters.value < 0 ? "#dc2626" : "#1d4ed8"
)
</script>

<template>
  <div class="mx-10 mt-10">
    <label
      for="message"
      class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
      >Your message</label
    >
    <textarea
      id="message"
      v-model="content"
      rows="4"
      class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500"
      placeholder="Write here to see counter...."
    />
    <div class="flex items-center mt-5 gap-5">
      <svg-circle
        :progress
        :width="5"
        :size="50"
        :stroke-color="color"
        trail-color="#9ca3af"
      >
        {{ display_characters }}
      </svg-circle>
      <p>Characters Left</p>
    </div>
  </div>
</template>

Nous suivons le même protocole de passage le progress au <svg-circle /> composant et affichage des caractères à l’intérieur de l’emplacement. Cela doit également être un signal pour mettre à jour correctement le DOM.

Cercle de jargon

chrome_zwkq2tp17f.gif

Le bouton gamifié, similaire à Duolingo, est vraiment une question de style approprié.

<script setup lang="ts">
const progress = ref(0)

const changePercent = () => {
  const tmp = progress.value + 20
  progress.value = tmp > 100 ? 0 : tmp
}
</script>

<template>
  <div class="flex flex-col items-center justify-center my-10 gap-5">
    <h1 class="font-bold">Lingo</h1>
    <div class="mx-50">
      <svg-circle
        :progress
        trail-color="#e5e7eb"
        stroke-color="#4ade80"
        :size="100"
        :width="10"
      >
        <button
          type="button"
          class="inline-flex rounded-full items-center justify-center whitespace-nowrap text-sm font-bold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 uppercase tracking-wide bg-green-500 text-primary-foreground hover:bg-green-500/90 border-green-600 active:border-b-0 h-[70px] w-[70px] border-b-8 fill-primary-foreground text-primary-foreground"
          @click="changePercent"
        >
          <CrownSvg />
        </button>
      </svg-circle>
    </div>
  </div>
</template>

Avec notre <svg-circle /> Personnalisable, cela facilite la création de n’importe quel composant. Nous pouvons calculer nos progrès, qui doivent être un signal, et le transmettre au composant Circle comme d’habitude. Au lieu d’utiliser un nombre à l’intérieur de notre emplacement de composant, nous utilisons simplement un bouton stylé avec un onclick poignée.

Le reste est à la hauteur de votre imagination!

Démo: Vercel
Repo: Girub


Encore plus facile? Découvrez les progrès de l’interface utilisateur de Kendo pour Vue ProgressBarl’un des plus de 110 composants construits professionnellement avec l’accessibilité cuite.




Source link