Bottone di Vetro con Filtri SVG e GSAP in Webflow

Intermedio 27:13 webflowsvg-filtergsaphoverglassmorphism

Crea in Webflow un bottone di cristallo che rifrange la pagina dietro di sé con un filtro SVG e, all'hover, passa da vetro morbido a ghiaccio nitido con GSAP.

Punti chiave

  • L''effetto vetro non è una sfocatura CSS: è un filtro SVG applicato tramite backdrop-filter, quindi il bottone rifrange davvero ciò che scorre dietro di lui invece di limitarsi a smerigliarlo.
  • La mappa di spostamento è l''ingrediente segreto: è una texture che il filtro legge come una mappa topografica per decidere quanto spingere e tirare ogni pixel, ed è per questo che sembra vetro vero e non una sfocatura piatta.
  • Tutto ruota attorno a tre primitive del filtro — feImage carica la mappa, feGaussianBlur ammorbidisce il bottone e feDisplacementMap lo deforma — e il solo parametro scale ti porta da increspature delicate al caos da specchio deformante.
  • Lo script trova il bottone tramite l''attributo fc-glass-button, non tramite una classe, così puoi chiamare le tue classi come vuoi e l''effetto si aggancia lo stesso.
  • GSAP anima gli stessi valori del filtro all''hover e, poiché scatta anche sul focus da tastiera, la transizione verso il cristallo resta accessibile invece di essere un vezzo solo per il mouse.

Capitoli del video

  1. 00:00 Intro
  2. 01:47 Analisi della struttura della pagina
  3. 04:16 Creare il bottone
  4. 07:37 Aggiungere il filtro SVG
  5. 08:53 Collegare il filtro SVG al bottone
  6. 13:10 Come funziona il filtro SVG e come personalizzarlo
  7. 19:00 Animare il bottone con GSAP
  8. 23:38 Esempi bonus
  9. 24:18 Più bottoni sulla stessa pagina o su pagine diverse
  10. 26:00 Outro

Crea in Webflow un bottone di cristallo che rifrange davvero la pagina dietro di sé e poi prende vita all’hover, passando da vetro smerigliato a ghiaccio nitido con un pizzico di GSAP. Il trucco non è una sfocatura CSS: è un vero filtro SVG collegato al bottone tramite backdrop-filter, così tutto ciò che scorre sotto si piega e si increspa attraverso il vetro.

Quello che lo rende speciale è quanto sembra fisico. Dato che il filtro legge il contenuto reale dietro il bottone, l’effetto cambia mentre scorri, e l’animazione all’hover gli regala una piccola spinta tattile. Sembrano tanti pezzi in movimento, ma sotto ci sono tre primitive di un filtro SVG, poche righe di JavaScript per dargli una texture e una minuscola timeline GSAP.

E niente è legato ai miei nomi di classe. Lo script si aggancia a un attributo, quindi puoi chiamare le cose come preferisci e avere lo stesso effetto senza pensarci.

Come funziona

