Transformez un nombre en affichage par étoiles en utilisant jQuery et CSS

101

J'ai regardé le plugin jquery et je me demandais comment adapter ce plugin pour transformer un nombre (comme 4.8618164) en 4,8618164 étoiles remplies sur 5. En gros, interpréter un nombre <5 en étoiles remplies dans un système d'évaluation 5 étoiles en utilisant jQuery / JS / CSS.

Notez que cela n'affichera / affichera que les étoiles d'un nombre déjà disponible et n'acceptera pas de nouvelles soumissions de notes.

Steve
la source

Réponses:

255

Voici une solution pour vous, en utilisant une seule image très petite et simple et un élément span généré automatiquement:

CSS

span.stars, span.stars span {
    display: block;
    background: url(stars.png) 0 -16px repeat-x;
    width: 80px;
    height: 16px;
}

span.stars span {
    background-position: 0 0;
}

Image

texte alternatif
(source: ulmanen.fi )

Note: ne pas hotlink à l'image ci - dessus! Copiez le fichier sur votre propre serveur et utilisez-le à partir de là.

jQuery

$.fn.stars = function() {
    return $(this).each(function() {
        // Get the value
        var val = parseFloat($(this).html());
        // Make sure that the value is in 0 - 5 range, multiply to get width
        var size = Math.max(0, (Math.min(5, val))) * 16;
        // Create stars holder
        var $span = $('<span />').width(size);
        // Replace the numerical value with stars
        $(this).html($span);
    });
}

Si vous souhaitez limiter les étoiles à une demi-étoile ou un quart d'étoile uniquement, ajoutez l'une de ces lignes avant la var sizeligne:

val = Math.round(val * 4) / 4; /* To round to nearest quarter */
val = Math.round(val * 2) / 2; /* To round to nearest half */

HTML

<span class="stars">4.8618164</span>
<span class="stars">2.6545344</span>
<span class="stars">0.5355</span>
<span class="stars">8</span>

Usage

$(function() {
    $('span.stars').stars();
});

Production

Image tirée du jeu d'icônes de fugue (www.pinvoke.com)
(source: ulmanen.fi )

Démo

http://www.ulmanen.fi/stuff/stars.php

Cela répondra probablement à vos besoins. Avec cette méthode, vous n'avez pas besoin de calculer des largeurs d'étoiles de trois quarts ou autres, donnez-lui simplement un flotteur et il vous donnera vos étoiles.


Une petite explication sur la façon dont les étoiles sont présentées pourrait être de mise.

Le script crée deux éléments d'étendue de niveau bloc. Les deux travées ont initialement une taille de 80px * 16px et une image d'arrière-plan stars.png. Les travées sont imbriquées, de sorte que la structure des travées ressemble à ceci:

<span class="stars">
    <span></span>
</span>

La travée extérieure obtient un background-positionde 0 -16px. Cela rend visibles les étoiles grises de la travée extérieure. Comme la travée extérieure a une hauteur de 16px et repeat-x, elle n'affichera que 5 étoiles grises.

La durée intérieure d'autre part a background-positionde 0 0qui fait que les étoiles jaunes visibles.

Cela fonctionnerait bien sûr avec deux fichiers image séparés, star_yellow.png et star_gray.png. Mais comme les étoiles ont une hauteur fixe, nous pouvons facilement les combiner en une seule image. Cela utilise la technique du sprite CSS .

Désormais, au fur et à mesure que les travées sont imbriquées, elles sont automatiquement superposées les unes sur les autres. Dans le cas par défaut, lorsque la largeur des deux travées est de 80 pixels, les étoiles jaunes masquent complètement les étoiles grises.

Mais lorsque nous ajustons la largeur de la travée intérieure, la largeur des étoiles jaunes diminue, révélant les étoiles grises.

En ce qui concerne l'accessibilité, il aurait été plus sage de laisser le nombre flottant à l'intérieur de la plage intérieure et de le masquer avec text-indent: -9999px, de sorte que les personnes avec CSS désactivé voient au moins le nombre à virgule flottante au lieu des étoiles.

J'espère que cela a du sens.


Mis à jour le 22/10/2010

Maintenant encore plus compact et plus difficile à comprendre! Peut également être réduit à une seule doublure:

