rond étoile carré
♪Le rond devient étoile, l'étoile devient carré…
Une forme de 12 points lissés en courbes qui passe sans fin du rond à l'étoile puis au carré, environ 2 s par passage avec un départ et une arrivée en douceur, dans un dégradé indigo, violet et rose — un canvas 2D net sur écran Retina, une boucle qui s'arrête hors écran, une étoile fixe si l'utilisateur réduit les animations. Un algorithme d'interpolation, pas d'intelligence artificielle.
Mis à jour le
Cet effet est gratuit — la catégorie Liquid en contient 31 au total, dont 6 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →
rond étoile carré
♪Le rond devient étoile, l'étoile devient carré…
23 œuvres, 4 lieux, un logo qui ne tient jamais en place.
12-15 mars« Les formes » — Cie Pas de Côté
Séance scolaire, jeudi 10 h · 45 min
Top vidéo 3 · en boucle
Le cadre .fx-0411-root fait 250 px de haut et toute la largeur ; le canvas s'y cale (display: block, 100 % × 100 %). Le script lit sa taille affichée, la multiplie par devicePixelRatio pour la taille interne et dessine en pixels CSS grâce à setTransform : mesuré à densité 2, un canvas de 1280 × 500 pour 640 × 250 affichés, donc des bords nets sur Retina. Un ResizeObserver refait ce calcul quand le cadre change de taille ; la forme reste centrée.
Chaque forme est une liste de 12 points en coordonnées relatives (de −0,5 à 0,5) : le rond à 0,35 du centre, l'étoile qui alterne 0,4 et 0,2 (six branches), le carré de côté 0,6. À chaque image, progress avance de 0,008 et chaque point est placé entre sa position dans la forme courante et dans la suivante, avec une courbe 0,5 − 0,5 × cos(progress × π) : départ lent, milieu rapide, arrivée lente. 125 images par passage, soit environ 2 s à 60 images/s. Le contour passe par le milieu entre deux points voisins et prend chaque point comme point de contrôle d'une courbe quadratique : il est arrondi partout et se referme sans arête. La taille suit le plus petit côté du cadre (Math.min(W, H)) et le dégradé linéaire, posé en diagonale sur ±32 % de ce côté, garde les trois couleurs visibles quelle que soit la taille.
Le rond et l'étoile sont décrits dans le même sens, à partir de 3 h : ce passage-là est direct. Le carré, lui, commence à son coin haut gauche : entre l'étoile et le carré, puis entre le carré et le rond, chaque point traverse vers son nouveau côté, la forme se replie et tourne d'un quart de tour avant de se rouvrir. C'est ce pli qui donne au mouvement son côté organique ; pour un passage direct, faites partir le carré à 3 h comme les deux autres (voir la FAQ). La boucle requestAnimationFrame s'arrête dès qu'un IntersectionObserver voit le canvas sortir de l'écran (mesuré : 0 image par seconde hors écran contre 120 à l'écran) et repart à son retour ; dans un onglet masqué, le navigateur la suspend de lui-même.
prefers-reduced-motion: reduce, aucune boucle ne démarre ; le canvas affiche une étoile fixe, redessinée seulement si le cadre change de taille. Mesuré dans Chromium 151, Firefox 153 et WebKit 26 : 0 % de pixels changés en 500 ms.aria-hidden="true" au canvas s'il ne fait que décorer.progress à partir du temps écoulé plutôt que du nombre d'images.Canvas 2D + 4,6 Ko de JavaScript, aucune dépendance. Mesuré dans Chromium 151, Firefox 153 et WebKit 26 : même forme, même dégradé, taille interne doublée à densité 2, hauteur stable après 20 redimensionnements, boucle à l'arrêt hors écran, étoile fixe sous mouvement réduit. Coût mesuré : moins de 0,1 ms de JavaScript par image dans Chromium, de l'ordre de 1 ms dans Firefox. ResizeObserver existe depuis Chrome 64, Firefox 69 et Safari 13.1 ; sans lui, le script écoute le redimensionnement de la fenêtre.
Sans JavaScript, le cadre reste vide sur son fond gris-bleu sombre. Sans IntersectionObserver (navigateurs antérieurs à 2019), la boucle tourne aussi hors écran ; sans ResizeObserver, le canvas suit le redimensionnement de la fenêtre, pas celui de son seul cadre.
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="fx-0411-root">
<div class="ai-shape-morph-container">
<canvas class="ai-shape-canvas"></canvas>
</div>
</div>
.fx-0411-root,
.fx-0411-root *,
.fx-0411-root *::before,
.fx-0411-root *::after {
box-sizing: border-box
}
.fx-0411-root {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 250px;
overflow: hidden;
position: relative;
background: rgb(26, 26, 36);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.ai-shape-morph-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.ai-shape-morph-container .ai-shape-canvas {
display: block;
width: 100%;
height: 100%;
}
(function() {
// ============================================
// AI & Intelligence Effects
// ============================================
// 1. AI Shape Morph
// Une instance par bloc .fx-0411-root : plusieurs copies cohabitent sur une page
document.querySelectorAll('.fx-0411-root').forEach(function(root) {
const canvas = root.querySelector('.ai-shape-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
// Mouvement réduit demandé : une étoile fixe, aucune boucle
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let W = 0,
H = 0;
// Taille interne = taille affichée × devicePixelRatio (net sur écran Retina) ;
// le dessin reste en pixels CSS grâce à setTransform.
function resize() {
const dpr = window.devicePixelRatio || 1;
W = canvas.clientWidth;
H = canvas.clientHeight;
canvas.width = Math.round(W * dpr);
canvas.height = Math.round(H * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
if (reduceMotion) draw();
}
const shapes = [
// Circle points
Array.from({
length: 12
}, (_, i) => {
const a = (i / 12) * Math.PI * 2;
return {
x: Math.cos(a) * 0.35,
y: Math.sin(a) * 0.35
};
}),
// Star points
Array.from({
length: 12
}, (_, i) => {
const a = (i / 12) * Math.PI * 2;
const r = i % 2 === 0 ? 0.4 : 0.2;
return {
x: Math.cos(a) * r,
y: Math.sin(a) * r
};
}),
// Square points
Array.from({
length: 12
}, (_, i) => {
const t = i / 12;
const side = Math.floor(t * 4);
const p = (t * 4) % 1;
const coords = [{
x: -0.3 + p * 0.6,
y: -0.3
},
{
x: 0.3,
y: -0.3 + p * 0.6
},
{
x: 0.3 - p * 0.6,
y: 0.3
},
{
x: -0.3,
y: 0.3 - p * 0.6
}
];
return coords[side];
})
];
let currentShape = reduceMotion ? 1 : 0;
let progress = 0;
let morphPoints = shapes[currentShape].map(p => ({
...p
}));
let raf = 0;
let visible = true;
function draw() {
const nextShape = (currentShape + 1) % shapes.length;
const eased = 0.5 - 0.5 * Math.cos(progress * Math.PI);
for (let i = 0; i < morphPoints.length; i++) {
morphPoints[i].x = shapes[currentShape][i].x + (shapes[nextShape][i].x - shapes[currentShape][i].x) * eased;
morphPoints[i].y = shapes[currentShape][i].y + (shapes[nextShape][i].y - shapes[currentShape][i].y) * eased;
}
ctx.clearRect(0, 0, W, H);
const cx = W / 2;
const cy = H / 2;
const scale = Math.min(W, H);
// Dégradé proportionnel à la forme : ±80 px pour un cadre de 250 px de haut
const g = scale * 0.32;
const gradient = ctx.createLinearGradient(cx - g, cy - g, cx + g, cy + g);
gradient.addColorStop(0, '#6366f1');
gradient.addColorStop(0.5, '#a855f7');
gradient.addColorStop(1, '#ec4899');
// Départ au milieu du dernier côté : les 12 points servent tous de points de contrôle,
// la forme se referme sans arête droite
const n = morphPoints.length;
ctx.beginPath();
ctx.moveTo(cx + (morphPoints[n - 1].x + morphPoints[0].x) / 2 * scale, cy + (morphPoints[n - 1].y + morphPoints[0].y) / 2 * scale);
for (let i = 0; i < n; i++) {
const curr = morphPoints[i];
const next = morphPoints[(i + 1) % n];
const cpx = cx + (curr.x + next.x) / 2 * scale;
const cpy = cy + (curr.y + next.y) / 2 * scale;
ctx.quadraticCurveTo(cx + curr.x * scale, cy + curr.y * scale, cpx, cpy);
}
ctx.closePath();
ctx.fillStyle = gradient;
ctx.fill();
}
function animate() {
progress += 0.008;
if (progress >= 1) {
progress = 0;
currentShape = (currentShape + 1) % shapes.length;
}
draw();
// Hors écran, la boucle s'arrête ; l'IntersectionObserver la relance au retour
raf = visible ? requestAnimationFrame(animate) : 0;
}
resize();
if ('ResizeObserver' in window) new ResizeObserver(resize).observe(canvas);
else window.addEventListener('resize', resize);
if (!reduceMotion) {
if ('IntersectionObserver' in window) {
new IntersectionObserver(function(entries) {
visible = entries[0].isIntersecting;
if (visible && !raf) raf = requestAnimationFrame(animate);
}).observe(canvas);
}
raf = requestAnimationFrame(animate);
}
});
})();
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Durée d'un passage (JS — progress += 0.008) |
0,008 par image, environ 2 s à 60 images/s | 0,004 donne environ 4 s, 0,016 environ 1 s. Pour une durée fixe en secondes, quel que soit l'écran, calculez progress à partir de l'horodatage reçu par animate. |
Taille de la forme (JS — const scale = Math.min(W, H)) |
le plus petit côté du cadre | Math.min(W, H) * 1.3 agrandit la forme de 30 % (le dégradé suit). Au-delà de 1,4, les pointes de l'étoile touchent les bords d'un cadre carré. |
Couleurs (JS — les trois addColorStop) |
#6366f1, #a855f7, #ec4899 |
Remplacez-les par vos couleurs ; le dégradé reste en diagonale, du haut gauche au bas droit. Deux arrêts suffisent pour un dégradé simple. |
Formes (JS — tableau shapes) |
rond, étoile à six branches, carré ; 12 points chacun | Ajoutez une liste de 12 points entre −0,5 et 0,5 pour une nouvelle forme, par exemple un hexagone : r = 0.35 pour les points pairs, 0.3 pour les impairs. Toutes les formes doivent avoir le même nombre de points. |
Hauteur du cadre (CSS — .fx-0411-root { height: 250px }) |
250 px | Le canvas se cale sur le cadre et la forme suit le plus petit côté : 400 px donnent une forme 1,6 fois plus grande. |
Fond (CSS — background de .fx-0411-root) |
rgb(26, 26, 36) |
Le canvas est effacé à chaque image : transparent laisse voir le décor de la page derrière la forme. |
Image en mouvement réduit (JS — reduceMotion ? 1 : 0) |
1, l'étoile | 0 affiche le rond, 2 le carré. |
C'est le nom de l'effet dans le catalogue. Le code ne contient aucun modèle d'intelligence artificielle : il interpole 12 points entre trois listes de coordonnées écrites à la main, avec une courbe d'accélération. Tout tient en une trentaine de lignes lisibles.
Le rond et l'étoile numérotent leurs points à partir de 3 h, le carré à partir de son coin haut gauche : chaque point traverse vers un autre côté, la forme se replie et tourne d'un quart de tour. Pour un passage direct, faites partir le carré à 3 h : remplacez sa liste par des points pris sur ses côtés à 0°, 30°, 60°… (r = 0.3 / Math.max(Math.abs(Math.cos(a)), Math.abs(Math.sin(a)))). Ses coins seront alors légèrement arrondis, aucun point ne tombant pile dessus.
Ces deux effets changent de forme au clic, d'un état à l'autre. AI Shape Morph tourne seul, sans fin, et calcule chaque image sur un canvas : aucune forme intermédiaire n'est sautée, et il n'y a rien à cliquer. Choisissez-le pour un fond ou un logo vivant ; les deux autres pour une réponse à un geste.