Effets visuels créatifs

Effet de chute de neige romantique

Scène de chute de neige en plein écran avec quantité de neige et direction du vent réglables, Canvas.

HTMLCanvasJavaScriptFlocons de neige
Après activation de JavaScript, ce cas peut être modifié, exécuté et enregistré. Code source fixe ci-dessous :
<!doctype html>
<html lang="fr" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Effet de chute de neige romantique</title>
  <style>
    * { box-sizing: border-box; }
    body { min-height: 100vh; margin: 0; overflow: hidden; color: #f6fbff; font-family: "Avenir Next", "Microsoft YaHei", sans-serif; background: linear-gradient(#07162b, #173354 58%, #6285a2); }
    canvas { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; }
    .moon { position: fixed; top: 9vh; right: 12vw; width: clamp(70px, 12vw, 130px); aspect-ratio: 1; border-radius: 50%; background: #fff7d3; box-shadow: 0 0 55px rgba(255,247,211,.45); }
    .mountains { position: fixed; inset: 38% -5% 0; background: #17304b; clip-path: polygon(0 58%, 11% 25%, 20% 48%, 34% 7%, 49% 52%, 62% 22%, 73% 56%, 87% 11%, 100% 43%, 100% 100%, 0 100%); }
    .mountains::after { content: ""; position: absolute; inset: 18% 0 0; background: #0d253d; clip-path: polygon(0 58%, 15% 29%, 28% 61%, 45% 20%, 58% 58%, 74% 27%, 88% 60%, 100% 38%, 100% 100%, 0 100%); }
    .cabin { position: fixed; left: 16vw; bottom: 10vh; width: 118px; height: 80px; background: #3d2a28; box-shadow: inset 0 -16px rgba(0,0,0,.18); }
    .cabin::before { content: ""; position: absolute; left: -18px; top: -43px; border-left: 77px solid transparent; border-right: 77px solid transparent; border-bottom: 48px solid #241d22; }
    .cabin::after { content: ""; position: absolute; right: 20px; bottom: 24px; width: 24px; height: 25px; background: #ffd77a; box-shadow: 0 0 24px #f6aa42; }
    main { position: relative; z-index: 2; width: min(1120px, calc(100% - 36px)); min-height: 100vh; margin: auto; padding-top: clamp(36px, 10vh, 100px); }
    .copy { max-width: 560px; text-shadow: 0 3px 16px rgba(3,12,25,.65); }
    .eyebrow { color: #b9dfff; font-size: 11px; font-weight: 800; letter-spacing: .22em; }
    h1 { margin: 12px 0; font: 500 clamp(46px, 10vw, 92px)/.92 "STKaiti", "KaiTi", serif; }
    .copy p { color: #c2d5e6; line-height: 1.7; }
    .controls { position: fixed; z-index: 3; right: 22px; bottom: 22px; width: min(310px, calc(100% - 44px)); padding: 18px; border: 1px solid rgba(255,255,255,.2); border-radius: 18px; background: rgba(7,22,43,.72); backdrop-filter: blur(12px); }
    .control { display: grid; grid-template-columns: 58px 1fr 40px; gap: 9px; align-items: center; color: #cadbea; font-size: 12px; }
    .control + .control { margin-top: 14px; }
    input { width: 100%; accent-color: #fff7d3; }
    output { text-align: right; color: #fff7d3; font: 700 11px/1 "Courier New", monospace; }
    @media (max-width: 600px) { .copy p { max-width: 75%; } .cabin { left: 10vw; bottom: 26vh; transform: scale(.72); } }
  </style>
</head>
<body>
  <div class="moon" aria-hidden="true"></div><div class="mountains" aria-hidden="true"></div><div class="cabin" aria-hidden="true"></div>
  <canvas id="snow" aria-hidden="true"></canvas>
  <main><section class="copy"><span class="eyebrow">A QUIET WINTER NIGHT</span><h1>La neige tombe en silence</h1><p>Ajustez la quantité de neige et la direction du vent pour créer votre propre nuit d’hiver. Le canevas s’adapte automatiquement à la fenêtre et aux écrans haute définition.</p></section></main>
  <aside class="controls" aria-label="Paramètres de chute de neige">
    <label class="control"><span>Quantité de neige</span><input id="amount" type="range" min="30" max="220" value="120"><output id="amountValue">120</output></label>
    <label class="control"><span>Direction du vent</span><input id="wind" type="range" min="-12" max="12" value="2"><output id="windValue">+0.2</output></label>
  </aside>
  <script>
    const canvas = document.querySelector('#snow');
    const context = canvas.getContext('2d');
    const amountInput = document.querySelector('#amount');
    const windInput = document.querySelector('#wind');
    const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
    let width = 0, height = 0, flakes = [];

    function makeFlake(randomY = true) {
      return { x: Math.random() * width, y: randomY ? Math.random() * height : -12, radius: 1 + Math.random() * 3.2, speed: .35 + Math.random() * 1.25, drift: Math.random() * Math.PI * 2, opacity: .35 + Math.random() * .65 };
    }
    function syncAmount() {
      const amount = Number(amountInput.value);
      while (flakes.length < amount) flakes.push(makeFlake());
      flakes.length = amount;
      document.querySelector('#amountValue').value = String(amount);
      draw();
    }
    function resize() {
      const ratio = Math.min(devicePixelRatio || 1, 2);
      width = innerWidth; height = innerHeight;
      canvas.width = Math.round(width * ratio); canvas.height = Math.round(height * ratio);
      canvas.style.width = `${width}px`; canvas.style.height = `${height}px`;
      context.setTransform(ratio, 0, 0, ratio, 0, 0);
      flakes = Array.from({ length: Number(amountInput.value) }, () => makeFlake());
      draw();
    }
    function draw() {
      context.clearRect(0, 0, width, height);
      context.fillStyle = '#ffffff';
      flakes.forEach(flake => { context.globalAlpha = flake.opacity; context.beginPath(); context.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2); context.fill(); });
      context.globalAlpha = 1;
    }
    function animate() {
      const wind = Number(windInput.value) / 10;
      flakes.forEach(flake => {
        flake.y += flake.speed;
        flake.x += wind + Math.sin(flake.drift += .012) * .25;
        if (flake.y > height + 10 || flake.x < -20 || flake.x > width + 20) Object.assign(flake, makeFlake(false));
      });
      draw(); requestAnimationFrame(animate);
    }
    amountInput.addEventListener('input', syncAmount);
    windInput.addEventListener('input', () => { const wind = Number(windInput.value) / 10; document.querySelector('#windValue').value = `${wind >= 0 ? '+' : ''}${wind.toFixed(1)}`; });
    addEventListener('resize', resize); resize(); if (!reduceMotion) requestAnimationFrame(animate);
  </script>
</body>
</html>
Commentaires sur la pageVous avez découvert un problème ou avez une suggestion d’amélioration ?
Les fonctions de retour nécessitent l’utilisation de la version en ligne

Veuillez activer JavaScript dans la version en ligne avant de soumettre ; les fonctionnalités locales de l’outil ou du cas ne sont pas affectées.

Aller vers la version en ligne pour envoyer des commentaires