$.fn.stars = function() {
    return $(this).each(function() {
        $(this).html($('<span />').width(Math.max(0, (Math.min(5, parseFloat($(this).html())))) * 16));
    });
}
Tatu Ulmanen
la source
@Tatu, toute chance de l'étoffer un peu plus avec la façon dont cela fonctionne. Ma connaissance de jQuery est juste un peu courte pour l'obtenir totalement. Je pense que je comprends comment le CSS fonctionne avec la répétition dans la direction x et le 16 * 5 = 80 bits et comment vous ajustez la largeur de l'image étoile jaune, mais comment superposez-vous les deux images l'une sur l'autre à partir d'un seule image qui a une étoile au-dessus de l'autre? Le -16px amène-t-il l'étoile grise au même niveau que le jaune?
paxdiablo
Merci pour la mise à jour, @Tatu, cela me semble beaucoup plus logique maintenant. Malheureusement, je vous avais déjà donné un +1, donc je ne peux pas le refaire, mais j'espère que l'explication vous rapportera un peu plus de représentants (bien mérités). À votre santé.
paxdiablo
2
@paxdiablo et autres, merci pour le soutien. Peut-être que je devrais en faire un plugin jQuery approprié car cette technique semble surprendre la plupart :)
Tatu Ulmanen
@Tatu, merci infiniment. Les plugins cinq étoiles existants semblent tous vouloir un formulaire qui convient à la saisie d'une note, mais il est excessif de changer la représentation d'un nombre.
vfilby le
7
Ou encore plus petit: jsbin.com/IBIDalEn/2/edit (PS a supprimé les éléments inutiles dans JS, les sélecteurs CSS minifiés et utilisés max-width).
Roko C. Buljan
30

Si vous ne devez prendre en charge que les navigateurs modernes, vous pouvez vous en sortir avec:

  • Aucune image;
  • Principalement css statique;
  • Presque pas de jQuery ou Javascript;

Il vous suffit de convertir le nombre en a class, par exemple class='stars-score-50'.

Tout d'abord une démonstration du balisage "rendu":

body { font-size: 18px; }

.stars-container {
  position: relative;
  display: inline-block;
  color: transparent;
}

.stars-container:before {
  position: absolute;
  top: 0;
  left: 0;
  content: '★★★★★';
  color: lightgray;
}

.stars-container:after {
  position: absolute;
  top: 0;
  left: 0;
  content: '★★★★★';
  color: gold;
  overflow: hidden;
}

.stars-0:after { width: 0%; }
.stars-10:after { width: 10%; }
.stars-20:after { width: 20%; }
.stars-30:after { width: 30%; }
.stars-40:after { width: 40%; }
.stars-50:after { width: 50%; }
.stars-60:after { width: 60%; }
.stars-70:after { width: 70%; }
.stars-80:after { width: 80%; }
.stars-90:after { width: 90%; }
.stars-100:after { width: 100; }
Within block level elements:

<div><span class="stars-container stars-0">★★★★★</span></div>
<div><span class="stars-container stars-10">★★★★★</span></div>
<div><span class="stars-container stars-20">★★★★★</span></div>
<div><span class="stars-container stars-30">★★★★★</span></div>
<div><span class="stars-container stars-40">★★★★★</span></div>
<div><span class="stars-container stars-50">★★★★★</span></div>
<div><span class="stars-container stars-60">★★★★★</span></div>
<div><span class="stars-container stars-70">★★★★★</span></div>
<div><span class="stars-container stars-80">★★★★★</span></div>
<div><span class="stars-container stars-90">★★★★★</span></div>
<div><span class="stars-container stars-100">★★★★★</span></div>

<p>Or use it in a sentence: <span class="stars-container stars-70">★★★★★</span> (cool, huh?).</p>

Puis une démo qui utilise un petit bout de code:

$(function() {
  function addScore(score, $domElement) {
    $("<span class='stars-container'>")
      .addClass("stars-" + score.toString())
      .text("★★★★★")
      .appendTo($domElement);
  }

  addScore(70, $("#fixture"));
});
body { font-size: 18px; }

.stars-container {
  position: relative;
  display: inline-block;
  color: transparent;
}

.stars-container:before {
  position: absolute;
  top: 0;
  left: 0;
  content: '★★★★★';
  color: lightgray;
}

.stars-container:after {
  position: absolute;
  top: 0;
  left: 0;
  content: '★★★★★';
  color: gold;
  overflow: hidden;
}

