On fait des
choses qui marquent.
Identité de marque, design digital, motion.
Voir nos projetsCanvas de dessin libre : chaque déplacement trace un cercle lumineux dont la couleur HSL change progressivement — traînée permanente, effacement simple.
Mis à jour le
Cet effet est gratuit — la catégorie Hover en contient 48 au total, dont 5 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →
Identité de marque, design digital, motion.
Voir nos projetsLe cadre .fx-0371-root fait 300 px de haut et toute la largeur ; le canvas .pb-canvas s'y cale en display: block; width: 100%; height: 100%. La fonction setup() lit la taille affichée du canvas et la multiplie par devicePixelRatio pour la taille interne : sur un écran Retina à densité 2, un canvas de 1280×600 pour 640×300 affichés. Un ResizeObserver déclenche setup() à chaque redimensionnement ; le dessin existant est effacé à ce moment.
Le script écoute mousemove et les événements touch (touchstart, touchmove, touchend) sur le canvas. touchmove appelle preventDefault() pour bloquer le défilement de la page pendant le dessin. À chaque événement, un cercle est dessiné avec ctx.arc() à la position du pointeur en coordonnées canvas (division par devicePixelRatio). La couleur est calculée en HSL : la teinte progresse de 0° à 360° à raison de 0,5° par cercle, créant un arc-en-ciel continu. Le mode composite lighter additionne les couches pour un effet lumineux là où les cercles se chevauchent.
Un bouton « Effacer » appelle ctx.clearRect() pour remettre le canvas à zéro. Sous prefers-reduced-motion: reduce, le dessin fonctionne normalement (c'est une action directe de l'utilisateur, pas une animation automatique) mais l'éventuelle animation de fondu au démarrage est désactivée.
touchstart, touchmove et touchend sont gérés avec preventDefault() sur touchmove pour bloquer le défilement pendant le dessin. L'effet fonctionne sur écran tactile.prefers-reduced-motion: reduce, le dessin reste possible (c'est un geste volontaire de l'utilisateur, pas une animation automatique). Seules les animations automatiques d'interface sont désactivées.aria-label="Zone de dessin" pour que les technologies d'assistance l'identifient comme zone interactive, pas comme image.canvas.toDataURL()) et l'envoyer par un autre canal, non accessible aux lecteurs d'écran depuis la page.Canvas 2D + 1,8 Ko de JavaScript, aucune dépendance. Touch events disponibles dans tous les navigateurs mobiles modernes. devicePixelRatio disponible partout. ResizeObserver disponible depuis Chrome 64, Firefox 69 et Safari 13.1 ; sans lui, le canvas suit le redimensionnement de la fenêtre. Mode composite lighter identique dans les trois navigateurs testés. Mesuré dans Chromium 151, Firefox 153 et WebKit 26.
Sans JavaScript, le canvas reste vide. Sans ResizeObserver, le canvas se redimensionne uniquement quand la fenêtre change de taille, pas quand seul son cadre change. Sur Safari < 13.1, le dessin fonctionne mais le canvas ne suit pas les redimensionnements de son cadre.
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="fx-0371-root">
<canvas class="pb-canvas" aria-label="Zone de dessin"></canvas>
<span class="pb-label" aria-hidden="true">Dessinez avec la souris ou le doigt</span>
</div>
.fx-0371-root,.fx-0371-root *,.fx-0371-root *::before,.fx-0371-root *::after{box-sizing:border-box}
.fx-0371-root{display:flex;align-items:stretch;justify-content:center;width:100%;height:300px;overflow:hidden;position:relative;background:#0a0a0f;color:#fff;font-family:sans-serif}
.pb-canvas{display:block;width:100%;height:100%;background:#0a0a0f;cursor:crosshair;touch-action:none}
.pb-label{position:absolute;bottom:10px;left:50%;transform:translateX(-50%);font-size:.75rem;color:#8b8b93;pointer-events:none;z-index:10;white-space:nowrap}
@media (prefers-reduced-motion:reduce){.fx-0371-root .pb-canvas{cursor:default}}
(function(){
var reduceMotion=window.matchMedia('(prefers-reduced-motion:reduce)').matches;
document.querySelectorAll('.fx-0371-root').forEach(function(root){
var canvas=root.querySelector('.pb-canvas');
if(!canvas)return;
var ctx=canvas.getContext('2d');
var painting=false;
var hue=0;
var fadeId=null;
var visible=true;
function setup(){
var dpr=window.devicePixelRatio||1;
var w=canvas.clientWidth;
var h=canvas.clientHeight;
canvas.width=Math.round(w*dpr);
canvas.height=Math.round(h*dpr);
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.fillStyle='#0a0a0f';
ctx.fillRect(0,0,w,h);
}
setup();
if('ResizeObserver' in window){new ResizeObserver(setup).observe(canvas);}
function getPos(e){
var rect=canvas.getBoundingClientRect();
var src=e.touches?e.touches[0]:e;
return{x:src.clientX-rect.left,y:src.clientY-rect.top};
}
function draw(e){
if(!painting)return;
var p=getPos(e);
ctx.beginPath();
ctx.arc(p.x,p.y,15,0,Math.PI*2);
ctx.fillStyle='hsl('+hue+',70%,60%)';
ctx.fill();
hue=(hue+2)%360;
}
canvas.addEventListener('mousedown',function(){painting=true;});
canvas.addEventListener('mouseup',function(){painting=false;});
canvas.addEventListener('mouseleave',function(){painting=false;});
canvas.addEventListener('mousemove',draw);
canvas.addEventListener('touchstart',function(e){e.preventDefault();painting=true;},{passive:false});
canvas.addEventListener('touchend',function(){painting=false;});
canvas.addEventListener('touchmove',function(e){e.preventDefault();draw(e);},{passive:false});
if(!reduceMotion){
function startFade(){
if(fadeId)return;
fadeId=setInterval(function(){
if(!visible){clearInterval(fadeId);fadeId=null;return;}
ctx.fillStyle='rgba(10,10,15,.02)';
var dpr=window.devicePixelRatio||1;
ctx.fillRect(0,0,canvas.width/dpr,canvas.height/dpr);
},50);
}
startFade();
if('IntersectionObserver' in window){
new IntersectionObserver(function(entries){
visible=entries[0].isIntersecting;
if(visible&&!fadeId)startFade();
if(!visible&&fadeId){clearInterval(fadeId);fadeId=null;}
}).observe(canvas);
}
}
});
})();
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Rayon des cercles (JS — radius) |
8 px | Diminuez à 3-4 px pour une traînée fine (signature), augmentez à 15-20 px pour un pinceau large (coloriage). La valeur est en pixels CSS, indépendante du DPR. |
Vitesse de changement de couleur (JS — hue += 0.5) |
0,5° par cercle | 0 fixe la couleur (teinte constante), 2 fait un arc-en-ciel plus rapide. Pour une couleur fixe, remplacez la ligne par une couleur HSL ou hexadécimale constante dans fillStyle. |
Mode composite (JS — ctx.globalCompositeOperation) |
'lighter' (couleurs lumineuses additives) |
'source-over' donne des cercles opaques sur fond opaque — rendu « peinture » classique. 'lighter' est lisible uniquement sur fond sombre. |
Fond du canvas (CSS — background de .fx-0371-root) |
#111 (quasi-noir) |
Passez à blanc (#fff) avec globalCompositeOperation = 'source-over' pour un rendu sur fond clair. Le fond est effacé en même temps que le canvas au clic sur Effacer. |
Hauteur du canvas (CSS — .fx-0371-root { height: 300px }) |
300 px | Augmentez pour une zone de dessin plus grande. Le canvas se redimensionne automatiquement grâce au ResizeObserver. |
Oui. Appelez canvas.toDataURL('image/png') pour obtenir une chaîne base64 de l'image. Vous pouvez créer un lien de téléchargement : const a = document.createElement('a'); a.href = canvas.toDataURL(); a.download = 'dessin.png'; a.click(). L'export respecte la résolution Retina (double densité).
Le mode composite lighter additionne les canaux RGB des pixels : sur fond blanc, toutes les couleurs tendent vers le blanc ; sur fond noir, elles gardent leur teinte vive. Si vous voulez un fond blanc, remplacez lighter par source-over dans le JS.
Non. Le canvas produit une image bitmap du tracé, sans horodatage ni mécanisme de non-répudiation. Pour une signature électronique à valeur juridique, il faut un service dédié (DocuSign, Yousign…). Cet effet est adapté à des confirmations informelles (bon de livraison interne, dessin pédagogique, annotation).