Html
Css
Javascript
Rating
Etoiles
Notation
Template
Système de notation par étoiles interactif en HTML CSS JS pur : hover animé, score dynamique, couleurs ambre et zéro dépendance externe.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="copyright" content="AngularForAll" />
<meta name="author" content="AngularForAll" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="public, max-age=604800" />
<title>Snippets Rating HTML CSS JS 2026 05022048 | AngularForAll</title>
<style>
/* =============================================
RESET & VARIABLES CSS
============================================= */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--star-color: #e2e8f0;
--star-active: #f59e0b;
--star-hover: #fbbf24;
--star-shadow: rgba(245, 158, 11, 0.3);
--bg-primary: #f8fafc;
--bg-card: #ffffff;
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-muted: #94a3b8;
--border-color: #e2e8f0;
--radius-sm: 10px;
--radius-md: 16px;
--radius-lg: 24px;
--radius-xl: 28px;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04);
--shadow-lg: 0 20px 50px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.04);
--shadow-xl: 0 25px 60px rgba(0, 0, 0, 0.1);
--transition-fast: 0.15s ease;
--transition-smooth: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
body {
font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
background: linear-gradient(160deg, #f8fafc 0%, #f1f5f9 30%, #fef3c7 70%, #fff7ed 100%);
background-attachment: fixed;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
color: var(--text-primary);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Motif de fond subtil */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
radial-gradient(circle at 20% 30%, rgba(245, 158, 11, 0.06) 0%, transparent 50%),
radial-gradient(circle at 75% 60%, rgba(251, 191, 36, 0.05) 0%, transparent 50%),
radial-gradient(circle at 50% 80%, rgba(245, 158, 11, 0.04) 0%, transparent 40%);
pointer-events: none;
z-index: 0;
}
/* =============================================
CONTENEUR PRINCIPAL
============================================= */
.rating-container {
position: relative;
z-index: 1;
width: 100%;
max-width: 520px;
}
.rating-card {
background: var(--bg-card);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-xl);
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.04);
backdrop-filter: blur(10px);
}
/* =============================================
EN-TÊTE
============================================= */
.rating-header {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
padding: 30px 28px;
color: white;
position: relative;
overflow: hidden;
}
.rating-header::before {
content: '';
position: absolute;
top: -40%;
right: -20%;
width: 180px;
height: 180px;
background: radial-gradient(circle, rgba(245, 158, 11, 0.2) 0%, transparent 70%);
border-radius: 50%;
}
.rating-header::after {
content: '';
position: absolute;
bottom: -30%;
left: 30%;
width: 150px;
height: 150px;
background: radial-gradient(circle, rgba(251, 191, 36, 0.1) 0%, transparent 70%);
border-radius: 50%;
}
.header-content {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
.header-left {
display: flex;
align-items: center;
gap: 14px;
}
.header-icon {
width: 48px;
height: 48px;
background: rgba(245, 158, 11, 0.2);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
flex-shrink: 0;
border: 1px solid rgba(245, 158, 11, 0.3);
}
.header-text h1 {
font-size: 1.3rem;
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 2px;
}
.header-text p {
font-size: 0.78rem;
opacity: 0.65;
}
.header-badge {
background: rgba(245, 158, 11, 0.2);
border: 1px solid rgba(245, 158, 11, 0.3);
color: #fbbf24;
padding: 8px 16px;
border-radius: 25px;
font-size: 0.8rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 6px;
}
/* =============================================
CORPS DE LA CARTE
============================================= */
.rating-body {
padding: 28px;
}
/* =============================================
SECTION AFFICHAGE
============================================= */
.display-section {
text-align: center;
margin-bottom: 28px;
padding: 20px;
background: var(--bg-primary);
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
}
.display-label {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
margin-bottom: 12px;
}
.stars-display {
display: flex;
justify-content: center;
gap: 6px;
margin-bottom: 8px;
}
.star-display {
font-size: 2.2rem;
color: var(--star-color);
transition: all var(--transition-smooth);
position: relative;
}
.star-display.filled {
color: var(--star-active);
filter: drop-shadow(0 0 6px var(--star-shadow));
}
.star-display.half {
position: relative;
color: var(--star-color);
}
.star-display.half::before {
content: '★';
position: absolute;
left: 0;
top: 0;
width: 50%;
overflow: hidden;
color: var(--star-active);
filter: drop-shadow(0 0 6px var(--star-shadow));
}
.rating-value {
font-size: 3rem;
font-weight: 800;
color: var(--text-primary);
letter-spacing: -0.03em;
line-height: 1;
}
.rating-value .decimal {
font-size: 1.8rem;
color: var(--text-secondary);
}
.rating-value .total {
font-size: 1rem;
color: var(--text-muted);
font-weight: 500;
}
.rating-text {
font-size: 0.85rem;
color: var(--text-secondary);
margin-top: 4px;
font-weight: 500;
}
.rating-count {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 2px;
}
/* =============================================
SECTION ÉDITION
============================================= */
.edit-section {
text-align: center;
padding: 20px;
background: linear-gradient(135deg, #fffbeb, #fff7ed);
border-radius: var(--radius-lg);
border: 1px solid #fde68a;
position: relative;
}
.edit-label {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #92400e;
margin-bottom: 14px;
}
.stars-interactive {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 10px;
}
.star-interactive {
font-size: 2.5rem;
color: var(--star-color);
cursor: pointer;
transition: all var(--transition-bounce);
position: relative;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.star-interactive:hover {
transform: scale(1.15);
color: var(--star-hover);
filter: drop-shadow(0 0 10px rgba(251, 191, 36, 0.5));
}
.star-interactive.active {
color: var(--star-active);
filter: drop-shadow(0 0 8px var(--star-shadow));
animation: starPop 0.3s ease forwards;
}
@keyframes starPop {
0% { transform: scale(1); }
40% { transform: scale(1.3); }
70% { transform: scale(0.9); }
100% { transform: scale(1); }
}
.hover-text {
font-size: 0.85rem;
font-weight: 600;
color: #92400e;
min-height: 24px;
transition: all var(--transition-fast);
}
.edit-buttons {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 12px;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 20px;
border-radius: 25px;
font-size: 0.85rem;
font-weight: 600;
font-family: inherit;
cursor: pointer;
transition: all var(--transition-fast);
border: 2px solid transparent;
outline: none;
white-space: nowrap;
}
.btn:active {
transform: scale(0.95);
}
.btn-save {
background: #f59e0b;
color: #1e293b;
border-color: #f59e0b;
box-shadow: 0 4px 14px rgba(245, 158, 11, 0.3);
}
.btn-save:hover {
background: #fbbf24;
border-color: #fbbf24;
box-shadow: 0 6px 20px rgba(245, 158, 11, 0.4);
}
.btn-reset {
background: white;
color: #64748b;
border-color: #e2e8f0;
}
.btn-reset:hover {
background: #f8fafc;
border-color: #cbd5e1;
}
.btn-emoji {
background: white;
border: 2px solid #e2e8f0;
border-radius: 50%;
width: 42px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
cursor: pointer;
transition: all var(--transition-bounce);
padding: 0;
}
.btn-emoji:hover {
transform: scale(1.15);
border-color: #f59e0b;
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
}
.btn-emoji.selected {
background: #fef3c7;
border-color: #f59e0b;
box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.15);
}
/* =============================================
BARRES DE DISTRIBUTION
============================================= */
.distribution-section {
margin-top: 24px;
padding: 20px;
background: var(--bg-primary);
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
}
.distribution-title {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
margin-bottom: 14px;
text-align: center;
}
.distribution-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.distribution-label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-secondary);
width: 24px;
text-align: right;
flex-shrink: 0;
}
.distribution-bar-bg {
flex: 1;
height: 8px;
background: #e2e8f0;
border-radius: 10px;
overflow: hidden;
}
.distribution-bar-fill {
height: 100%;
background: linear-gradient(90deg, #f59e0b, #fbbf24);
border-radius: 10px;
transition: width var(--transition-smooth);
}
.distribution-count {
font-size: 0.72rem;
color: var(--text-muted);
width: 36px;
text-align: left;
flex-shrink: 0;
}
/* =============================================
TOAST NOTIFICATION
============================================= */
.toast {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%) translateY(120px);
background: #1e293b;
color: white;
padding: 12px 24px;
border-radius: 30px;
font-size: 0.85rem;
font-weight: 600;
z-index: 1000;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
transition: transform var(--transition-smooth);
display: flex;
align-items: center;
gap: 8px;
}
.toast.show {
transform: translateX(-50%) translateY(0);
}
/* =============================================
RESPONSIVE
============================================= */
@media (max-width: 600px) {
.rating-header {
padding: 22px 18px;
}
.rating-body {
padding: 18px;
}
.header-text h1 {
font-size: 1.1rem;
}
.star-display {
font-size: 1.7rem;
}
.star-interactive {
font-size: 2rem;
gap: 4px;
}
.stars-interactive {
gap: 4px;
}
.rating-value {
font-size: 2.4rem;
}
.display-section,
.edit-section,
.distribution-section {
padding: 16px;
}
.btn {
padding: 8px 16px;
font-size: 0.78rem;
}
}
@media (max-width: 380px) {
.star-display {
font-size: 1.4rem;
}
.star-interactive {
font-size: 1.7rem;
}
.btn-emoji {
width: 35px;
height: 35px;
font-size: 1rem;
}
}
</style>
</head>
<body>
<!-- =============================================
CONTENEUR PRINCIPAL
============================================= -->
<div class="rating-container">
<div class="rating-card">
<!-- ========== EN-TÊTE ========== -->
<div class="rating-header">
<div class="header-content">
<div class="header-left">
<div class="header-icon">⭐</div>
<div class="header-text">
<h1>RatingStars</h1>
<p>Évaluez ce produit</p>
</div>
</div>
<div class="header-badge">
<span>🏆</span> Top Qualité
</div>
</div>
</div>
<!-- ========== CORPS ========== -->
<div class="rating-body">
<!-- Section Affichage -->
<div class="display-section">
<div class="display-label">📊 Note moyenne</div>
<div class="stars-display" id="starsDisplay">
<!-- Rempli par JS -->
</div>
<div class="rating-value" id="ratingValue">
<!-- Rempli par JS -->
</div>
<div class="rating-text" id="ratingText"></div>
<div class="rating-count" id="ratingCount"></div>
</div>
<!-- Section Édition -->
<div class="edit-section">
<div class="edit-label">✏️ Votre évaluation</div>
<div class="stars-interactive" id="starsInteractive">
<!-- Rempli par JS -->
</div>
<div class="hover-text" id="hoverText">Cliquez pour noter</div>
<!-- Émotions rapides -->
<div style="display:flex;gap:8px;justify-content:center;margin-top:8px;" id="emojiButtons">
<button class="btn-emoji" data-rating="1" title="Très déçu">😡</button>
<button class="btn-emoji" data-rating="2" title="Déçu">😞</button>
<button class="btn-emoji" data-rating="3" title="Correct">😐</button>
<button class="btn-emoji" data-rating="4" title="Bien">😊</button>
<button class="btn-emoji" data-rating="5" title="Excellent">😍</button>
</div>
<div class="edit-buttons">
<button class="btn btn-save" id="btnSave">
✓ Enregistrer
</button>
<button class="btn btn-reset" id="btnReset">
↺ Réinitialiser
</button>
</div>
</div>
<!-- Section Distribution -->
<div class="distribution-section">
<div class="distribution-title">📈 Répartition des notes</div>
<div id="distributionBars">
<!-- Rempli par JS -->
</div>
</div>
</div>
</div>
</div>
<!-- ========== TOAST ========== -->
<div class="toast" id="toast">
<span id="toastIcon">✓</span>
<span id="toastMessage">Note enregistrée !</span>
</div>
<script>
(function() {
// =============================================
// DONNÉES INITIALES
// =============================================
const TOTAL_STARS = 5;
// Simulation de notes existantes
let ratings = [0, 3, 8, 27, 42, 85]; // index 1 à 5 (index 0 inutilisé)
let userRating = 0; // Note de l'utilisateur (0 = pas encore noté)
let currentHover = 0; // Étoile survolée
// =============================================
// ÉLÉMENTS DOM
// =============================================
const starsDisplay = document.getElementById('starsDisplay');
const ratingValue = document.getElementById('ratingValue');
const ratingText = document.getElementById('ratingText');
const ratingCount = document.getElementById('ratingCount');
const starsInteractive = document.getElementById('starsInteractive');
const hoverText = document.getElementById('hoverText');
const distributionBars = document.getElementById('distributionBars');
const btnSave = document.getElementById('btnSave');
const btnReset = document.getElementById('btnReset');
const toast = document.getElementById('toast');
const toastIcon = document.getElementById('toastIcon');
const toastMessage = document.getElementById('toastMessage');
// =============================================
// FONCTIONS DE CALCUL
// =============================================
/** Calcule la note moyenne */
function getAverageRating() {
let totalSum = 0;
let totalCount = 0;
for (let i = 1; i <= TOTAL_STARS; i++) {
totalSum += i * ratings[i];
totalCount += ratings[i];
}
if (totalCount === 0) return 0;
return totalSum / totalCount;
}
/** Calcule le nombre total de votes */
function getTotalVotes() {
return ratings.reduce((sum, val, idx) => idx > 0 ? sum + val : sum, 0);
}
/** Retourne le texte correspondant à une note */
function getRatingLabel(rating) {
if (rating === 0) return 'Aucune note';
if (rating <= 1.4) return 'Très décevant';
if (rating <= 2.4) return 'Décevant';
if (rating <= 3.4) return 'Correct';
if (rating <= 4.4) return 'Très bien';
return 'Excellent';
}
// =============================================
// AFFICHAGE DES ÉTOILES (MODE LECTURE)
// =============================================
function renderDisplayStars() {
const average = getAverageRating();
const fullStars = Math.floor(average);
const decimalPart = average - fullStars;
const hasHalfStar = decimalPart >= 0.25 && decimalPart < 0.75;
const extraFullStar = decimalPart >= 0.75 ? 1 : 0;
starsDisplay.innerHTML = '';
for (let i = 1; i <= TOTAL_STARS; i++) {
const star = document.createElement('span');
star.className = 'star-display';
if (i <= fullStars + extraFullStar) {
star.classList.add('filled');
star.textContent = '★';
} else if (i === fullStars + 1 && hasHalfStar) {
star.classList.add('half');
star.textContent = '★';
} else {
star.textContent = '★';
}
starsDisplay.appendChild(star);
}
// Mise à jour de la valeur numérique
const avgFormatted = average.toFixed(1);
const [whole, decimal] = avgFormatted.split('.');
ratingValue.innerHTML = `
${whole}<span class="decimal">.${decimal}</span>
<span class="total">/ ${TOTAL_STARS}</span>
`;
// Texte et compteur
ratingText.textContent = getRatingLabel(average);
const totalVotes = getTotalVotes();
ratingCount.textContent = `${totalVotes} avis au total`;
}
// =============================================
// AFFICHAGE DES ÉTOILES (MODE ÉDITION)
// =============================================
function renderInteractiveStars() {
starsInteractive.innerHTML = '';
for (let i = 1; i <= TOTAL_STARS; i++) {
const star = document.createElement('span');
star.className = 'star-interactive';
star.textContent = '★';
star.dataset.value = i;
// Survol
star.addEventListener('mouseenter', () => {
currentHover = i;
updateInteractiveStars();
updateHoverText(i);
});
star.addEventListener('mouseleave', () => {
currentHover = 0;
updateInteractiveStars();
updateHoverText(userRating);
});
// Clic
star.addEventListener('click', () => {
userRating = i;
currentHover = 0;
updateInteractiveStars();
updateHoverText(userRating);
updateEmojiButtons();
// Animation de la note
animateRatingChange(i);
});
// Tactile
star.addEventListener('touchend', (e) => {
e.preventDefault();
userRating = i;
currentHover = 0;
updateInteractiveStars();
updateHoverText(userRating);
updateEmojiButtons();
animateRatingChange(i);
});
starsInteractive.appendChild(star);
}
updateInteractiveStars();
updateEmojiButtons();
}
function updateInteractiveStars() {
const stars = starsInteractive.querySelectorAll('.star-interactive');
const activeValue = currentHover > 0 ? currentHover : userRating;
stars.forEach((star, index) => {
const value = index + 1;
star.classList.remove('active');
if (value <= activeValue) {
star.classList.add('active');
}
});
}
function updateHoverText(rating) {
const texts = {
0: 'Cliquez pour noter',
1: '😡 Très déçu',
2: '😞 Déçu',
3: '😐 Correct',
4: '😊 Très bien',
5: '😍 Excellent !'
};
hoverText.textContent = texts[rating] || 'Cliquez pour noter';
// Animation
hoverText.style.transform = 'scale(1.05)';
setTimeout(() => {
hoverText.style.transform = 'scale(1)';
}, 150);
}
function animateRatingChange(rating) {
const stars = starsInteractive.querySelectorAll('.star-interactive');
stars.forEach((star, index) => {
if (index + 1 <= rating) {
star.style.animation = 'none';
star.offsetHeight;
star.style.animation = 'starPop 0.3s ease forwards';
star.style.animationDelay = `${index * 0.06}s`;
}
});
}
// =============================================
// BOUTONS EMOJI
// =============================================
function updateEmojiButtons() {
const buttons = document.querySelectorAll('#emojiButtons .btn-emoji');
buttons.forEach(btn => {
const btnRating = parseInt(btn.dataset.rating);
btn.classList.remove('selected');
if (btnRating === userRating) {
btn.classList.add('selected');
}
});
}
function setupEmojiButtons() {
const buttons = document.querySelectorAll('#emojiButtons .btn-emoji');
buttons.forEach(btn => {
btn.addEventListener('click', () => {
const rating = parseInt(btn.dataset.rating);
userRating = rating;
currentHover = 0;
updateInteractiveStars();
updateHoverText(userRating);
updateEmojiButtons();
animateRatingChange(rating);
});
});
}
// =============================================
// BARRES DE DISTRIBUTION
// =============================================
function renderDistribution() {
const totalVotes = getTotalVotes();
distributionBars.innerHTML = '';
for (let i = TOTAL_STARS; i >= 1; i--) {
const count = ratings[i];
const percentage = totalVotes > 0 ? (count / totalVotes) * 100 : 0;
const row = document.createElement('div');
row.className = 'distribution-row';
const starsLabel = '★'.repeat(i);
row.innerHTML = `
<div class="distribution-label">${i}</div>
<div class="distribution-bar-bg">
<div class="distribution-bar-fill" style="width: ${percentage}%;"></div>
</div>
<div class="distribution-count">${count}</div>
`;
distributionBars.appendChild(row);
}
}
// =============================================
// SAUVEGARDE ET RÉINITIALISATION
// =============================================
function saveRating() {
if (userRating === 0) {
showToast('⚠️', 'Veuillez sélectionner une note');
return;
}
// Ajouter la note
ratings[userRating]++;
// Mettre à jour l'affichage
renderDisplayStars();
renderDistribution();
// Réinitialiser la sélection
userRating = 0;
currentHover = 0;
updateInteractiveStars();
updateHoverText(0);
updateEmojiButtons();
showToast('✅', 'Votre note a été enregistrée !');
}
function resetRating() {
if (userRating === 0) {
showToast('ℹ️', 'Aucune note à réinitialiser');
return;
}
userRating = 0;
currentHover = 0;
updateInteractiveStars();
updateHoverText(0);
updateEmojiButtons();
showToast('🔄', 'Note réinitialisée');
}
// =============================================
// TOAST NOTIFICATION
// =============================================
let toastTimeout;
function showToast(icon, message) {
toastIcon.textContent = icon;
toastMessage.textContent = message;
clearTimeout(toastTimeout);
toast.classList.add('show');
toastTimeout = setTimeout(() => {
toast.classList.remove('show');
}, 2500);
}
// =============================================
// ÉVÉNEMENTS
// =============================================
btnSave.addEventListener('click', saveRating);
btnReset.addEventListener('click', resetRating);
// Raccourci clavier
document.addEventListener('keydown', (e) => {
if (e.key >= '1' && e.key <= '5') {
const rating = parseInt(e.key);
userRating = rating;
currentHover = 0;
updateInteractiveStars();
updateHoverText(userRating);
updateEmojiButtons();
animateRatingChange(rating);
}
if (e.key === 'Enter') {
saveRating();
}
if (e.key === 'Escape') {
resetRating();
}
});
// =============================================
// INITIALISATION
// =============================================
function init() {
renderDisplayStars();
renderInteractiveStars();
renderDistribution();
setupEmojiButtons();
console.log('⭐ RatingStars initialisé • Prêt à évaluer !');
}
init();
})();
</script>
</body>
</html>
Télécharger le fichier source