.stars-0:after { width: 0%; }
.stars-10:after { width: 10%; }
.stars-20:after { width: 20%; }
.stars-30:after { width: 30%; }
.stars-40:after { width: 40%; }
.stars-50:after { width: 50%; }
.stars-60:after { width: 60%; }
.stars-70:after { width: 70%; }
.stars-80:after { width: 80%; }
.stars-90:after { width: 90%; }
.stars-100:after { width: 100; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Generated: <div id="fixture"></div>

Les plus gros inconvénients de cette solution sont:

  1. Vous avez besoin des étoiles à l'intérieur de l'élément pour générer une largeur correcte;
  2. Il n'y a pas de balisage sémantique, par exemple vous préférez la partition comme texte à l'intérieur de l'élément;
  3. Il permet seulement autant de scores que vous aurez de classes (car nous ne pouvons pas utiliser Javascript pour définir un précis widthsur un pseudo-élément).

Pour résoudre ce problème, la solution ci-dessus peut être facilement modifiée. Les bits :beforeet :afterdoivent devenir de véritables éléments dans le DOM (nous avons donc besoin de JS pour cela).

Ce dernier est laissé comme un exercice pour le lecteur.

Jeroen
la source
3
J'ai étendu cette solution pour tenir compte des flottants et des scores générés dynamiquement qui ne rentrent pas dans les dixièmes de 100. Codepen . Merci de m'avoir lancé: D
cameck
Ceci est un script assez doux
Nick Div
1
Solution parfaite. Tu as fait ma journée. Merci @Jeroen ... continue à basculer.
Prabhu Nandan Kumar
22

Essayez cette fonction / fichier d'aide jquery

jquery.Rating.js

//ES5
$.fn.stars = function() {
    return $(this).each(function() {
        var rating = $(this).data("rating");
        var fullStar = new Array(Math.floor(rating + 1)).join('<i class="fas fa-star"></i>');
        var halfStar = ((rating%1) !== 0) ? '<i class="fas fa-star-half-alt"></i>': '';
        var noStar = new Array(Math.floor($(this).data("numStars") + 1 - rating)).join('<i class="far fa-star"></i>');
        $(this).html(fullStar + halfStar + noStar);
    });
}

//ES6
$.fn.stars = function() {
    return $(this).each(function() {
        const rating = $(this).data("rating");
        const numStars = $(this).data("numStars");
        const fullStar = '<i class="fas fa-star"></i>'.repeat(Math.floor(rating));
        const halfStar = (rating%1!== 0) ? '<i class="fas fa-star-half-alt"></i>': '';
        const noStar = '<i class="far fa-star"></i>'.repeat(Math.floor(numStars-rating));
        $(this).html(`${fullStar}${halfStar}${noStar}`);
    });
}

index.html

   <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Star Rating</title>
        <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" rel="stylesheet">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script src="js/jquery.Rating.js"></script>
        <script>
            $(function(){
                $('.stars').stars();
            });
        </script>
    </head>
    <body>

        <span class="stars" data-rating="3.5" data-num-stars="5" ></span>

    </body>
    </html>

Capture d'écran

Rajasekar D
la source
Juste une note que la balise de fermeture </script> est manquante :)
Rob Stocki
7

Pourquoi ne pas avoir simplement cinq images distinctes d'une étoile (vide, quart plein, moitié plein, trois quart plein et plein), puis injecter simplement les images dans votre DOM en fonction de la valeur tronquée ou arrondie de la note multipliée par 4 ( pour obtenir un nombre entier pour les trimestres)?

Par exemple, 4,8618164 multiplié par 4 et arrondi est de 19, ce qui correspond à quatre étoiles et trois quarts.

Sinon (si vous êtes paresseux comme moi), choisissez simplement une image parmi 21 (de 0 étoiles à 5 étoiles par incréments d'un quart) et sélectionnez l'image unique en fonction de la valeur susmentionnée. Ensuite, c'est juste un calcul suivi d'un changement d'image dans le DOM (plutôt que d'essayer de changer cinq images différentes).

paxdiablo
la source
5

J'ai fini par devenir totalement sans JS pour éviter le décalage de rendu côté client. Pour ce faire, je génère du HTML comme ceci:

<span class="stars" title="{value as decimal}">
    <span style="width={value/5*100}%;"/>
</span>

Pour faciliter l'accessibilité, j'ajoute même la valeur de notation brute dans l'attribut title.

Tim Schmidt
la source
4

en utilisant jquery sans prototype, mettez à jour le code js pour

$( ".stars" ).each(function() { 
    // Get the value
    var val = $(this).data("rating");
    // Make sure that the value is in 0 - 5 range, multiply to get width
    var size = Math.max(0, (Math.min(5, val))) * 16;
    // Create stars holder
    var $span = $('<span />').width(size);
    // Replace the numerical value with stars
    $(this).html($span);
});

J'ai également ajouté un attribut de données par le nom de l'évaluation des données dans la plage.

<span class="stars" data-rating="4" ></span>
Galuga
la source
2

DEMO

Vous pouvez le faire avec 2 images seulement. 1 étoiles vierges, 1 étoiles remplies.

Superposer l'image remplie par-dessus l'autre. et convertissez le numéro de classement en pourcentage et utilisez-le comme largeur de l'image de remplissage.

entrez la description de l'image ici

.containerdiv {
  border: 0;
  float: left;
  position: relative;
  width: 300px;
} 
.cornerimage {
  border: 0;
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
 } 
 img{
   max-width: 300px;
 }
Elyor
la source
0

Voici mon avis sur l'utilisation de JSX et de la police géniale, limitée à une précision de 0,5 seulement:

       <span>
          {Array(Math.floor(rating)).fill(<i className="fa fa-star"></i>)}
          {(rating) - Math.floor(rating)==0 ? ('') : (<i className="fa fa-star-half"></i>)}
       </span>

La première rangée correspond à l'étoile entière et la deuxième rangée à la demi-étoile (le cas échéant)

Ardhi
la source