Costruisci uno spotlight in Webflow che segue il cursore su un’immagine hero — un cono di luce morbido che rivela la foto dove punti e lascia sciogliere tutto il resto nel buio. Sembra roba da WebGL o da plugin pesante, ma in realtà è solo un gradiente radiale più una delle interazioni più vecchie di Webflow, condita con un pizzico di matematica.
Poi andiamo oltre. Sopra lo spotlight aggiungiamo un’animazione hover con GSAP che scala e ruota leggermente l’immagine, e come gran finale un filtro prisma SVG che scompone l’immagine nei suoi canali rosso, verde e blu e li separa per ottenere un glow premium con shift di colore. Tre tecniche, impilate, che insieme danno una hero che sembra viva davvero.
La parte più bella è quanto ogni pezzo sia abbordabile da solo. Lo spotlight e l’hover sono puro Webflow — zero codice. Il prisma è l’unica parte che richiede uno snippet, ed è già scritto per te nel cloneable. Prendila un livello alla volta e ti porti a casa una manciata di trucchi da riusare ovunque.
Come funziona
Lo spotlight non è affatto luce: è buio con un buco in mezzo. Metti un div a tutto schermo (l’overlay) sopra l’immagine e gli dai un gradiente radiale trasparente al centro e nero ai bordi. Il trucco è stringere gli stop di colore: una sfumatura pigra sembra nebbia, mentre tirare il nero vicino al centro (con un paio di stop semi-trasparenti attorno) ti dà un cerchio di visibilità netto che legge come un vero spotlight. Fondamentale: l’overlay vive tra l’immagine e il contenuto nel navigator, così scurisce la foto ma lascia intatti titolo e testo sopra di essa.
Per farlo seguire il cursore usi l’interazione classica “mouse move over element” sulla sezione, spostando l’overlay da -50vw a 50vw sull’asse X e da -50vh a 50vh sull’asse Y. Sono esattamente le distanze dal centro del viewport a ogni bordo, così il centro dello spotlight cade preciso sotto il puntatore. Alza lo smoothing intorno al 95% e il movimento diventa setoso. Il tranello: un overlay in absolute copre solo il suo genitore, quindi appena si muove scopre un bordo. La soluzione è sovradimensionarlo — larghezza e altezza al 200%, poi offset di -50% in alto e a sinistra per ricentrarlo — così copre sempre il viewport ovunque lo spinga il cursore. Impostare l’overflow della sezione su hidden nasconde tutto ciò che deborda.
Qui dentro c’è anche una lezione sullo stacking context. L’immagine in absolute sta naturalmente sopra un container static, quindi riordinare le cose nel navigator non basta a mettere il contenuto davanti. Dai al main container position: relative e i due elementi condividono lo z-index: auto di default — a quel punto vince l’ordine del DOM, e l’elemento che viene dopo si disegna sopra. È un’impostazione minuscola con una resa enorme, e vale la pena farla propria perché frega tantissime persone.
L’animazione hover mette in mostra le nuove interazioni GSAP. Invece di costruire animazioni separate per “hover in” e “hover out” come ti obbligava il vecchio sistema, ne costruisci una sola (scala l’immagine a 1.1, ruotala di 3°) e imposti due trigger: mouse enter su play e mouse leave su reverse. Poiché riparte dalla posizione attuale invece di ricominciare da capo, entrare e uscire velocemente col cursore risulta continuo e non a scatti. Il prisma è il finale: un filtro SVG (con ID prism-basic) scompone l’immagine nei canali RGB con feColorMatrix, sposta ognuno con feOffset e li ricombina con feBlend. Applicato all’immagine con filter: url(#prism-basic), a riposo non fa nulla perché gli offset sono a zero — i canali si ricompongono alla perfezione. Un piccolo script GSAP prende quei nodi feOffset e anima i loro valori dx/dy all’hover (e al focus, per chi usa la tastiera), separando i colori in quel glow cromatico. Due accortezze: l’embed del filtro deve stare prima della sezione nel DOM così è definito prima di essere richiamato, e lo script va in fondo alla pagina così tutto esiste quando parte.
Ecco il filtro esattamente com’è nel cloneable. Nota color-interpolation-filters="sRGB" — tiene la fusione dei canali nello spazio colore che stai effettivamente guardando, invece dello spazio lineare che i filtri usano di default:
<!-- Embed piazzato prima della sezione -->
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" style="position:absolute">
<defs>
<!-- PRISM BASIC: splits R, G, B and shifts them slightly -->
<filter id="prism-basic" color-interpolation-filters="sRGB">
<!-- RED -->
<feColorMatrix in="SourceGraphic" result="red" type="matrix"
values="1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0"/>
<feOffset in="red" dx="0" dy="0" result="redShift"/>
<!-- GREEN -->
<feColorMatrix in="SourceGraphic" result="green" type="matrix"
values="0 0 0 0 0
0 1 0 0 0
0 0 0 0 0
0 0 0 1 0"/>
<feOffset in="green" dx="0" dy="0" result="greenShift"/>
<!-- BLUE -->
<feColorMatrix in="SourceGraphic" result="blue" type="matrix"
values="0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 1 0"/>
<feOffset in="blue" dx="0" dy="0" result="blueShift"/>
<!-- Recombine the three channels -->
<feBlend in="redShift" in2="greenShift" mode="screen" result="rg"/>
<feBlend in="rg" in2="blueShift" mode="screen" result="rgb"/>
</filter>
</defs>
</svg>
Ogni feColorMatrix è solo una maschera di canale: quella del rosso tiene la prima riga e azzera verde e blu, e così via, con l’ultima riga che preserva l’alpha. Poi il blend in screen risomma le tre copie mascherate — ecco perché con gli offset a zero ottieni l’immagine originale intatta.
E lo script che lo anima. Tutto quello che vorresti regolare sta nelle costanti in cima:
<!-- Embed in fondo alla pagina -->
<script>
// Wait until the DOM is fully loaded before running the script
document.addEventListener('DOMContentLoaded', () => {
// ===== CONFIGURATION CONSTANTS =====
// Offsets (dx, dy) for each color channel
const DX_RED = -6
const DY_RED = -6
const DX_GREEN = 0
const DY_GREEN = 0
const DX_BLUE = 6
const DY_BLUE = 6
// Duration and easing for the GSAP timeline
const DURATION = 1.5
const EASE = 'power1.inOut'
// ===== DOM ELEMENTS =====
// Select the button using a custom attribute
// (keeps class naming flexible)
const prismButton = document.querySelector('[fc-prism-button]')
if (!prismButton) return
// Grab the filter by its ID
const filter = document.querySelector('#prism-basic')
if (!filter) return
// Get the three <feOffset> nodes that shift R, G, and B
const redOffset = filter.querySelector('feOffset[in=red]')
const greenOfset = filter.querySelector('feOffset[in=green]')
const blueOffset = filter.querySelector('feOffset[in=blue]')
// ===== GSAP TIMELINE =====
// Build a paused timeline with default duration/ease
// Animate all three offsets in parallel (position = 0)
const tl = gsap.timeline({ paused: true, defaults: { duration: DURATION, ease: EASE }})
.to(redOffset, { attr: { dx: DX_RED, dy: DY_RED } }, 0)
.to(greenOfset, { attr: { dx: DX_GREEN, dy: DY_GREEN } }, 0)
.to(blueOffset, { attr: { dx: DX_BLUE, dy: DY_BLUE } }, 0)
// ===== INTERACTION HANDLERS =====
// Play or reverse the timeline based on hover/focus
// Ensures pointer + keyboard accessibility
prismButton.addEventListener('pointerenter', () => tl.play())
prismButton.addEventListener('pointerleave', () => tl.reverse())
prismButton.addEventListener('focus', () => tl.play())
prismButton.addEventListener('blur', () => tl.reverse())
})
</script>
Il bottone viene trovato dall’attributo fc-prism-button — aggiunto senza valore, conta solo la sua presenza — così le tue classi restano tue. Le costanti di default tirano il rosso in alto a sinistra di 6 e il blu in basso a destra di 6 mentre il verde resta fermo: è questo che si legge come separazione prismatica. Allontanali di più per un’aberrazione più marcata, o muovi un solo canale per una tinta più sottile. Due cose da tenere a mente: lo script prende un singolo bottone (querySelector), quindi un secondo prism button nella stessa pagina richiede di trasformare la selezione in un ciclo, e legge i nodi feOffset dai loro valori in — rinomina red/green/blue nel filtro e i selettori smettono di combaciare.
Come si usa
-
Clona il progetto. Prendi il cloneable di Webflow — arriva con tutta la struttura della hero, il gradiente radiale già tarato, le interazioni e il filtro SVG più lo script GSAP già scritto.
-
Scegli un’immagine ad alto contrasto. L’effetto vive o muore sulla foto. Scegline una con zone di luce e ombra decise, gradienti forti e highlight marcati — le immagini piatte sotto lo spotlight sembrano spente. Il profilo Unsplash di Paweł Czerwiński è una miniera d’oro proprio per questo stile. Imposta l’immagine su
width: 100%,height: 100%,object-fit: cover,position: absolute(full), e dai al container che la contieneposition: relativecosì il contenuto resta sopra. -
Costruisci l’overlay dello spotlight. Aggiungi un div tra l’immagine e il contenuto, mettilo in absolute (full) e dagli un gradiente radiale che va da trasparente al centro a nero ai bordi. Stringi gli stop finché il cerchio di luce non è netto invece che nebbioso, poi scala l’overlay al
200%di larghezza e altezza e spostalo di-50%in alto e a sinistra. Imposta la sezione suheight: 100vh,overflow: hidden,position: relative. -
Fallo seguire il cursore. Sulla sezione, aggiungi un’interazione classica “mouse move over element”. Anima il move transform dell’overlay:
-50vw/50vwsu X,-50vh/50vhsu Y. Alza lo smoothing intorno al 95% e prova in anteprima — lo spotlight dovrebbe scivolare sotto il puntatore. -
Aggiungi l’animazione hover. Passa alle interazioni GSAP sul bottone. Costruisci una sola animazione custom che ha come target la classe dell’immagine (scala
1.1, rotate3°, durata1.5s, easingpower1.inOut), poi aggiungi due trigger: mouse enter → play, mouse leave → reverse. -
Inserisci il filtro prisma. Aggiungi un elemento Embed, incolla il filtro SVG qui sopra e mettilo prima della sezione nel navigator. Applicalo all’immagine con una custom property
filter: url(#prism-basic). Poi aggiungi un secondo Embed con lo script GSAP e mettilo in fondo alla pagina. Infine aggiungi l’attributofc-prism-buttonal tuo bottone (senza valore) così lo script lo trova qualunque sia la sua classe, e gioca con le costantiDX_/DY_in cima allo script per regolare lo shift di colore.