Blog / Canvas & Effets visuels

Metaballs en Canvas : créer un effet de sang liquide (filtre blur + contrast)

Du sang qui goutte, fusionne et poole — en metaballs avec Canvas pur et l'astuce CSS blur + contrast. Physique de gouttes, glob au curseur, zéro asset, zéro bibliothèque. Code vanilla copier-coller.

Les metaballs — ces formes organiques qui fusionnent quand elles se rapprochent, comme des gouttes de mercure ou de lave — ont longtemps été réputées coûteuses : le calcul exact passe par un champ scalaire évalué pour chaque pixel. Cet article décrit l'effet Cursed Blood Goo, l'effet gratuit du sprint Halloween d'octobre 2026 sur Effect.Labs : du sang liquide qui goutte, fusionne et s'accumule en flaque, avec un glob qui suit le curseur — le tout en Canvas pur et une seule ligne de filtre CSS, sans aucune bibliothèque ni asset, à 60fps.

Définition — Metaballs. Des « blobs » dont les surfaces se soudent dès qu'ils se chevauchent, donnant un rendu liquide/gélatineux. La méthode classique calcule, pour chaque pixel, la somme des influences de chaque blob et seuille le résultat. On va obtenir le même rendu sans ce coût par pixel.

L'astuce : blur + contrast

La technique (popularisée par Lucas Bebber) tient en deux filtres enchaînés sur la couche qui contient les blobs :

  1. blur() étale les bords de chaque blob en un dégradé doux. Deux blobs proches voient leurs halos flous se superposer.
  2. contrast() élevé écrase ces valeurs intermédiaires : tout ce qui est sous le seuil bascule vers le fond, tout ce qui est au-dessus vers la couleur pleine. Résultat : un bord net reconstitué, et là où deux halos se chevauchaient, une seule masse organique soudée.
