Skip to content

VIcon

🌟 Introduction

Le composant VIcon est un composant Vue.js permettant d'afficher des icônes avec une large gamme d'options de personnalisation, y compris des animations, des couleurs, et des tailles. Il est conçu pour être flexible et performant, avec une prise en charge des différentes options d'affichage, de flip, et de titres accessibles.

Il a exactement la même API que OhVueIcon, et utilise @iconify/vue sous le capot.

Attention

Les noms des icônes doivent être ceux de Iconify-vue.

📐 Structure

Le composant VIcon s'intègre facilement en utilisant la syntaxe suivante :

vue
<VIcon name="nom-collection:nom-de-l-icone" :scale="1.5" color="#FF5733" animation="spin" />
vue
<VIcon name="ri:alert-fill" :scale="1.5" color="#FF5733" animation="ring" />

Migration depuis 5.x

Pour les noms de collection qui ne contiennent pas de tiret (-), il est accepté de séparer le nom de la collection du nom de l’icône avec un tiret -.

vue
<VIcon name="ri-alert-fill" :scale="1.5" color="#FF5733" animation="ring" />

Ceci rend le composant VIcon totalement compatible avec OhVueIcon si n’étaient utilisées que les icônes RemixIcon et quelques autres collections.

DX

Pour l’expérience développeur, il est conseillé d’utiliser l’extension vscode antfu.iconify.

🛠️ Props

Voici les différentes propriétés que vous pouvez utiliser avec ce composant :

PropTypeDéfautDescription
namestringObligatoireLe nom de l'icône à afficher.
scalestring | number1Échelle de l'icône, avec un facteur multiplicateur de la taille par défaut.
verticalAlignstring'-0.2em'Alignement vertical de l'icône par rapport à la ligne de base.
animation'spin' | 'wrench' | 'pulse' | 'spin-pulse' | 'flash' | 'float'undefinedType d'animation appliqué à l'icône.
speed'fast' | 'slow'undefinedVitesse de l'animation si elle est définie.
flip'horizontal' | 'vertical' | 'both'undefinedInverse l'icône horizontalement, verticalement ou les deux.
labelstringundefinedÉtiquette ARIA pour l'accessibilité.
titlestringundefinedTitre de l'icône (balise <title>), utilisé pour l'accessibilité et les info-bulles.
colorstringundefinedCouleur principale de l'icône.
fillstringundefinedCouleur de remplissage de l'icône (utilise in fine color comme conseillé dans la doc de @iconify/vue). Cette prop n’existe que pour la rétrocompatibilité avec OhVueIcon, préférer l’utilisation de la prop color.
inversebooleanfalseApplique une couleur inversée à l'icône.
ssrbooleanfalseActive le rendu côté serveur (Server-Side Rendering).
display'block' | 'inline-block' | 'inline''inline-block'Définit le mode d'affichage de l'icône.

📡 Événements

Ce composant ne déclenche pas d'événements personnalisés.

🧩 Slots

Ce composant ne contient pas de slots.

⚙️ Code source du composant

vue
<script lang="ts" setup>
import VIcon from '../VIcon.vue'
</script>

<template>
  <div class="flex  justify-between p-4">
    <section>
      <header class="fr-text--lg">
        Simple :
      </header>
      <p>
        <VIcon
          name="ri-close-line"
        />
        <VIcon
          name="ri-checkbox-circle-line"
        />
      </p>
    </section>
    <section>
      <header class="fr-text--lg">
        Plus grande :
      </header>
      <p>
        <VIcon
          name="ri-close-line"
          scale="2"
        />
        <VIcon
          name="ri-checkbox-circle-line"
          scale="2"
        />
      </p>
    </section>
    <section>
      <header class="fr-text--lg">
        Animée :
      </header>
      <p>
        spin :
        <VIcon
          name="ri-loader-4-line"
          animation="spin"
        />
      </p>
      <p>
        spin-pulse :
        <VIcon
          name="ri-loader-4-line"
          animation="spin-pulse"
        />
      </p>
      <p>
        pulse :
        <VIcon
          name="ri-checkbox-circle-line"
          animation="pulse"
        />
      </p>
      <p>
        flash :
        <VIcon
          name="ri-close-line"
          animation="flash"
        />
      </p>
      <p>
        float :
        <VIcon
          name="ri-close-line"
          animation="float"
        />
      </p>
      <p>
        ring :
        <VIcon
          name="fa6-regular:bell"
          animation="ring"
        />
      </p>
      <p>
        wrench :
        <VIcon
          name="twemoji:rolling-on-the-floor-laughing"
          animation="wrench"
        />
      </p>
    </section>
    <section>
      <header class="fr-text--lg">
        Colorée :
      </header>
      <p>
        lightgreen :
        <VIcon
          name="ri-checkbox-circle-line"
          color="lightgreen"
        />
      </p>
      <p>
        #f90 :
        <VIcon
          name="ri-checkbox-circle-line"
          color="#f90"
        />
      </p>
      <p>
        var(--blue-france-main-525) :
        <VIcon
          name="ri-checkbox-circle-line"
          color="var(--blue-france-main-525)"
        />
      </p>
    </section>
  </div>