L’aspetto vetro nasce da un filtro SVG, non da una sfocatura CSS. Incolli il markup del filtro dentro un elemento Embed, gli dai display: none e lo sposti proprio in cima al <body>. La posizione conta: metti il filtro in alto così è globale e sempre disponibile — più in basso alcuni browser lo limitano allo scope locale o lo saltano, un po’ come una variabile CSS che va messa in :root. Il filtro ha id="glass" e il bottone lo richiama con una proprietà personalizzata, backdrop-filter: url(#glass). È quella riga a creare il collegamento: il bottone prende la sorgente del filtro dall’elemento con quell’ID.

<!-- Embed in cima al body, display: none -->
<svg>
  <filter id="glass"
          x="-1" y="-1" width="3" height="3"
          filterUnits="objectBoundingBox"
          primitiveUnits="objectBoundingBox">
    <feImage x="-0.5" y="-0.5" width="2" height="2"
             preserveAspectRatio="none"
             result="map" />
    <feGaussianBlur in="SourceGraphic" stdDeviation="0.02" result="blur"/>
    <feDisplacementMap in="blur" in2="map"
                       scale="0.8" xChannelSelector="R" yChannelSelector="G"/>
  </filter>
</svg>

Dentro lavorano tre primitive. feImage carica la mappa di spostamento — una texture che funziona come una mappa topografica, dove le zone più chiare e più scure dicono al filtro quanto spingere o tirare ogni pixel — e produce un risultato che il resto della catena può leggere. feGaussianBlur ammorbidisce prima il bottone; il suo stdDeviation regola quanto il vetro appare nitido o nebbioso. Poi la star, feDisplacementMap, prende insieme il bottone sfocato e la mappa e sposta i pixel: il suo scale è la manopola dell’intensità. La regione del filtro è volutamente sovradimensionata (x="-1" y="-1" width="3" height="3") così ai bordi non si taglia nulla.

Il dettaglio che rende sensati quei numeri è primitiveUnits="objectBoundingBox": ogni valore dentro il filtro è una frazione del box del bottone, non un numero di pixel. Ecco perché stdDeviation="0.02" e scale="0.8" sembrano minuscoli ma pesano tanto — 0.8 significa che lo spostamento può spingere un pixel per quasi tutta la larghezza del bottone. Significa anche che l’effetto si adatta da solo alla dimensione del bottone, senza bisogno di numeri diversi per uno piccolo e uno grande. Spingi scale oltre ~1.4 e il vetro si legge come frantumato invece che rifrangente.

Nota anche che feImage non ha un href nel markup — è voluto, ed è l’unica cosa che il solo markup non può fare. La mappa di spostamento è un’immagine esterna, quindi un breve script la recupera e la passa dentro. Senza, il filtro sa di dover distorcere ma non ha nessuna texture con cui farlo:

<!-- Embed in fondo al body: carica la mappa + clona il filtro per ogni bottone -->
<script>
  document.addEventListener('DOMContentLoaded', async () => {
    // ===== 1) LOAD DISPLACEMENT MAP INTO <feImage> ===================
    const MAP_URL = "https://essykings.github.io/JavaScript/map.png"

    const template = document.getElementById('glass')
    const feImage  = template?.querySelector('feImage')
    const btns     = document.querySelectorAll('[fc-glass-button]')

    if (!template || !feImage) {
      console.warn("[glass] Filter template or <feImage> not found. Ensure #glass is in the DOM.")
      return
    }
    if (!btns.length) return // no buttons on page

    try {
      // Fetch the map image, convert to blob, and set it as <feImage> source
      const res    = await fetch(MAP_URL)
      const blob   = await res.blob()
      const objURL = URL.createObjectURL(blob)

      // SVG2 prefers `href`; some browsers still look for `xlink:href`
      feImage.setAttribute('href', objURL)
      feImage.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', objURL)

      // Keep a global reference to reuse in clones
      window.__glassMapHref = objURL
      // Revoke only when the page is closed
      window.addEventListener('beforeunload', () => URL.revokeObjectURL(objURL))
    } catch (err) {
      console.error("[glass] Failed to load displacement map:", err)
    }

    // ===== 1b) CLONE THE FILTER FOR EACH BUTTON ======================
    // Store references to <feGaussianBlur> and <feDisplacementMap> per button
    const mapHref =
      feImage.getAttribute('href') ||
      feImage.getAttributeNS('http://www.w3.org/1999/xlink','href') ||
      (feImage.href && feImage.href.baseVal) ||
      window.__glassMapHref || null

    const glassNodes = new WeakMap()
    btns.forEach((btn, i) => {
      const clone = template.cloneNode(true)
      const id = `glass-${i+1}`
      clone.id = id
      template.parentNode.appendChild(clone)

      // Ensure the cloned filter points to the same displacement map
      const img = clone.querySelector('feImage')
      if (mapHref && img) {
        img.setAttribute('href', mapHref)
        img.setAttributeNS('http://www.w3.org/1999/xlink','xlink:href', mapHref)
      }

      // Apply this unique filter to the button via backdrop-filter
      btn.style.backdropFilter = `url(#${id})`

      // Save blur + displacement nodes for this button
      glassNodes.set(btn, {
        blur: clone.querySelector('feGaussianBlur'),
        disp: clone.querySelector('feDisplacementMap')
      })
    })

    // Expose references globally for the second script
    window.__glassNodes = glassNodes
    document.dispatchEvent(new CustomEvent('glass:filters-ready'))
  })
</script>

Quello script fa più che caricare un’immagine, e la parte in più è ciò che rende possibili più bottoni. Un filtro unico animato da due bottoni litigherebbe con sé stesso — passare su uno deformerebbe entrambi — quindi lo script clona il filtro per ogni bottone, dà a ogni clone il suo id (glass-1, glass-2, …) e ripunta il backdrop-filter di quel bottone sulla propria copia. Lo url(#glass) che imposti a mano in Webflow è in realtà l’anteprima nel Designer; sulla pagina live lo script lo sostituisce in silenzio con il clone. Tiene i nodi di blur e displacement di ogni clone in una WeakMap e lancia un evento glass:filters-ready quando ha finito: è così che lo script dell’animazione sa cosa prendere. Fondamentale: trova i bottoni tramite l’attributo fc-glass-button (presente, senza valore), non tramite una classe, quindi la tua convenzione di naming è del tutto irrilevante.

L’hover arriva per ultimo, ed è GSAP:

<!-- Embed in fondo al body: animazione hover (vetro -> ghiaccio) -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    // ===== 2) HOVER ANIMATION (GLASS -> ICE) =========================
    // Edit only these constants to change the feel of the effect
    const HOVER_SCALE        = 1.40  // on-hover displacement intensity
    const HOVER_TRANSF_SCALE = 1.015 // scale transform applied to the button
    const HOVER_BLUR         = 0.00  // on-hover softness (lower = sharper/ice-like)
    const DURATION           = 0.30  // animation duration (0.2–0.4)
    const EASE               = 'power1.out' // GSAP easing

    const btns = document.querySelectorAll('[fc-glass-button]')
    if (!btns.length) return

    const setup = () => {
      const map = window.__glassNodes
      if (!map) return false

      btns.forEach((btn) => {
        const nodes = map.get(btn)
        if (!nodes?.blur || !nodes?.disp) return

        const { blur, disp } = nodes

        // Build a per-button timeline
        const tl = gsap.timeline({ paused: true, defaults: { duration: DURATION, ease: EASE }})
          .to(disp, { attr: { scale: HOVER_SCALE } }, 0)       // stronger displacement
          .to(blur, { attr: { stdDeviation: HOVER_BLUR } }, 0) // less blur = icy/crisp
          .to(btn,  { y: -1, scale: HOVER_TRANSF_SCALE }, 0)   // tiny physical feedback

        // Pointer + keyboard accessibility
        btn.addEventListener('pointerenter', () => tl.play())
        btn.addEventListener('pointerleave', () => tl.reverse())
        btn.addEventListener('focus',        () => tl.play())
        btn.addEventListener('blur',         () => tl.reverse())
      })
      return true
    }

    // Run immediately if filters are ready, otherwise wait for event
    if (!setup()) {
      document.addEventListener('glass:filters-ready', setup, { once: true })
    }
  })