.goo-scene { background: #0b0406; }            /* fond OPAQUE sombre, indispensable */
.goo-canvas { filter: blur(7px) contrast(16); } /* le combo metaball */
Le piège n°1. contrast() agit sur les canaux de couleur, jamais sur l'alpha. Il faut donc dessiner des blobs pleins sur un fond opaque sombre : le blur mélange la couleur du blob avec le fond, puis le contrast re-seuille. Sur un canvas transparent, aucune fusion ne se produit — c'est l'erreur la plus fréquente.

Dessiner les gouttes

Côté Canvas, c'est volontairement bête : on remplit le fond opaque, puis on dessine chaque goutte comme un simple cercle plein de la couleur du sang. Tout le « liquide » vient du filtre CSS appliqué par-dessus.

const ctx = canvas.getContext('2d');
const COLOR = '#c81e2d';

function render() {
  ctx.fillStyle = '#0b0406';           // fond opaque (re-seuillé en noir par contrast)
  ctx.fillRect(0, 0, W, H);
  ctx.fillStyle = COLOR;
  for (const d of drops) {             // chaque goutte = un disque plein
    ctx.beginPath();
    ctx.arc(d.x, d.y, d.r, 0, 6.2832);
    ctx.fill();
  }
}

La physique : chute, flaque, curseur

Trois comportements donnent vie au sang, tous very cheap :

1. La dégoulinade

On fait apparaître une goutte en haut à intervalle régulier, soumise à la gravité. Quelques dizaines suffisent — la fusion visuelle fait le reste.

if (frame % 11 === 0 && drops.length < 130)
  drops.push({ x: Math.random()*W, y: -12, vx: 0, vy: 1.2,
               r: 9 + Math.random()*9 });

for (const d of drops) { d.vy += 0.28; d.x += d.vx; d.y += d.vy; }

2. La flaque qui monte

Quand une goutte atteint le sol, elle ne disparaît pas : elle nourrit le niveau de la flaque (proportionnel à sa surface). On dessine la flaque comme une surface ondulée par un sinus — et elle s'écoule lentement pour atteindre un équilibre plutôt que déborder.

if (d.y + d.r*0.4 >= H - pool) { pool += d.r*d.r / W * 0.8; remove(d); }
pool = Math.max(0, pool - 0.14);     // drainage lent → équilibre

3. Le glob au curseur

Le curseur porte un gros blob (qui fusionne avec tout ce qu'il touche, grâce au filtre). Quand on bouge vite, il projette des gouttelettes dans la direction du mouvement, et il aspire les gouttes proches. C'est ce qui rend l'effet tactile.

// glob qui suit le curseur (lissé)
bx += (mx - bx) * 0.3; by += (my - by) * 0.3;
// vitesse du curseur → projection de gouttelettes
const speed = Math.hypot(mx - pmx, my - pmy);
if (speed > 13) drops.push({ x: bx, y: by,
  vx: (mx - pmx)*0.07, vy: (my - pmy)*0.07 + 1, r: 6 + Math.random()*6 });

Le code complet (gestion du devicePixelRatio, attraction des gouttes vers le curseur, fond personnalisable) est disponible directement sur la page Effets visuels du cataloguecet effet est gratuit.

Personnaliser : sang, slime, encre, lave

Une seule variable de couleur transforme l'effet. Deux ou trois réglages suffisent à changer l'ambiance :

ParamètrePlageEffet
Couleur#c81e2d / #5ad12e / #111 / #ff7a18Sang / slime toxique / encre / lave
contrast()10 → 22Plus élevé = bords plus nets, plus « gélatineux »
blur()5 → 10 pxÉpaisseur de la fusion entre gouttes
cadence frame % N6 → 20Débit de la dégoulinade

Pensez à garder le fond opaque cohérent avec la teinte (un vert très sombre pour le slime, par exemple), sinon le re-seuillage du contraste vire au gris.

Performance et accessibilité

Un seul canvas, ~100 gouttes, un filtre GPU : l'effet tient le 60fps sur mobile récent. Quelques garde-fous :

  • Plafonner le nombre de gouttes (130 ici) et le devicePixelRatio à 2.
  • Le filtre blur()+contrast() est exécuté par le compositeur GPU — bien plus rapide qu'un champ scalaire calculé en JS.
  • prefers-reduced-motion : on dessine une scène figée (flaque + quelques gouttes au repos), sans boucle d'animation. L'effet est purement décoratif, le contenu reste accessible.

Ce que ChatGPT et v0 ne font pas

Demandez des « metaballs en canvas » à un générateur : vous obtenez presque toujours un champ scalaire par pixel (double boucle sur la largeur × hauteur), qui rame dès qu'on dépasse quelques blobs. Ce que l'IA rate :

  • L'astuce blur + contrast : la solution GPU « gratuite » est rarement proposée spontanément.
  • Le fond opaque obligatoire : le code IA dessine sur un canvas transparent → aucune fusion, et personne ne comprend pourquoi.
  • La physique qui donne vie : flaque qui monte, drainage à l'équilibre, projection de gouttelettes à la vitesse du curseur.
  • devicePixelRatio : oublié → rendu flou sur Retina (ironique pour un effet déjà flouté).
  • prefers-reduced-motion : règle d'accessibilité régulièrement absente des résultats générés.

Questions fréquentes

Le filtre blur + contrast fonctionne-t-il aussi en DOM/SVG ?

Oui : c'est même son usage d'origine (sur des div ou un filtre SVG feGaussianBlur + feColorMatrix). Sur Canvas on garde un seul élément à composer, ce qui reste le plus simple et le plus rapide.

Peut-on l'utiliser avec React, Vue ou Svelte ?

Oui : code vanilla JS, aucune dépendance npm. Dans React, encapsuler la boucle dans un useEffect avec cleanup (cancelAnimationFrame) ; dans Svelte, onMount/onDestroy.

Le code complet, prêt à l'emploi

Cet effet est gratuit ce mois-ci. Customisez la couleur en direct et copiez-collez le code. Accès à 800+ autres effets premium avec l'abonnement.

Rejoindre les fondateurs — 9,90 € / mois