</template>
vue
<script lang="ts" setup>
import { Icon } from '@iconify/vue'
import { computed, nextTick, onMounted, ref, watch } from 'vue'

import type { VIconProps } from './VIcon.types'

export type { VIconProps }

const props = withDefaults(defineProps<VIconProps>(), {
  scale: 1,
  verticalAlign: '-0.2em',
  display: 'inline-block',
})

const icon = ref<{ $el: SVGElement } | null>(null)

const fontSize = computed(() => `${+props.scale * 1.2}rem`)
const flip = computed(() => {
  if (props.flip === 'both') {
    return 'horizontal,vertical'
  }
  return props.flip
})

watch(() => props.title, setTitle)

async function setTitle () {
  if (!(icon.value?.$el)) {
    return
  }
  const titleExists = !!(icon.value?.$el).querySelector('title')
  const titleEl = document.createElement('title')
  if (!props.title) {
    titleEl.remove()
    return
  }
  titleEl.innerHTML = props.title
  await nextTick()
  if (!titleExists) {
    (icon.value?.$el as SVGElement).firstChild?.before(titleEl)
  }
}
onMounted(setTitle)

const finalName = computed(() => {
  return props.name?.startsWith('vi-') ? props.name.replace(/vi-(.*)/, 'vscode-icons:$1') : props.name ?? ''
})
const finalColor = computed(() => {
  return props.color ?? props.fill ?? 'inherit'
})
</script>

<template>
  <Icon
    ref="icon"
    :icon="finalName"
    :style="{ fontSize, verticalAlign, display }"
    :aria-label="label"
    class="vicon"
    :class="{
      'vicon-spin': props.animation === 'spin',
      'vicon-wrench': props.animation === 'wrench',
      'vicon-pulse': props.animation === 'pulse',
      'vicon-spin-pulse': props.animation === 'spin-pulse',
      'vicon-flash': props.animation === 'flash',
      'vicon-float': props.animation === 'float',
      'vicon-ring': props.animation === 'ring',
      'vicon-slow': props.speed === 'slow',
      'vicon-fast': props.speed === 'fast',
      'vicon-inverse': props.inverse,
    }"
    :flip
    :ssr
  />
</template>

<style scoped>
.vicon {
  color: v-bind(finalColor);
}
.vicon-inverse {
  color: #fff;
}
/* ---------------- spin ---------------- */
.vicon-spin:not(.vicon-hover),
.vicon-spin.vicon-hover:hover,
.vicon-parent.vicon-hover:hover > .vicon-spin {
  animation: vicon-spin 1s linear infinite;
}

.vicon-spin:not(.vicon-hover).vicon-fast,
.vicon-spin.vicon-hover.vicon-fast:hover,
.vicon-parent.vicon-hover:hover > .vicon-spin.vicon-fast {
  animation: vicon-spin 0.7s linear infinite;
}

.vicon-spin:not(.vicon-hover).vicon-slow,
.vicon-spin.vicon-hover.vicon-slow:hover,
.vicon-parent.vicon-hover:hover > .vicon-spin.vicon-slow {
  animation: vicon-spin 2s linear infinite;
}

/* ---------------- spin-pulse ---------------- */

.vicon-spin-pulse:not(.vicon-hover),
.vicon-spin-pulse.vicon-hover:hover,
.vicon-parent.vicon-hover:hover > .vicon-spin-pulse {
  animation: vicon-spin 1s infinite steps(8);
}

.vicon-spin-pulse:not(.vicon-hover).vicon-fast,
.vicon-spin-pulse.vicon-hover.vicon-fast:hover,
.vicon-parent.vicon-hover:hover > .vicon-spin-pulse.vicon-fast {
  animation: vicon-spin 0.7s infinite steps(8);
}

.vicon-spin-pulse:not(.vicon-hover).vicon-slow,
.vicon-spin-pulse.vicon-hover.vicon-slow:hover,
.vicon-parent.vicon-hover:hover > .vicon-spin-pulse.vicon-slow {
  animation: vicon-spin 2s infinite steps(8);
}

@keyframes vicon-spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

/* ---------------- wrench ---------------- */
.vicon-wrench:not(.vicon-hover),
.vicon-wrench.vicon-hover:hover,
.vicon-parent.vicon-hover:hover > .vicon-wrench {
  animation: vicon-wrench 2.5s ease infinite;
}

.vicon-wrench:not(.vicon-hover).vicon-fast,
.vicon-wrench.vicon-hover.vicon-fast:hover,
.vicon-parent.vicon-hover:hover > .vicon-wrench.vicon-fast {
  animation: vicon-wrench 1.2s ease infinite;
}

