Blur
BêtaEffetUne profondeur de champ pour n'importe quel contenu : net autour d'un point, flou en s'en éloignant. Le point suit le pointeur, le défilement ou votre application.
- Frameworks
- ReactVueVanilla
- Technologies
- CSS
- Déclenché par
- PointeurDéfilementPiloté par l'application
- Mis à jour le
- Construit sur
- FrameLoopPointerTrackerViewProgress
Blur donne à un contenu une profondeur de champ, comme une photo prise à grande ouverture : net autour d'un point de mise au point, de plus en plus flou en s'en éloignant. Le point suit le pointeur, le défilement ou les valeurs de votre application. Un même composant donne ainsi une mise au point à la souris, un tilt-shift, une ligne de lecture ou des bords progressifs. Réservez-le aux moments où le flou sert le contenu (une photo, un récit, une mise en avant), pas à une interface que l'on utilise sans cesse.
Installation
Installez l'élément et ses dépendances en une commande :
pnpm dlx shadcn@latest add https://o-ui.hd987.dev/r/blur.jsonBlur n'a aucune dépendance ; ses adaptateurs utilisent seulement cn de @/lib/utils. Il s'appuie sur trois primitives d'O-UI, installées avec lui : FrameLoop, PointerTracker et ViewProgress. Les prérequis communs (Tailwind CSS v4, React 19 ou Vue 3.5) sont décrits dans Installation.
Utilisation
Enveloppez le contenu : une image, une carte, un titre, une section.
import { Blur } from "@/components/o-ui/blur/blur";
export function Cover() {
return (
<Blur className="overflow-hidden rounded-2xl">
<img
src="/lake.webp"
alt="A jetty on a misty lake"
className="aspect-video w-full object-cover"
/>
</Blur>
);
}Sans aucun réglage, le centre est net et les bords se floutent ; la netteté suit la souris sur l'image, puis revient au centre. Quelques règles font la différence :
- Donnez la forme du cadre à Blur. Le flou est dessiné par des calques posés sur l'élément, qui reprennent son arrondi : placez
rounded-*sur Blur. Ajoutezoverflow-hiddensi le contenu bouge (parallax,tilt) : il ne dépassera pas du cadre. - Le contenu reste le vôtre. Rien n'est cloné : liens, boutons, sélection de texte et lecteurs d'écran fonctionnent comme d'habitude. Les calques sont vides, masqués aux technologies d'assistance et transparents au pointeur.
- Le flou ne déborde pas de l'élément. Son contour reste franc, comme le bord d'une photo, et un contenu flou près du bord y est coupé. Autour d'un texte, laissez une marge intérieure (
p-6). - Pour garder un élément net au-dessus du flou (un badge, une légende, un bouton), donnez-lui une position (
relativeouabsolute) etz-[2]: il passe au-dessus des calques. Avecparallaxoutilt, faites-en aussi un plandata-blur-depth(voir Plans en profondeur) : sinon il bouge avec le contenu et repasse sous le flou. - Mettez
relativevous-même sur Blur si le contenu contient des éléments enabsolute. Blur l'ajoute à un élément qui n'est pas positionné, mais seulement une fois le script chargé, et le retire quand l'effet est désactivé. - Mettez la mise en page dans le contenu. Le composant enveloppe vos enfants dans un
<div>intérieur, qui porte le parallax : unflexou ungridposé sur Blur n'agit pas sur eux. Placez-le sur un élément à vous, à l'intérieur.
Exemples
Tilt-shift
Une bande de netteté très fine (shape="band", focusSize={0.05}), un peu inclinée (angle={-4}), avec une transition courte (falloff={0.3}). Au repos, elle traverse les rochers ; elle suit ensuite le pointeur de haut en bas. Le paysage prend l'allure d'une maquette.
Aperçu discret
Avec idle="blur", la photo est entièrement floue au repos ; une fenêtre nette s'ouvre là où l'on regarde et se referme quand le pointeur s'en va. Le badge porte absolute z-[2] et reste net au-dessus du flou.
- La photo est dans un bouton (« View the 24 photos ») : au clavier, son focus rend la photo nette, comme le survol à la souris.
- Sur un écran tactile, sans survol possible, la photo montre son point de repos, net au centre.
- Le flou reste décoratif : le contenu est net au chargement de la page, au clavier et en contraste élevé. Ne l'utilisez jamais pour cacher une information.
Plans en profondeur
Chaque élément marqué data-blur-depth devient un plan, d'une profondeur de -4 à 4 (1 sans valeur). Avec parallax={18}, il se décale vers le pointeur de 18 px fois sa profondeur, quand le pointeur est au bord de l'image : le fond (-0.5) part à l'opposé, la photo du vase (0.6) suit un peu, l'étiquette (1.4) davantage. La netteté suit le pointeur d'un plan à l'autre, et l'étiquette, en z-[2], reste lisible.
- Donnez au fond une marge qui déborde du cadre (
-inset-6ici) : il ne doit pas découvrir ses bords en glissant. - Sans plan marqué, c'est le contenu entier qui se décale ;
tiltl'incline en plus vers le pointeur. - Un plan placé dans un autre plan cumule les deux décalages.
Visite guidée
Avec trigger="none", Blur ne suit plus le pointeur : c'est l'application qui place la mise au point, avec focusX et focusY (des fractions de la largeur et de la hauteur). Chaque changement est rejoint en douceur, en duration={0.8} seconde. Ici, chaque élément de la liste attire la netteté sur son détail, au survol, au clic ou au focus clavier.
Ouverture au défilement
Avec trigger="scroll" et scrollMode="reveal", la photo arrive floue, puis la netteté s'ouvre depuis le sommet de la montagne (focusX={0.76}, focusY={0.3}) pendant qu'elle entre dans la vue. L'ouverture va de rangeStart="entry 20%" à rangeEnd="cover 50%", quand la photo est au milieu de l'écran. Elle se referme si l'on remonte. La forme ellipse suit les proportions de la photo et laisse ses coins légèrement flous.
Ligne de lecture
Avec trigger="scroll" (et scrollMode="track", la valeur par défaut), la mise au point reste sur le milieu de la zone qui défile, pendant que le texte passe dessous. Une bande (shape="band") et un flou léger (blur={4}) gardent le texte autour lisible.
Bords progressifs
Le hook useBlur, en React, et le composable du même nom, en Vue, s'appliquent à un élément existant, sans enveloppe. Ici, une bande large et fixe (trigger="none", focusSize={0.3}, falloff={0.2}) floute progressivement le haut et le bas d'une liste qui défile.
- Appliquez Blur à un cadre qui ne défile pas, autour de la zone qui défile : les calques restent en place et la liste passe dessous.
- Les calques ne changent pas pendant le défilement : le navigateur recompose seul le flou, sans aucun calcul en JavaScript.
- La zone qui défile est focalisable, pour défiler au clavier. Pendant ce focus, les bords redeviennent nets, comme tout contenu de Blur au focus clavier.
Personnalisation
Le même composant produit des effets très différents. Ces combinaisons sont un bon point de départ :
| Effet | Réglages |
|---|---|
| Mise au point (défaut) | aucun |
| Vignette | shape="ellipse" focusSize={0.45} trigger="none" |
| Tilt-shift | shape="band" focusSize={0.05} falloff={0.3} angle={-4} |
| Projecteur | idle="clear" : tout est net au repos ; au survol, seul le voisinage du pointeur reste net |
| Fenêtre au survol | idle="blur" falloff={0.3} |
| Pinceau de flou | invert focusSize={0.08} falloff={0.2} : le pointeur floute, le reste reste net |
| Ligne de lecture | trigger="scroll" shape="band" blur={4} |
| Ouverture en iris | trigger="scroll" scrollMode="reveal" shape="ellipse" |
| Bords progressifs | trigger="none" shape="band" focusSize={0.3} falloff={0.2} |
| Profondeur | parallax={16} et des plans data-blur-depth, ou tilt={4} pour un contenu d'un bloc |
Les options se répartissent en trois groupes :
- L'optique.
blurrègle le flou le plus fort,shapela forme de la zone nette,focusSizesa taille etfalloffla longueur de la transition. Les deux tailles sont des fractions du plus petit côté de l'élément : le rendu reste le même sur une carte comme sur une image plein écran.invertéchange la zone nette et la zone floue, etangleoriente une bande. - Le pilotage.
triggerchoisit ce qui déplace la mise au point : le pointeur, le défilement, les deux (le pointeur d'abord) ou rien.idledit ce que montre l'élément sans pointeur au-dessus, avectriggerpointerounone; au défilement, c'est la position de l'élément qui décide.focusXetfocusYplacent le point de repos. - Le mouvement.
durationrègle le temps que met la mise au point à suivre.parallax,tiltetperspectivefont bouger le contenu avec le pointeur.
Chaque option peut changer en cours de route, sans saut : la mise au point rejoint sa nouvelle place, et une nouvelle amplitude de parallax ou de tilt est rejointe au même rythme.
Intégration
Next.js et Nuxt
Le rendu serveur produit l'élément et son contenu, nets. Les calques s'ajoutent à l'hydratation, et la profondeur de champ apparaît en fondu, en duration secondes (0,5 par défaut).
- Next.js : Blur est un composant client, utilisable dans un Server Component. Un fichier qui ajoute un état ou une
ref, comme la visite guidée, doit lui-même commencer par"use client". Les exemples portent tous la directive, pour se copier tels quels où que ce soit. next/image: avec<Image fill>, l'élément positionné est Blur lui-même ; donnez-luirelativeet une taille (aspect-video, par exemple).- Nuxt : aucun
<ClientOnly>n'est nécessaire. Importez Blur explicitement : l'import automatique de Nuxt préfixe le nom d'un composant par son dossier.
Piloter la mise au point
Passez trigger="none", puis changez focusX, focusY ou idle depuis votre état : chaque changement est animé. Passer idle de "blur" à "focus" ouvre la netteté en iris, par exemple à la fin d'un chargement.
"use client";
import { useState } from "react";
import { Blur } from "@/components/o-ui/blur/blur";
export function Reveal({ src, alt }: { src: string; alt: string }) {
const [loaded, setLoaded] = useState(false);
return (
<Blur trigger="none" idle={loaded ? "focus" : "blur"} shape="ellipse" focusSize={0.5}>
<img src={src} alt={alt} onLoad={() => setLoaded(true)} />
</Blur>
);
}Utiliser sans enveloppe
Pour appliquer Blur à un élément de votre propre composant, utilisez le hook useBlur en React ou le composable du même nom en Vue. L'élément que vous passez reçoit les calques ; avec parallax ou tilt, c'est son premier enfant qui bouge, ou l'élément target.
"use client";
import { useRef } from "react";
import { useBlur } from "@/components/o-ui/blur/blur";
export function Photo({ src, alt }: { src: string; alt: string }) {
const frameRef = useRef<HTMLDivElement>(null);
const imageRef = useRef<HTMLImageElement>(null);
useBlur(frameRef, { target: imageRef, parallax: 12, shape: "ellipse" });
return (
<div ref={frameRef} className="overflow-hidden rounded-xl">
<img ref={imageRef} src={src} alt={alt} className="scale-105" />
</div>
);
}- Les options sont celles du composant, plus
target: une ref ou un élément en React ; un élément, unerefde template ou un getter en Vue. Elles s'appliquent en direct, et une option retirée revient à sa valeur par défaut. - En Vue, les options sont un objet, une
refou un getter : utilisez un getter quand elles dépendent d'un état. - Passez un conteneur (
<div>,<figure>), jamais une liste (<ul>,<ol>) ni un tableau : Blur y ajoute un<div>pour ses calques. - Le hook et le composable ne renvoient rien. Le comportement est celui du composant.
- L'image agrandie (
scale-105) garde une marge : en se décalant, elle ne découvre pas ses bords.
Dans une zone qui défile
Au défilement, Blur suit la première zone qui défile autour de lui, ou la fenêtre (source="auto"). Passez source="viewport" pour suivre la fenêtre malgré une zone intermédiaire, ou l'élément qui défile lui-même. axis="x" suit un défilement horizontal.
En React, passez cet élément depuis un état : une ref.current lue pendant le rendu vaut encore null, et serait ignorée.
const [scroller, setScroller] = useState<HTMLElement | null>(null);
<div ref={setScroller} className="h-96 overflow-y-auto">
<Blur trigger="scroll" shape="band" source={scroller ?? "auto"}>
…
</Blur>
</div>;Accéder à l'élément
La ref donne l'élément qui reçoit les calques, la racine de Blur. En Vue, la ref posée sur le composant expose { el }. Blur n'a pas d'API impérative : pilotez-le par ses props.
API
| Prop | Type | Défaut | Description |
|---|---|---|---|
blur | number | 12 | Flou des zones les plus floues, en pixels CSS, de 0 à 64. Entre 8 et 16 pour une photo, 3 à 5 pour du texte. À 0, rien n'est flou. |
shape | BlurShape | "circle" | `"circle"` : une zone nette ronde, comme un objectif. `"ellipse"` : la même, aux proportions de l'élément, comme une vignette. `"band"` : une bande nette, comme un tilt-shift ou une ligne de lecture. |
focusSize | number | 0.18 | Rayon de la zone nette (ou demi-largeur d'une bande), en fraction du plus petit côté de l'élément, de 0 à 1. À 0, seul le point visé est net. |
falloff | number | 0.6 | Distance sur laquelle le flou passe de nul à `blur`, en fraction du plus petit côté de l'élément, de 0,05 à 2. Courte, la transition est franche ; longue, elle est très progressive. |
angle | number | 0 | Orientation d'une bande, en degrés, de -90 à 90 : 0 la rend horizontale, 90 verticale. Sans effet sur les autres formes. |
focusX | number | 0.5 | Position du point de repos sur la largeur, de -1 à 2 : 0 au bord gauche, 1 au bord droit. Au-delà, le point sort de l'élément, ce qui ne laisse qu'un bord net. |
focusY | number | 0.5 | Position du point de repos sur la hauteur, de -1 à 2 : 0 en haut, 1 en bas. |
invert | boolean | false | Booléen : floute la zone de mise au point et garde le reste net. Le pointeur devient un pinceau de flou. |
idle | BlurIdle | "focus" | Ce que montre l'élément sans pointeur au-dessus, avec `trigger` `"pointer"` ou `"none"`. `"focus"` : la mise au point est au point de repos. `"clear"` : tout est net, le flou n'apparaît qu'au survol. `"blur"` : tout est flou, une fenêtre nette s'ouvre au survol. Avec `trigger="pointer"`, sur un écran tactile, `"blur"` montre le point de repos. |
trigger | "both" | "none" | "pointer" | "scroll" | "pointer" | `"pointer"` : la mise au point suit une souris ou un stylet au-dessus de l'élément. `"scroll"` : elle suit le défilement. `"both"` : le pointeur d'abord, le défilement sinon. `"none"` : seules les options la déplacent. |
scrollMode | "reveal" | "track" | "track" | `"track"` : la mise au point reste sur le milieu de la zone qui défile. `"reveal"` : la netteté s'ouvre pendant que l'élément entre dans la vue, de `rangeStart` à `rangeEnd`, et se referme au retour. |
axis | ViewAxis | "y" | `"y"` pour un défilement vertical, `"x"` pour un défilement horizontal. |
rangeStart | ViewRange | "entry 0%" | Où commence l'ouverture de `reveal`, comme `animation-range-start` en CSS : un nom (`entry`, `cover`, `contain`, `exit`, `entry-crossing`, `exit-crossing`) suivi d'un pourcentage. `entry 0%` : le haut de l'élément touche le bas de la zone visible. `cover 50%` : son milieu est au milieu de la zone. |
rangeEnd | ViewRange | "entry 100%" | Où l'ouverture de `reveal` est complète, avec la même syntaxe. `entry 100%`, la valeur par défaut : l'élément est entièrement entré. |
source | ViewSource | "auto" | `"auto"` : la première zone qui défile autour de l'élément, ou la fenêtre. `"viewport"` : la fenêtre. Un élément : cette zone, qui doit contenir Blur. |
duration | number | 0.5 | Temps que met la mise au point à rejoindre sa nouvelle place, en secondes, de 0,05 à 2, sans rebond. Le parallax et le fondu d'entrée suivent au même rythme. |
parallax | number | 0 | Décalage maximal du contenu vers le pointeur, en pixels CSS, de -80 à 80, atteint quand le pointeur est au bord de l'élément. Négatif, le contenu s'éloigne du pointeur. Multiplié par la profondeur de chaque plan `data-blur-depth`. |
tilt | number | 0 | Inclinaison du contenu vers le pointeur, en degrés, de 0 à 20. Sans plans `data-blur-depth` uniquement. Entre 2 et 6 pour rester discret. |
perspective | number | 1000 | Distance du point de vue pour `tilt`, en pixels CSS, de 200 à 4000. Plus courte, l'inclinaison est plus prononcée. |
disabled | boolean | false | Booléen : retire l'effet sans démonter le composant. Les calques disparaissent et l'élément retrouve ses styles. |
reducedMotion | ReducedMotion | "user" | `"user"` suit le réglage du système, `"always"` immobilise toujours le contenu, `"never"` le laisse bouger même si la personne a demandé moins d'animations : à réserver au cas où elle les a réactivées dans les réglages de votre site. |
Les nombres hors plage sont ramenés dans la plage. Une valeur invalide (NaN, mauvais type, valeur inconnue) garde la valeur courante, ou la valeur par défaut à la création. Le playground ne montre que ce qui se voit dans son cadre, avec des plages utiles : les modes au défilement s'essaient dans leurs exemples, et perspective comme reducedMotion ne s'y règlent pas. Les props acceptent toute la plage ci-dessus.
Les adaptateurs transmettent les attributs natifs à la racine, un <div>, ainsi que className en React et class en Vue. Le contenu est placé dans un <div> intérieur (data-slot="blur-target") qui porte le parallax. En React, le type BlurProps décrit toutes les props. Le cœur, le hook et le composable ajoutent l'option target, l'élément à déplacer.
Chaque descendant marqué data-blur-depth (un nombre de -4 à 4, 1 par défaut) devient un plan qui se décale de parallax fois sa profondeur.
Accessibilité
Ce que fait Blur
- Blur ne touche pas au contenu : rôles, noms, liens et boutons restent les mêmes. Ses calques sont vides, en
aria-hiddenet transparents au pointeur : la sélection de texte et les clics passent au travers. - Au focus clavier d'un élément qu'il contient (ou de l'élément qui le contient), tout redevient net en 0,15 s et le contenu déplacé revient aussitôt à sa place : l'anneau de focus ne bouge pas. Le flou revient quand le focus part.
- Seuls une souris ou un stylet déplacent la mise au point. Sur un écran tactile, l'élément montre son point de repos (tout net avec
idle="clear") ; le défilement agit normalement. - Rien ne bouge de lui-même : la mise au point ne change qu'avec le pointeur, le défilement ou vos props. L'effet ne demande donc aucun contrôle de pause.
Ce que vous devez faire
- Garder lisible ce qui compte. Un flou est décoratif : un texte essentiel, un prix, une action ne doivent pas dépendre du survol pour être lus. Placez-les hors de l'élément, ou au-dessus du flou (
relative z-[2]). - Avec
idle="blur", placer un élément focalisable dans Blur (un lien ou un bouton vers la galerie) : son focus clavier montre le contenu net, comme le survol. - Garder un flou léger sur du texte (3 à 5 px) : les lignes autour de la ligne de lecture restent déchiffrables.
Mouvement réduit. Quand la personne demande moins d'animations, le contenu ne bouge plus (parallax et tilt sont ignorés). La mise au point continue de suivre le pointeur ou le défilement : elle change la netteté, sans rien déplacer. Le réglage système s'applique en direct, et la prop reducedMotion permet de le forcer ou de l'ignorer.
Contraste élevé. En forced-colors ou avec une préférence de contraste renforcé (prefers-contrast: more), Blur retire ses calques et coupe le parallax : tout est net et immobile. Le retour à la normale est immédiat quand la préférence change.
Performances
- Le cœur et ses fichiers annexes pèsent environ 5,8 kB minifiés et compressés (14 kB minifiés), sans dépendance. Avec FrameLoop, PointerTracker et les fonctions de ViewProgress qu'il utilise, partagés avec les autres éléments d'O-UI, l'ensemble pèse 8 kB.
- Le flou est calculé par le navigateur, sur le GPU, avec
backdrop-filter. Des calques empilés, chacun un peu plus flou que le précédent, dessinent un dégradé continu ; leur nombre suitblur, de 1 à 8 (6 pour 12 px, 8 au-delà de 24 px). - Seuls les masques des calques changent quand la mise au point bouge : jamais la mise en page. Sur un Apple M5, avec Chrome et un écran Retina, une photo de 820 × 460 px demande 1,3 ms par image pendant que le pointeur bouge (3 ms à
blur={40}), sur un budget de 16 ms. - Rien ne tourne au repos, et une instance éloignée de l'écran n'écoute ni ne mesure rien. Toutes les instances partagent une seule boucle d'animation et les mêmes écouteurs de pointeur et de défilement. Pendant un défilement, la ligne de lecture garde la cadence de l'écran, même avec un processeur ralenti quatre fois.
- Le coût du flou suit la surface couverte. Pour de grandes surfaces ou beaucoup d'instances visibles en même temps, préférez un
blurmodéré, ettrigger="none"quand la mise au point n'a pas besoin de bouger : les calques sont alors immobiles.
Problèmes fréquents
- Rien n'est flou. Le navigateur ne prend pas en charge
backdrop-filter(voir les notes), une préférence de contraste est active, ou un élément a le focus clavier dans Blur. - Un élément du contenu reste net. Il a un
z-indexsupérieur à 1 : il passe au-dessus des calques. Retirez sonz-index, ou donnez-luiz-[1]s'il doit rester positionné. - Les coins du flou dépassent de l'arrondi. Placez
rounded-*sur Blur lui-même : ses calques reprennent l'arrondi de l'élément qui les reçoit. - Le flou ne suit pas le défilement. Blur suit la première zone qui défile autour de lui : passez
source="viewport"ou l'élément qui défile. Pour des bords progressifs, appliquez Blur au cadre autour de la zone qui défile, jamais à la zone elle-même. - Le contenu découvre ses bords avec le parallax. Donnez-lui une marge : une image agrandie (
scale-105) ou un fond qui déborde (-inset-6). - Une transformation ajoutée après coup est écrasée pendant le parallax. Blur écrit
transformen ligne sur l'élément déplacé, à partir de celle qu'il avait au départ. Utilisez plutôttranslate,rotateetscale(les propriétés des classes Tailwind v4), que Blur ne touche pas, ou placez la transformation sur un élément intérieur. - Un élément en
absolutesort du cadre au chargement. Blur n'est positionné qu'une fois le script chargé : mettezrelativesur Blur vous-même.
Notes et limites
- Blur demande
backdrop-filteretmask-image, présents dans tous les navigateurs récents : Chrome et Edge 87 et plus, Safari 14.1 et plus, Firefox 103 et plus. Sans eux, le contenu reste net et le parallax fonctionne. - Le flou est celui d'un objectif, pas celui d'une vraie scène en 3D : il dépend de la distance au point de mise au point sur l'écran, pas de la profondeur de chaque élément. Les plans
data-blur-depthrèglent seulement leur parallax. - Les calques floutent tout ce qui est peint sous l'élément, fond de page compris là où l'élément est transparent. Un contenu qui déborde de l'élément n'est pas flouté.
- Blur donne à l'élément
position: relatives'il n'était pas positionné, etisolation: isolatepour garder ses calques à l'intérieur. Les deux sont retirés à la destruction. - Si la page se réorganise sans défilement ni redimensionnement alors que le pointeur est immobile, la mise au point garde sa place jusqu'au prochain mouvement.