Skip to Content

Welcome .


Sign up

Cette question a été signalée

Bloc HTML/CSS/JS prêt à intégrer : rendu glassmorphique, animation fluide, grille Y, valeurs modifiables via le tableau DATA, et mise à jour à chaud avec updateFrozenBars() :


<!-- Graphique en bâtons "Frozen Glass" — Exemple + largeur réduite -->
<div class="fg-scene">
  <div class="fg-card" id="glassBarChart" aria-label="Graphique en bâtons">
    <div class="fg-head">
      <h3>Ventes mensuelles (exemple)</h3>
      <span class="fg-ymax" id="fgYMax"></span>
    </div>
    <div class="fg-chart">
      <div class="fg-grid" id="fgGrid" aria-hidden="true"></div>
      <div class="fg-bars" id="fgBars"></div>
    </div>
    <div class="fg-legend" id="fgLegend"></div>
  </div>
</div>

<style>
  /* ===== Scene (fond) ===== */
  .fg-scene{
    --bg1:#0b1220; --bg2:#0f213a; --bg3:#143a64;
    --ice:#ffffff; --ice-05:rgba(255,255,255,.05); --ice-08:rgba(255,255,255,.08);
    --ice-12:rgba(255,255,255,.12); --ice-18:rgba(255,255,255,.18); --ice-30:rgba(255,255,255,.30);
    --accent:#8bd3ff; /* teinte principale des barres */
    --accent-2:#b0e4ff;
    --text:#eaf6ff; --muted:#b7c7d8;
    --shadow: 0 12px 40px rgba(0,0,0,.35);
    --radius: 22px;
    --grid: rgba(255,255,255,.10);
    --tick: rgba(255,255,255,.18);

    position: relative;
    padding: 32px;
    background:
      radial-gradient(1200px 800px at 15% 10%, rgba(139,211,255,.25), transparent 60%),
      radial-gradient(1000px 700px at 85% 90%, rgba(84,145,255,.22), transparent 55%),
      linear-gradient(140deg, var(--bg1), var(--bg2) 45%, var(--bg3));
    min-height: 420px;
    display: grid;
    place-items: center;
  }

  /* ===== Carte "frozen glass" (largeur réduite) ===== */
  .fg-card{
    width: min(780px, 92vw); /* <- moins large */
    padding: 22px 22px 16px;
    border-radius: var(--radius);
    background: linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.06));
    border: 1px solid var(--ice-18);
    box-shadow: var(--shadow);
    backdrop-filter: blur(14px) saturate(160%);
    -webkit-backdrop-filter: blur(14px) saturate(160%);
    color: var(--text);
  }
  .fg-head{
    display:flex; align-items:baseline; justify-content:space-between; gap:16px;
    padding: 4px 2px 12px;
  }
  .fg-head h3{ margin:0; font: 600 18px/1.2 system-ui, Segoe UI, Roboto, Arial, sans-serif; letter-spacing:.2px; }
  .fg-ymax{ color: var(--muted); font-size: 12px; }

  /* ===== Zone chart ===== */
  .fg-chart{
    position: relative;
    height: clamp(220px, 40vh, 360px);
    border-radius: calc(var(--radius) - 6px);
    background: linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,.04));
    border: 1px solid var(--ice-12);
    overflow: hidden;
  }
  /* Grille horizontale */
  .fg-grid{
    position:absolute; inset:0 0 24px 0; /* laisser la place aux labels X */
    background-image:
      linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
    background-size: 100% calc(20%); /* 5 lignes -> 0%,20%,40%,60%,80% */
    pointer-events:none;
  }
  .fg-grid::after{
    content:""; position:absolute; left:0; right:0; bottom:0; height:1px; background: var(--grid);
  }

  /* Conteneur des barres */
  .fg-bars{
    position:absolute; inset:0 0 24px 0;
    display:flex; align-items:flex-end; gap:clamp(8px, 1.6vw, 14px);
    padding: 16px 14px 0 14px;
  }

  /* Barre + label */
  .fg-bar{
    flex:1 1 0;
    display:flex; flex-direction:column; justify-content:flex-end; align-items:center;
    min-width: 28px; max-width: 120px;
  }
  .fg-bar .fg-stick{
    width: 100%;
    height: 0%; /* sera animé en JS */
    border-radius: 12px 12px 8px 8px;
    background:
      linear-gradient(180deg, rgba(255,255,255,.55), rgba(255,255,255,.15)) /* givre */,
      linear-gradient(180deg, var(--accent), var(--accent-2));
    box-shadow:
      inset 0 1px 1px rgba(255,255,255,.35),
      inset 0 -10px 30px rgba(0,0,0,.15),
      0 8px 18px rgba(0,0,0,.28);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    transform-origin: bottom;
    transform: scaleY(0);
    transition: transform 900ms cubic-bezier(.22,1,.36,1), box-shadow .3s ease;
  }
  .fg-bar:is(:hover, :focus-within) .fg-stick{
    box-shadow:
      inset 0 1px 1px rgba(255,255,255,.55),
      inset 0 -10px 30px rgba(0,0,0,.09),
      0 10px 26px rgba(0,0,0,.35);
  }

  .fg-value{
    margin-bottom: 8px;
    font-size: 12px;
    color: var(--text);
    background: rgba(255,255,255,.12);
    border: 1px solid var(--ice-18);
    padding: 2px 7px;
    border-radius: 10px;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transform: translateY(6px);
    opacity: 0;
    transition: opacity .35s ease .3s, transform .35s ease .3s;
    pointer-events:none;
  }
  .fg-bar._in .fg-value{ opacity:1; transform: translateY(0); }

  .fg-xlabel{
    margin-top: 8px;
    font-size: 12px; color: var(--muted);
    text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }

  /* Légende des ticks Y à gauche */
  .fg-legend{
    display:flex; justify-content:space-between; gap:8px;
    padding: 10px 8px 0; color:var(--muted); font-size:11px;
  }

  /* Fallback si backdrop-filter non supporté */
  @supports not ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))){
    .fg-card, .fg-chart, .fg-value{ background: rgba(255,255,255,.12); }
  }