.vicon-wrench:not(.vicon-hover).vicon-slow,
.vicon-wrench.vicon-hover.vicon-slow:hover,
.vicon-parent.vicon-hover:hover > .vicon-wrench.vicon-slow {
  animation: vicon-wrench 3.7s ease infinite;
}

@keyframes vicon-wrench {
  0% {
    transform: rotate(-12deg);
  }

  8% {
    transform: rotate(12deg);
  }

  10%, 28%, 30%, 48%, 50%, 68% {
    transform: rotate(24deg);
  }

  18%, 20%, 38%, 40%, 58%, 60% {
    transform: rotate(-24deg);
  }

  75%, 100% {
    transform: rotate(0deg);
  }
}

/* ---------------- ring ---------------- */
.vicon-ring:not(.vicon-hover),
.vicon-ring.vicon-hover:hover,
.vicon-parent.vicon-hover:hover > .vicon-ring {
  animation: vicon-ring 2s ease infinite;
}

.vicon-ring:not(.vicon-hover).vicon-fast,
.vicon-ring.vicon-hover.vicon-fast:hover,
.vicon-parent.vicon-hover:hover > .vicon-ring.vicon-fast {
  animation: vicon-ring 1s ease infinite;
}

.vicon-ring:not(.vicon-hover).vicon-slow,
.vicon-ring.vicon-hover.vicon-slow:hover,
.vicon-parent.vicon-hover:hover > .vicon-ring.vicon-slow {
  animation: vicon-ring 3s ease infinite;
}

@keyframes vicon-ring {
  0% {
    transform: rotate(-15deg);
  }

  2% {
    transform: rotate(15deg);
  }

  4%, 12% {
    transform: rotate(-18deg);
  }

  6% {
    transform: rotate(18deg);
  }

  8% {
    transform: rotate(-22deg);
  }

  10% {
    transform: rotate(22deg);
  }

  12% {
    transform: rotate(-18deg);
  }

  14% {
    transform: rotate(18deg);
  }

  16% {
    transform: rotate(-12deg);
  }

  18% {
    transform: rotate(12deg);
  }

  20%, 100% {
    transform: rotate(0deg);
  }
}

/* ---------------- pulse ---------------- */
.vicon-pulse:not(.vicon-hover),
.vicon-pulse.vicon-hover:hover,
.vicon-parent.vicon-hover:hover > .vicon-pulse {
  animation: vicon-pulse 2s linear infinite;
}

.vicon-pulse:not(.vicon-hover).vicon-fast,
.vicon-pulse.vicon-hover.vicon-fast:hover,
.vicon-parent.vicon-hover:hover > .vicon-pulse.vicon-fast {
  animation: vicon-pulse 1s linear infinite;
}

.vicon-pulse:not(.vicon-hover).vicon-slow,
.vicon-pulse.vicon-hover.vicon-slow:hover,
.vicon-parent.vicon-hover:hover > .vicon-pulse.vicon-slow {
  animation: vicon-pulse 3s linear infinite;
}

@keyframes vicon-pulse {
  0% {
    transform: scale(1.1);
  }

  50% {
    transform: scale(0.8);
  }

  100% {
    transform: scale(1.1);
  }
}

/* ---------------- flash ---------------- */
.vicon-flash:not(.vicon-hover),
.vicon-flash.vicon-hover:hover,
.vicon-parent.vicon-hover:hover > .vicon-flash {
  animation: vicon-flash 2s ease infinite;
}

.vicon-flash:not(.vicon-hover).vicon-fast,
.vicon-flash.vicon-hover.vicon-fast:hover,
.vicon-parent.vicon-hover:hover > .vicon-flash.vicon-fast {
  animation: vicon-flash 1s ease infinite;
}

.vicon-flash:not(.vicon-hover).vicon-slow,
.vicon-flash.vicon-hover.vicon-slow:hover,
.vicon-parent.vicon-hover:hover > .vicon-flash.vicon-slow {
  animation: vicon-flash 3s ease infinite;
}

@keyframes vicon-flash {
  0%, 100%, 50%{
    opacity: 1;
  }
  25%, 75%{
    opacity: 0;
  }
}

/* ---------------- float ---------------- */
.vicon-float:not(.vicon-hover),
.vicon-float.vicon-hover:hover,
.vicon-parent.vicon-hover:hover > .vicon-float {
  animation: vicon-float 2s linear infinite;
}

.vicon-float:not(.vicon-hover).vicon-fast,
.vicon-float.vicon-hover.vicon-fast:hover,
.vicon-parent.vicon-hover:hover > .vicon-float.vicon-fast {
  animation: vicon-float 1s linear infinite;
}

.vicon-float:not(.vicon-hover).vicon-slow,
.vicon-float.vicon-hover.vicon-slow:hover,
.vicon-parent.vicon-hover:hover > .vicon-float.vicon-slow {
  animation: vicon-float 3s linear infinite;
}

@keyframes vicon-float {
  0%, 100% {
    transform: translateY(-3px);
  }
  50% {
    transform: translateY(3px);
  }
}
</style>