Like Heart
Un bouton cœur qui déclenche au clic trois animations simultanées en CSS : le cœur jaillit avec un dépassement élastique, un anneau se propage, et huit particules colorées s'écartent en étoile.
Mis à jour le
Cet effet est gratuit — la catégorie Hover en contient 48 au total, dont 5 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →
Exemples d'usage
Érable du Japon — Acer palmatum
Arbre ornemental à feuilles caduques palmées à 5–9 lobes. Feuillage rouge vif à l'automne. Résistant en zones 5–8. Éviter les sols gorgés d'eau.
Comment ça marche
La racine .fx-0361-root (250 px de haut, fond rgb(26, 26, 36)) centre une colonne formée du bouton et du compteur. Le bouton .mi-heart-btn est un cercle de 80 px. Il contient le SVG du cœur, huit span.mi-heart-particle absolus (chacun positionné à 50 % / 50 % pour partir du centre) et un pseudo-élément ::after pour l'anneau. Toutes les animations sont déclenchées par la présence de la classe .liked sur le bouton, sans JavaScript en dehors du toggle de cette classe.
Trois animations CSS s'enchaînent au clic. @keyframes miHeartPop (0,4 s) monte le cœur à 1,3× à 30 %, le retasse à 0,9× à 60 %, puis revient à l'unité avec une cubic-bezier(0.17, 0.89, 0.32, 1.49) qui donne le rebond. @keyframes miHeartRing (0,6 s) gonfle le ::after de 0,8× à 1,4× en fondu-sortant. @keyframes miParticleBurst (0,6 s) propulse chaque span le long d'un axe calculé par la propriété personnalisée --angle (0°, 45°, 90°… 315°) : chaque particule part du centre du bouton grâce à translate(-50%, -50%) de base. Le compteur adjacent passe au rouge dès que .liked est présent (sélecteur CSS ~ .mi-heart-count).
Le script trouve chaque .fx-0361-root de la page, ajoute un écouteur click sur son bouton et gère aria-pressed : « false » par défaut, « true » quand la classe .liked est présente. Chaque copie du composant est isolée des autres. Deux copies sur la même page ont des états indépendants.
Accessibilité
- Clavier : le composant est un vrai
<button>; Tab l'atteint, Espace ou Entrée le déclenche. Le contour de focus (2 px indigo) n'apparaît qu'à la navigation au clavier. - État annoncé :
aria-pressedest mis à jour à chaque clic. Un lecteur d'écran annonce le bouton comme non activé au repos et activé une fois liké. Vérifiez que l'aria-labelest traduit dans la langue de votre page. - Mouvement réduit : sous
prefers-reduced-motion: reduce, les trois animations sont désactivées ; les transitions de couleur et de remplissage du cœur (passage de gris à rouge) restent actives. L'état liké reste lisible visuellement sans aucun mouvement. - Toucher : le bouton répond normalement aux événements tactiles. Sa cible de 80 px de diamètre est confortable pour un doigt (minimum recommandé : 44 px).
Compatibilité navigateur
CSS pur (animations, propriété personnalisée --angle, pseudo-éléments) + 0,5 Ko de JavaScript (classList, addEventListener, aria-pressed), aucune dépendance. Vérifié dans Chromium 151 : trois animations simultanées, aria-pressed mis à jour, deux copies indépendantes sur la même page, 0 erreur.
Sans JavaScript, les animations restent actives via la classe .liked tant que l'attribut onclick est présent ; aria-pressed ne sera toutefois plus mis à jour. Sans CSS animations, le cœur reste gris au repos et rouge au clic (changement de couleur seul).
Le code
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="fx-0361-root">
<div class="mi-heart-scene">
<button class="mi-heart-btn" aria-pressed="false" aria-label="Aimer">
<svg viewBox="0 0 24 24"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
<div class="mi-heart-particles">
<span class="mi-heart-particle"></span>
<span class="mi-heart-particle"></span>
<span class="mi-heart-particle"></span>
<span class="mi-heart-particle"></span>
<span class="mi-heart-particle"></span>
<span class="mi-heart-particle"></span>
<span class="mi-heart-particle"></span>
<span class="mi-heart-particle"></span>
</div>
</button>
<span class="mi-heart-count">423 likes</span>
</div>
</div>
.fx-0361-root,
.fx-0361-root *,
.fx-0361-root *::before,
.fx-0361-root *::after {
box-sizing: border-box
}
.fx-0361-root {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 250px;
overflow: hidden;
position: relative;
background: rgb(26, 26, 36);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.mi-heart-scene {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.mi-heart-btn {
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.mi-heart-btn:hover {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
}
.mi-heart-btn:focus-visible {
outline: 2px solid #a5b4fc;
outline-offset: 3px;
}
.mi-heart-btn svg {
width: 36px;
height: 36px;
stroke: rgba(255, 255, 255, 0.5);
fill: none;
stroke-width: 2;
transition: stroke 0.3s ease, fill 0.3s ease;
}
.mi-heart-btn.liked svg {
stroke: #ef4444;
fill: #ef4444;
animation: miHeartPop 0.4s cubic-bezier(0.17, 0.89, 0.32, 1.49);
}
.mi-heart-btn.liked::after {
content: '';
position: absolute;
inset: -4px;
border-radius: 50%;
border: 2px solid #ef4444;
animation: miHeartRing 0.6s ease-out forwards;
}
.mi-heart-particles {
position: absolute;
inset: 0;
pointer-events: none;
}
.mi-heart-particle {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
top: 50%;
left: 50%;
opacity: 0;
}
.mi-heart-btn.liked .mi-heart-particle {
animation: miParticleBurst 0.6s ease-out forwards;
}
.mi-heart-particle:nth-child(1) { background: #ef4444; --angle: 0deg; }
.mi-heart-particle:nth-child(2) { background: #f97316; --angle: 45deg; }
.mi-heart-particle:nth-child(3) { background: #eab308; --angle: 90deg; }
.mi-heart-particle:nth-child(4) { background: #ef4444; --angle: 135deg; }
.mi-heart-particle:nth-child(5) { background: #ec4899; --angle: 180deg; }
.mi-heart-particle:nth-child(6) { background: #f97316; --angle: 225deg; }
.mi-heart-particle:nth-child(7) { background: #eab308; --angle: 270deg; }
.mi-heart-particle:nth-child(8) { background: #ec4899; --angle: 315deg; }
.mi-heart-count {
font-size: 0.85rem;
font-weight: 600;
color: rgba(255, 255, 255, 0.5);
transition: color 0.3s ease;
}
.mi-heart-btn.liked ~ .mi-heart-count {
color: #ef4444;
}
@keyframes miHeartPop {
0% { transform: scale(1); }
30% { transform: scale(1.3); }
60% { transform: scale(0.9); }
100% { transform: scale(1); }
}
@keyframes miHeartRing {
0% { transform: scale(0.8); opacity: 1; }
100% { transform: scale(1.4); opacity: 0; }
}
@keyframes miParticleBurst {
0% { opacity: 1; transform: translate(-50%, -50%) rotate(var(--angle)) translateY(0); }
100% { opacity: 0; transform: translate(-50%, -50%) rotate(var(--angle)) translateY(-40px); }
}
@media (prefers-reduced-motion: reduce) {
.mi-heart-btn { transition: background-color 0.15s, border-color 0.15s; }
.mi-heart-btn svg { transition: stroke 0.15s, fill 0.15s; }
.mi-heart-btn.liked svg { animation: none; }
.mi-heart-btn.liked::after { animation: none; opacity: 0; }
.mi-heart-btn.liked .mi-heart-particle { animation: none; }
}
!function(){document.querySelectorAll('.fx-0361-root').forEach(function(root){var btn=root.querySelector('.mi-heart-btn');if(!btn)return;btn.addEventListener('click',function(){var liked=btn.classList.toggle('liked');btn.setAttribute('aria-pressed',liked?'true':'false');});})}();
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Couleur du cœur (CSS — stroke et fill de .mi-heart-btn.liked svg) |
#ef4444 (rouge) |
Remplacez par #ec4899 pour un rose fuchsia ou #8b5cf6 pour un violet. La couleur de l'anneau (::after border) et du compteur (.mi-heart-btn.liked ~ .mi-heart-count) doivent être modifiés avec la même valeur. |
Nombre de particules (HTML — nombre de span.mi-heart-particle) |
8 particules, tous les 45° | Réduisez à 6 en supprimant 2 span et en ajustant les --angle à 60°, 120°…, ou montez à 12 particules à 30° d'intervalle. Chaque nouvelle particule doit être déclarée en CSS avec son --angle. |
Distance d'envol des particules (CSS — translateY(-40px) dans miParticleBurst) |
40 px | 60 px donne une explosion plus large ; 24 px une particule courte qui reste près du bouton. |
Taille du bouton (CSS — width / height de .mi-heart-btn) |
80 px de diamètre | Passez à 56 px pour l'intégrer dans une liste dense ; à 100 px pour un usage isolé. La taille des particules et du SVG suit si vous les exprimez en em. |
| Icône (HTML — SVG dans le bouton) | Cœur SVG | Remplacez le SVG par une étoile, une flamme ou tout autre pictogramme SVG de 24 px. Adaptez les keyframes miHeartPop si l'icône est asymétrique. |
Compteur (HTML — span.mi-heart-count) |
« 423 likes » | Mettez à jour le nombre via JavaScript (count.textContent = ...) dans votre logique d'application. L'animation de couleur sur le compteur est entièrement en CSS via le sélecteur ~. |
FAQ
Le bouton repasse-t-il à l'état initial si on clique à nouveau ?
Oui. Le script utilise classList.toggle('liked') : un deuxième clic retire la classe, les animations ne se rejouent pas et les couleurs reviennent à leur valeur d'origine via la transition CSS. aria-pressed repasse à « false ».
Peut-on mettre plusieurs boutons cœur sur la même page ?
Oui, sans restriction. Le script cherche tous les .fx-0361-root de la page et câble chaque bouton indépendamment. Chaque copie a son propre état : liker l'un ne touche pas les autres.
Les animations se rejouent-elles si on passe la souris dessus après avoir liké ?
Non. Les animations CSS sont déclarées sans animation-iteration-count: infinite : elles jouent une fois à l'ajout de la classe. Un deuxième survol sans retrait de classe ne relance pas les animations.