</script>

Una piccola timeline anima lo spostamento e la sfocatura dallo stato di riposo “vetro morbido” allo stato “ghiaccio nitido”, sollevando il bottone di 1px e ingrandendolo un filo per un feedback tattile. Nota la direzione del movimento: lo spostamento sale (0.8 → HOVER_SCALE 1.40) mentre la sfocatura scende (0.02 → HOVER_BLUR 0) — più distorsione, meno foschia, che è esattamente ciò che si legge come vetro che indurisce in ghiaccio. Lo regoli con le cinque costanti in cima: HOVER_SCALE (circa 1.05–1.25 per un’increspatura elegante, 1.40+ per bordi di cristallo spezzato), HOVER_TRANSF_SCALE (tienila contenuta — 1.015 si nota già), HOVER_BLUR (0 per nitido, ~0.03 per un atterraggio più morbido), DURATION (0.2s scattante, 0.4s elegante) ed EASE (per esempio power1.out, power2.out, expo.out). La stessa animazione parte anche sul focus da tastiera e si inverte al blur, così chi naviga da tastiera vive la stessa esperienza — l’effetto resta accessibile, non solo per il mouse.

Come usarlo

  1. Clona il progetto. Prendi il cloneable Webflow: include il bottone, il markup del filtro SVG, lo script che carica la mappa di spostamento e lo snippet GSAP per l’hover, tutto commentato.

  2. Dai al bottone qualcosa da rifrangere. L’effetto brilla solo quando c’è contenuto dietro. Nella demo il bottone è fixed e centrato mentre un’immagine di sfondo a tutta larghezza scorre sotto, così vedi il filtro cambiare con la pagina. Va bene qualsiasi sfondo ricco e scorrevole.

  3. Stila la base di vetro. Rendi lo sfondo del bottone trasparente con un gradiente lineare bianco e delicato (circa 135deg, bianco al ~20% → ~8% → ~3% di opacità), un border-radius a pillola e un bordo bianco di 1px a ~30% di opacità. È questa base smerigliata che il filtro rifrange — modificala o toglila pure: cambia l’aspetto, non il filtro.

  4. Aggiungi il filtro. Incolla il markup del filtro qui sopra in un Embed, mettilo a display: none e sposta quell’Embed in cima al body. Ha id="glass".

  5. Collega il filtro al bottone. Sul bottone aggiungi la proprietà personalizzata backdrop-filter con valore url(#glass). Vedrai comparire subito un primo effetto vetro.

  6. Carica la mappa di spostamento. Incolla lo script nel secondo Embed (display: none), posizionato in fondo al body. Passa l’immagine-mappa al filtro così ha una texture da piegare, poi clona il filtro una volta per bottone. La mappa viene scaricata da un URL esterno (MAP_URL in cima allo script) — sostituiscilo con una tua copia ospitata se preferisci non dipendere dal server di qualcun altro.

  7. Marca il bottone. Aggiungi l’attributo fc-glass-button al bottone — senza valore — così lo script lo trova a prescindere dal nome della classe.

  8. Aggiungi l’hover e attiva GSAP. Incolla lo snippet GSAP, poi attiva GSAP nelle impostazioni del progetto — il codice non fa nulla finché la libreria non è caricata. Ritocca HOVER_SCALE, HOVER_BLUR, DURATION ed EASE a piacere.

  9. Riusalo. Per più bottoni sulla stessa pagina, duplica il bottone: lo script dà già a ognuno il proprio filtro clonato, così si animano in modo indipendente. Per altre pagine, trasforma il filtro e lo script in componenti (le modifiche si propagano ovunque), oppure metti il solo script nel footer code del sito e piazza un bottone dove serve.

Risorse

Nella knowledge base

Pagine di riferimento derivate da questa lezione — lo stesso materiale riorganizzato per concetto, così cerchi una cosa sola senza riguardare il video. In inglese.

  • Build a glass button that refracts the page behind it

    A real SVG displacement-map filter wired to the button through backdrop-filter bends whatever scrolls underneath, and a tiny GSAP timeline hardens it from frosted glass to crisp ice on hover — but SVG filter references in backdrop-filter are Chromium-only, so Safari and Firefox visitors see the plain frosted base and none of the refraction.

  • Verified identifiers

    Every exact attribute name, class name, variable name, SVG id and selector used across the GSAP-in-Webflow corpus, each traced to the cloneable it was copied from; copy these verbatim and never reconstruct one from memory, because a wrong identifier fails silently.

Domande frequenti

Come faccio un bottone di vetro che distorce lo sfondo in Webflow?
La distorsione vera arriva da un filtro SVG, non da una sfocatura CSS. Incorpori un filtro che usa feDisplacementMap e lo richiami dal bottone con backdrop-filter: url(#glass): a quel punto il filtro piega ciò che sta dietro, così il contenuto in scorrimento si increspa attraverso il bottone come vero vetro.
Cos'è una mappa di spostamento e perché l'effetto ne ha bisogno?
Una mappa di spostamento è una texture in scala di grigi o a colori che il filtro legge come una mappa topografica: le zone più chiare e più scure gli dicono quanto spingere o tirare ogni pixel. Senza, il filtro non sa come piegare la luce e il vetro resta piatto — è la mappa a dargli profondità e movimento.
Perché il mio filtro SVG non funziona quando lo aggiungo in Webflow?
Di solito sono due cose. Metti l''embed del filtro proprio in cima al body così è disponibile a livello globale: più in basso alcuni browser lo limitano allo scope locale e lo ignorano. E assicurati che il bottone abbia l''attributo fc-glass-button, perché lo script punta a quell''attributo, non a una classe.
Mi serve GSAP per l'animazione all'hover del bottone?
Sì: la transizione da vetro morbido a ghiaccio nitido è una timeline GSAP che anima la sfocatura e lo spostamento del filtro. Ricordati di attivare GSAP nelle impostazioni del progetto Webflow — il codice gira, ma finché la libreria non è caricata non si muove niente.
Posso usare lo stesso bottone di vetro su più pagine in Webflow?
Puoi. Lo script gestisce già più bottoni sulla stessa pagina, quindi ti basta duplicare il bottone. Per riusarlo su altre pagine, trasforma il filtro e lo script in componenti (così le modifiche si propagano ovunque), oppure metti il solo script nel footer code del sito e piazza un bottone dove vuoi.

Fa parte anche di questi corsi