</style>

<script>
/* ===== VALEURS D'EXEMPLE ===== */
const DATA = [
  { label: "Jan",  value: 8  },
  { label: "Fév",  value: 12 },
  { label: "Mar",  value: 9  },
  { label: "Avr",  value: 15 },
  { label: "Mai",  value: 19 },
  { label: "Juin", value: 14 },
  { label: "Juil", value: 22 },
  { label: "Août", value: 17 },
  { label: "Sept", value: 20 },
  { label: "Oct",  value: 23 },
  { label: "Nov",  value: 18 },
  { label: "Déc",  value: 25 }
];

// Options rapidement ajustables
const OPTIONS = {
  yTicks: 5,           // nombre de niveaux sur l'axe Y (grille)
  max: null,           // fixe manuellement la valeur max (sinon auto)
  unit: " k€",         // unité affichée
  animMs: 900,         // durée d'animation d'une barre
  delayPerBar: 70      // décalage progressif entre barres (ms)
};

/* ===== Rendu du chart ===== */
(function(){
  const root = document.getElementById("glassBarChart");
  const grid = document.getElementById("fgGrid");
  const bars = document.getElementById("fgBars");
  const legend = document.getElementById("fgLegend");
  const ymaxEl = document.getElementById("fgYMax");

  function fmt(n){ return (Number.isInteger(n) ? n : +n.toFixed(1)) + (OPTIONS.unit||""); }

  function computeMax(data, manual){
    const maxVal = data.reduce((m,d)=> Math.max(m, d.value||0), 0);
    const goal = manual ?? maxVal;
    const pow = Math.pow(10, Math.floor(Math.log10(goal||1)));
    const n = goal / pow;
    const nice = (n<=1? 1 : n<=2? 2 : n<=5? 5 : 10) * pow;
    return nice || 1;
  }

  function render(data){
    const max = computeMax(data, OPTIONS.max);
    ymaxEl.textContent = "Max: " + fmt(max);

    // Ticks Y
    legend.innerHTML = "";
    for(let i=0;i<=OPTIONS.yTicks;i++){
      const v = (max/OPTIONS.yTicks)*i;
      const tick = document.createElement("span");
      tick.textContent = fmt(v);
      legend.appendChild(tick);
    }

    // Barres
    bars.innerHTML = "";
    data.forEach((d, idx)=>{
      const wrap = document.createElement("div");
      wrap.className = "fg-bar";
      wrap.style.setProperty("--idx", idx);

      const val = document.createElement("div");
      val.className = "fg-value";
      val.textContent = fmt(d.value);

      const stick = document.createElement("div");
      stick.className = "fg-stick";
      stick.setAttribute("role","img");
      stick.setAttribute("aria-label", `${d.label}: ${d.value}${OPTIONS.unit||""} (${Math.round((d.value/max)*100)}%)`);

      const xl = document.createElement("div");
      xl.className = "fg-xlabel";
      xl.textContent = d.label;

      wrap.appendChild(val);
      wrap.appendChild(stick);
      wrap.appendChild(xl);
      bars.appendChild(wrap);

      const pct = Math.max(0, Math.min(1, (d.value||0) / max));
      const delay = OPTIONS.delayPerBar * idx;
      stick.style.transitionDuration = OPTIONS.animMs + "ms";
      stick.style.transitionDelay = delay + "ms";

      requestAnimationFrame(()=>{
        wrap.classList.add("_in");
        stick.style.transform = "scaleY("+pct+")";
      });
    });
  }

  // Relance l'animation lorsque le composant redevient visible
  const obs = new IntersectionObserver((entries)=>{
    entries.forEach(e=>{
      if(e.isIntersecting){
        bars.querySelectorAll(".fg-stick").forEach(el=> el.style.transform = "scaleY(0)");
        bars.querySelectorAll(".fg-bar").forEach(el=> el.classList.remove("_in"));
        setTimeout(()=> render(DATA), 30);
      }
    });
  }, {threshold: .2});
  obs.observe(root);

  render(DATA);

  // API pour mise à jour à chaud
  window.updateFrozenBars = function(newData){
    if(Array.isArray(newData) && newData.length){
      render(newData);
    }
  };
})();
</script>

Ignorer
Related Posts Replies Views Activity
0
Aug 25
43
0
Aug 25
40
1
Aug 25
79
0
Aug 25
45
0
Aug 25
1