Sei un recruiter? Clicca qui!
Popup exit intent in WordPress come crearne uno con GeneratePress e GenerateBlocks senza plugin pesanti

Popup exit intent in WordPress: come crearne uno con GeneratePress e GenerateBlocks senza plugin pesanti

Scritto da Sergio Pinna il

15/06/2026

Creare un popup in WordPress è facilissimo, almeno in apparenza. Basta installare un plugin, scegliere un template, impostare due opzioni e il gioco sembra fatto.

Il problema è che spesso questi plugin si portano dietro un carico abbastanza importante: file CSS globali, JavaScript caricati su tutte le pagine, tracciamenti, funzioni che magari non useremo mai e una gestione del popup poco controllabile dal punto di vista tecnico.

Se stai lavorando con un tema leggero come GeneratePress e con blocchi flessibili come GenerateBlocks, ha molto più senso costruire un popup personalizzato, caricato solo dove serve e controllato con poche righe di codice mirato.

In questo articolo vediamo come creare un popup con exit intent, cioè un popup che appare quando l’utente sta per lasciare la pagina, senza usare plugin dedicati.

L’obiettivo è creare una soluzione:

  • leggera;
  • personalizzabile;
  • compatibile con GeneratePress;
  • costruibile graficamente con GenerateBlocks;
  • controllata tramite CSS e JavaScript;
  • caricata solo nelle pagine desiderate;
  • più rispettosa dell’esperienza utente rispetto a un popup invasivo.

Che cos’è un popup exit intent

Un popup con exit intent è un popup che viene mostrato quando il sito rileva un comportamento tipico di uscita.

Su desktop, il caso più comune è questo: l’utente sposta velocemente il mouse verso la parte alta del browser, magari per chiudere la scheda, digitare un nuovo indirizzo o tornare indietro.

In quel momento il sito intercetta il movimento e mostra un messaggio.

Per esempio:

“Prima di andare via, vuoi richiedere un preventivo?”

Oppure:

“Hai bisogno di aiuto per il tuo sito WordPress?”

Oppure ancora:

“Scarica una guida gratuita prima di lasciare la pagina.”

Il popup exit intent può essere utile perché non interrompe subito la navigazione. Non appare appena l’utente entra nella pagina, ma solo quando sembra intenzionato ad abbandonarla.

Questo lo rende meno aggressivo rispetto ai classici popup che compaiono dopo due secondi e coprono tutto il contenuto.

Perché creare il popup senza plugin

In WordPress esistono tantissimi plugin per creare popup. Alcuni sono ottimi, altri meno.

Il punto però è un altro: se ti serve un popup semplice, magari solo per alcune pagine strategiche del sito, un plugin completo può essere eccessivo.

Creare il popup con GeneratePress, GenerateBlocks e uno snippet personalizzato ti permette di avere un controllo molto più preciso.

Puoi decidere:

  • in quali pagine caricarlo;
  • dopo quanti giorni mostrarlo di nuovo;
  • se mostrarlo anche su mobile;
  • dopo quanto tempo attivarlo su smartphone;
  • quanto deve durare l’animazione;
  • quali colori usare;
  • quale contenuto inserire;
  • se chiuderlo cliccando sullo sfondo;
  • se chiuderlo con il tasto ESC;
  • se bloccare lo scroll della pagina quando il popup è aperto.

Inoltre, caricando CSS e JavaScript solo dove serve, eviti di appesantire inutilmente tutto il sito.

Per un sito costruito con GeneratePress questa è una filosofia molto coerente: meno plugin superflui, più codice mirato, più controllo.

La logica generale del popup

Il popup che costruiamo funziona in questo modo:

  1. Il contenuto del popup viene costruito con GenerateBlocks.
  2. Il contenitore principale riceve un ID specifico.
  3. Il CSS nasconde inizialmente il popup.
  4. Il JavaScript controlla quando mostrarlo.
  5. Su desktop il popup appare quando il mouse esce dalla finestra verso l’alto.
  6. Su mobile, dove non esiste un vero exit intent, il popup può apparire dopo un certo tempo e dopo una certa percentuale di scroll.
  7. Dopo la visualizzazione, il popup viene salvato nel browser per non essere mostrato continuamente.
  8. L’utente può chiuderlo cliccando sulla X, sullo sfondo oppure premendo ESC.

La cosa interessante è che il popup non è un elemento “rigido”. Il contenuto può essere progettato visivamente con GenerateBlocks, quindi puoi inserire titolo, testo, pulsanti, icone, immagini, colonne o qualsiasi altro elemento utile.

Creare la struttura del popup con GenerateBlocks

Prima di inserire lo snippet PHP/CSS/JS, bisogna costruire il popup nella pagina.

Con GenerateBlocks puoi creare una struttura molto semplice:

<div id="sp-exit-popup" class="sp-exit-popup">
    <div class="sp-exit-popup__box">
        <button class="sp-exit-popup__close" type="button">×</button>

        <h2>Prima di andare via...</h2>

        <p>
            Hai bisogno di migliorare il tuo sito WordPress o vuoi capire se la tua home funziona davvero?
        </p>

        <a href="/richiedi-un-preventivo/" class="button">
            Richiedi un preventivo
        </a>
    </div>
</div>

Naturalmente, se usi GenerateBlocks, non devi per forza scrivere questo HTML a mano. Puoi costruire la struttura tramite blocchi.

La cosa importante è assegnare correttamente classi e ID.

Il contenitore esterno deve avere:

ID: sp-exit-popup
Classe CSS: sp-exit-popup

Il contenitore interno, cioè il box bianco del popup, deve avere:

Classe CSS: sp-exit-popup__box

Il pulsante di chiusura deve avere:

Classe CSS: sp-exit-popup__close

Volendo, puoi aggiungere anche l’attributo:

data-sp-popup-close

a qualsiasi elemento che vuoi usare per chiudere il popup.

Per esempio, potresti avere un link “No grazie” dentro il popup che chiude la finestra senza portare l’utente altrove.

Dove inserire lo snippet

Lo snippet può essere inserito tramite il plugin Code Snippets, oppure nel file functions.php del tema child.

Personalmente, per questo tipo di interventi, preferisco usare Code Snippets, perché è più ordinato e ti permette di attivare/disattivare il codice più velocemente.

Lo snippet è diviso in tre parti:

  1. una funzione PHP che decide dove caricare il popup;
  2. il CSS inserito in wp_head;
  3. il JavaScript inserito in wp_footer.

Questa organizzazione è molto comoda perché evita di caricare tutto ovunque.

Decidere in quali pagine mostrare il popup

La prima parte dello snippet serve a dire a WordPress se il popup deve essere caricato oppure no.

Ecco la logica:

if ( is_front_page() || is_page( array( 2113, 276, 224, 943 ) ) ) return false;

Questa riga significa:

non caricare il popup in home e non caricarlo nelle pagine con ID 2113, 276, 224 e 943.

Questo approccio è molto utile se vuoi escludere pagine dove il popup sarebbe fuori luogo.

Per esempio, potresti non volerlo mostrare su:

  • pagina Privacy Policy;
  • Cookie Policy;
  • pagina Contatti;
  • pagina Richiedi preventivo;
  • landing page specifiche;
  • pagine dove hai già un modulo di contatto;
  • pagine molto delicate dal punto di vista UX.

Nel tuo caso, questa parte è già impostata bene, perché ti permette di avere un controllo chirurgico.

Se invece volessi mostrare il popup solo in alcune pagine specifiche, potresti usare una logica opposta:

if ( ! is_page( array( 224, 230, 226 ) ) ) {
return false;
}

In quel caso il popup verrebbe caricato solo nelle pagine indicate.

Il CSS del popup

Il CSS serve a trasformare un semplice contenitore HTML in una vera modale.

Il contenitore principale .sp-exit-popup viene fissato sopra tutta la pagina con:

position: fixed;
inset: 0;
z-index: 999999;

Questo lo rende un overlay a tutto schermo.

Inizialmente il popup è nascosto:

opacity: 0;
visibility: hidden;
pointer-events: none;

Quando viene aggiunta la classe .is-visible, il popup diventa visibile:

.sp-exit-popup.is-visible {
opacity: 1;
visibility: visible;
pointer-events: auto;
}

Il bello di questa soluzione è che la visualizzazione non avviene “a scatto”, ma con una transizione morbida.

Lo sfondo passa da trasparente a scuro, il box interno sale leggermente verso l’alto, aumenta di scala e perde il piccolo effetto blur iniziale.

Il risultato è molto più elegante rispetto a un popup che appare bruscamente.

Una piccola nota: nel codice che hai mostrato c’è un refuso qui:

border: 10px solid #22adf5;

La proprietà corretta è:

border: 10px solid #22adf5;

Sembra una sciocchezza, ma se il bordo non appare, il motivo è proprio quello.

Il JavaScript: come viene rilevato l’exit intent

La parte più importante è il JavaScript.

Su desktop, il popup viene mostrato quando il mouse esce dalla finestra del browser verso l’alto:

document.documentElement.addEventListener('mouseleave', function (event) {
if (event.clientY <= 10) {
showPopup();
}
});

Il controllo event.clientY <= 10 serve a capire se il cursore è uscito dalla parte superiore della finestra.

Questo comportamento è tipico dell’utente che sta per:

  • chiudere la scheda;
  • cambiare indirizzo;
  • tornare indietro;
  • uscire dal sito.

In più, lo script aspetta 1,5 secondi prima di attivare l’ascolto dell’evento:

setTimeout(function () {
// attiva exit intent
}, 1500);

Questo evita che il popup venga mostrato immediatamente a causa di movimenti accidentali appena caricata la pagina.

Il fallback mobile

Su smartphone e tablet l’exit intent classico non funziona, perché non c’è un mouse.

Per questo motivo lo snippet usa una logica alternativa.

Su dispositivi touch, il popup può essere mostrato solo se:

  • è passato un certo tempo;
  • l’utente ha scrollato una certa percentuale della pagina;
  • il popup non è già stato mostrato.

Nel codice questi valori sono controllati da tre costanti:

const ENABLE_MOBILE_FALLBACK = true;
const MOBILE_DELAY = 22000;
const MOBILE_SCROLL_PERCENT = 65;

Questo significa che su mobile il popup può apparire solo dopo 22 secondi e dopo che l’utente ha scrollato almeno il 65% della pagina.

È una scelta sensata, perché evita di disturbare subito l’utente.

Su mobile bisogna essere ancora più prudenti rispetto al desktop. Lo spazio è poco, il popup occupa gran parte dello schermo e il rischio di risultare fastidiosi è maggiore.

Per questo motivo è meglio farlo apparire solo quando l’utente ha già dimostrato un certo interesse verso il contenuto.

Evitare che il popup appaia continuamente

Uno degli errori più fastidiosi nei popup è mostrarli troppe volte.

Se un utente chiude un popup e lo rivede dopo pochi secondi su un’altra pagina, l’esperienza diventa negativa.

Nel nostro caso lo snippet usa due strumenti del browser:

localStorage
sessionStorage

Il sessionStorage serve a ricordare che il popup è già stato mostrato nella sessione corrente.

Il localStorage, invece, serve a ricordare quando il popup è stato mostrato l’ultima volta.

Questa costante controlla dopo quanti giorni può ricomparire:

const HIDE_DAYS = 1;

Con questo valore, il popup può essere mostrato di nuovo dopo un giorno.

Se vuoi renderlo meno insistente, puoi aumentare il valore:

const HIDE_DAYS = 7;

oppure:

const HIDE_DAYS = 30;

Per un sito professionale, spesso 7 giorni è un buon compromesso.

Un popup mostrato ogni giorno può avere senso in fase di test, ma sul lungo periodo rischia di essere un po’ aggressivo.

Accessibilità: un aspetto da non sottovalutare

Una cosa positiva di questo snippet è che non si limita a mostrare un elemento visivo, ma gestisce anche diversi aspetti legati all’accessibilità.

Il popup riceve questi attributi:

popup.setAttribute('aria-hidden', 'true');
popup.setAttribute('role', 'dialog');
popup.setAttribute('aria-modal', 'true');

Quando il popup viene aperto, aria-hidden passa a false.

Inoltre, lo script cerca automaticamente un titolo interno al popup e lo collega alla modale tramite aria-labelledby.

La stessa cosa viene fatta con il primo paragrafo descrittivo, usando aria-describedby.

Questo è importante perché uno screen reader deve poter capire che si è aperta una finestra di dialogo e deve poter comunicare all’utente il titolo e il contenuto principale del popup.

Lo snippet gestisce anche il focus.

Quando il popup si apre, il focus viene spostato sul primo elemento interattivo disponibile, per esempio il pulsante di chiusura o un link.

Quando il popup viene chiuso, il focus viene restituito all’elemento che era attivo prima dell’apertura.

Questo comportamento è molto più curato rispetto a tanti popup improvvisati che si limitano a comparire visivamente ma non funzionano bene da tastiera.

Focus trap: tenere la navigazione dentro il popup

Quando una modale è aperta, l’utente non dovrebbe poter navigare con il tasto TAB sugli elementi della pagina sottostante.

Per questo lo snippet include un piccolo focus trap.

La logica è questa:

  • se l’utente preme TAB sull’ultimo elemento del popup, il focus torna al primo;
  • se l’utente preme SHIFT + TAB sul primo elemento, il focus torna all’ultimo.

In questo modo la navigazione da tastiera rimane confinata dentro il popup finché la modale non viene chiusa.

È un dettaglio tecnico, ma fa una grande differenza nella qualità del componente.

Chiusura del popup

Il popup può essere chiuso in tre modi:

  1. cliccando sul pulsante con la X;
  2. cliccando sullo sfondo scuro;
  3. premendo il tasto ESC.

Questa è una buona scelta UX, perché non costringe l’utente a cercare per forza un solo punto di uscita.

Il pulsante di chiusura viene gestito con questa classe:

.sp-exit-popup__close

e lo script assegna automaticamente un’etichetta accessibile:

button.setAttribute('aria-label', 'Chiudi popup');

Questo è utile soprattutto se il bottone contiene solo il simbolo ×.

Visivamente l’utente capisce che serve a chiudere, ma uno screen reader ha bisogno di un testo chiaro.

Bloccare lo scroll della pagina

Quando il popup è aperto, lo snippet aggiunge questa classe all’elemento html:

document.documentElement.classList.add('sp-popup-open');

Nel CSS troviamo poi:

html.sp-popup-open,
html.sp-popup-open body {
overflow: hidden;
}

Questo impedisce alla pagina sottostante di continuare a scrollare mentre il popup è visibile.

È un dettaglio importante soprattutto su mobile, dove altrimenti si rischia di avere due livelli di scroll: quello del popup e quello della pagina dietro.

Responsive: comportamento su mobile

Nel CSS è presente una media query dedicata agli schermi più piccoli:

@media (max-width: 520px) {
.sp-exit-popup {
align-items: flex-end;
padding: 16px;
}

.sp-exit-popup__box {
width: 100%;
border-radius: 22px;
padding: 30px 24px 26px;
}
}

Su desktop il popup viene centrato.

Su mobile, invece, viene allineato verso il basso, con un comportamento più simile a un bottom sheet.

Questa scelta è molto intelligente, perché su smartphone un popup centrato può risultare più ingombrante, mentre un box che entra dal basso è più naturale e vicino ai pattern delle app moderne.

Animazioni morbide e rispetto delle preferenze utente

Il popup usa transizioni morbide con una curva di animazione abbastanza elegante:

cubic-bezier(0.22, 1, 0.36, 1)

Questa curva dà un effetto più naturale rispetto al classico ease.

In più, viene rispettata la preferenza dell’utente per la riduzione del movimento:

@media (prefers-reduced-motion: reduce) {
.sp-exit-popup,
.sp-exit-popup__box,
.sp-exit-popup__close {
transition: none !important;
}
}

Questo significa che se un utente ha impostato nel sistema operativo la riduzione delle animazioni, il popup non userà transizioni inutili.

Anche questo è un dettaglio da sito professionale.

Snippet completo

Ecco una versione completa dello snippet, con la correzione della proprietà border.

/**
* Exit Intent Popup con GeneratePress + GenerateBlocks
* Popup costruito con GenerateBlocks e controllato via CSS/JS.
*/

if ( ! function_exists( 'sp_should_load_exit_popup' ) ) {
function sp_should_load_exit_popup() {

if ( is_admin() ) {
return false;
}

// Esempio: esclude home e alcune pagine specifiche.
if ( is_front_page() || is_page( array( 2113, 276, 224, 943 ) ) ) {
return false;
}

return true;
}
}

add_action( 'wp_head', function() {

if ( ! sp_should_load_exit_popup() ) {
return;
}
?>

<style id="sp-exit-popup-css">
.sp-exit-popup {
position: fixed !important;
inset: 0;
z-index: 999999;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(18, 18, 18, 0);
backdrop-filter: blur(0);
-webkit-backdrop-filter: blur(0);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition:
opacity 520ms cubic-bezier(0.22, 1, 0.36, 1),
background-color 520ms cubic-bezier(0.22, 1, 0.36, 1),
backdrop-filter 520ms cubic-bezier(0.22, 1, 0.36, 1),
-webkit-backdrop-filter 520ms cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s linear 520ms;
}

.sp-exit-popup.is-visible {
opacity: 1;
visibility: visible;
pointer-events: auto;
background: rgba(18, 18, 18, 0.68);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
transition:
opacity 520ms cubic-bezier(0.22, 1, 0.36, 1),
background-color 520ms cubic-bezier(0.22, 1, 0.36, 1),
backdrop-filter 520ms cubic-bezier(0.22, 1, 0.36, 1),
-webkit-backdrop-filter 520ms cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s linear 0s;
}

.sp-exit-popup__box {
position: relative;
width: min(92vw, 560px);
max-height: calc(100vh - 48px);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
background: #ffffff;
color: #121212;
border-radius: 20px;
padding: 36px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
border: 10px solid #22adf5;
opacity: 0;
transform: translate3d(0, 34px, 0) scale(0.94);
filter: blur(2px);
transition:
opacity 520ms cubic-bezier(0.22, 1, 0.36, 1),
transform 520ms cubic-bezier(0.22, 1, 0.36, 1),
filter 520ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sp-exit-popup.is-visible .sp-exit-popup__box {
opacity: 1;
transform: translate3d(0, 0, 0) scale(1);
filter: blur(0);
}

.sp-exit-popup__close {
position: absolute;
top: 14px;
right: 14px;
width: 40px;
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0;
border-radius: 50%;
background: #22adf5;
color: #ffffff;
font-size: 26px;
line-height: 1;
cursor: pointer;
transition:
background 160ms ease,
transform 160ms ease;
}

.sp-exit-popup__close:hover,
.sp-exit-popup__close:focus {
background: #22adf5;
transform: scale(1.04);
}

.sp-exit-popup__close:focus-visible {
outline: 3px solid rgba(18, 18, 18, 0.35);
outline-offset: 3px;
}

html.sp-popup-open,
html.sp-popup-open body {
overflow: hidden;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.sp-exit-popup.is-visible {
background: rgba(18, 18, 18, 0.82);
}
}

@media (max-width: 520px) {
.sp-exit-popup {
align-items: flex-end;
padding: 16px;
}

.sp-exit-popup__box {
width: 100%;
border-radius: 22px;
padding: 30px 24px 26px;
transform: translate3d(0, 42px, 0) scale(0.98);
}

.sp-exit-popup.is-visible .sp-exit-popup__box {
transform: translate3d(0, 0, 0) scale(1);
}
}

@media (prefers-reduced-motion: reduce) {
.sp-exit-popup,
.sp-exit-popup__box,
.sp-exit-popup__close {
transition: none !important;
filter: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}

.sp-exit-popup__box,
.sp-exit-popup.is-visible .sp-exit-popup__box {
transform: none !important;
}
}
</style>

<?php
} );

add_action( 'wp_footer', function() {

if ( ! sp_should_load_exit_popup() ) {
return;
}
?>

<script id="sp-exit-popup-js">
(function () {
'use strict';

function initExitPopup() {

const popup = document.getElementById('sp-exit-popup');

if (!popup) {
return;
}

const closeButtons = popup.querySelectorAll('.sp-exit-popup__close, [data-sp-popup-close]');
const popupTitle = popup.querySelector('[data-sp-popup-title], #sp-exit-popup-title, h1, h2, h3, h4, h5, h6');
const popupDescription = popup.querySelector('[data-sp-popup-description], #sp-exit-popup-description, p');

const STORAGE_KEY = 'sp_exit_popup_last_seen';
const SESSION_KEY = 'sp_exit_popup_seen_this_session';
const HIDE_DAYS = 1;
const ENABLE_MOBILE_FALLBACK = true;
const MOBILE_DELAY = 22000;
const MOBILE_SCROLL_PERCENT = 65;
const POPUP_ANIMATION_DURATION = 520;

const raf = window.requestAnimationFrame || function (callback) {
return setTimeout(callback, 16);
};

const isTouchDevice =
(window.matchMedia && window.matchMedia('(pointer: coarse)').matches) ||
('ontouchstart' in window) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 0);

let popupVisible = false;
let popupTriggered = false;
let previousFocus = null;
let closeTimer = null;

popup.setAttribute('aria-hidden', 'true');
popup.setAttribute('role', 'dialog');
popup.setAttribute('aria-modal', 'true');

if (popupTitle) {
if (!popupTitle.id) {
popupTitle.id = 'sp-exit-popup-title';
}

popup.setAttribute('aria-labelledby', popupTitle.id);
}

if (popupDescription) {
if (!popupDescription.id) {
popupDescription.id = 'sp-exit-popup-description';
}

popup.setAttribute('aria-describedby', popupDescription.id);
}

closeButtons.forEach(function (button) {
if (!button.hasAttribute('aria-label')) {
button.setAttribute('aria-label', 'Chiudi popup');
}
});

function now() {
return Date.now();
}

function getStoredTime() {
try {
return Number(localStorage.getItem(STORAGE_KEY)) || 0;
} catch (error) {
return 0;
}
}

function setStoredTime() {
try {
localStorage.setItem(STORAGE_KEY, String(now()));
} catch (error) {}
}

function setSessionSeen() {
try {
sessionStorage.setItem(SESSION_KEY, '1');
} catch (error) {}
}

function hasSessionSeen() {
try {
return sessionStorage.getItem(SESSION_KEY) === '1';
} catch (error) {
return false;
}
}

function canShowPopup() {
const lastSeen = getStoredTime();
const hideTime = HIDE_DAYS * 24 * 60 * 60 * 1000;

if (hasSessionSeen()) {
return false;
}

if (!lastSeen) {
return true;
}

return now() - lastSeen > hideTime;
}

function getFocusableElements() {
return Array.prototype.slice.call(
popup.querySelectorAll(
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
)
).filter(function (element) {
return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement;
});
}

function showPopup() {
if (popupTriggered || popupVisible || !canShowPopup()) {
return;
}

if (closeTimer) {
clearTimeout(closeTimer);
closeTimer = null;
}

popupTriggered = true;
popupVisible = true;
previousFocus = document.activeElement;

document.documentElement.classList.add('sp-popup-open');
popup.setAttribute('aria-hidden', 'false');

raf(function () {
popup.classList.add('is-visible');
});

setStoredTime();
setSessionSeen();

const firstFocusable = getFocusableElements()[0];

if (firstFocusable) {
setTimeout(function () {
firstFocusable.focus();
}, POPUP_ANIMATION_DURATION);
}
}

function closePopup() {
if (!popupVisible) {
return;
}

popupVisible = false;
popup.classList.remove('is-visible');
popup.setAttribute('aria-hidden', 'true');

closeTimer = setTimeout(function () {
document.documentElement.classList.remove('sp-popup-open');

if (previousFocus && typeof previousFocus.focus === 'function') {
previousFocus.focus();
}
}, POPUP_ANIMATION_DURATION);
}

function trapFocus(event) {
if (!popupVisible || event.key !== 'Tab') {
return;
}

const focusableElements = getFocusableElements();

if (!focusableElements.length) {
return;
}

const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];

if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
}

if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}

closeButtons.forEach(function (button) {
button.addEventListener('click', function (event) {
event.preventDefault();
closePopup();
});
});

popup.addEventListener('click', function (event) {
if (event.target === popup) {
closePopup();
}
});

document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
closePopup();
return;
}

trapFocus(event);
});

if (!isTouchDevice) {
setTimeout(function () {
document.documentElement.addEventListener('mouseleave', function (event) {
if (event.clientY <= 10) {
showPopup();
}
});
}, 1500);
}

if (ENABLE_MOBILE_FALLBACK && isTouchDevice) {
let mobileReady = false;

setTimeout(function () {
mobileReady = true;
}, MOBILE_DELAY);

window.addEventListener('scroll', function () {
if (!mobileReady || popupTriggered || !canShowPopup()) {
return;
}

const scrollTop = window.scrollY || document.documentElement.scrollTop;
const documentHeight = document.documentElement.scrollHeight - window.innerHeight;

if (documentHeight <= 0) {
return;
}

const scrollPercent = (scrollTop / documentHeight) * 100;

if (scrollPercent >= MOBILE_SCROLL_PERCENT) {
showPopup();
}
}, { passive: true });
}
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initExitPopup);
} else {
initExitPopup();
}
})();
</script>

<?php
}, 100 );

Come personalizzare il popup

Una volta installato lo snippet, puoi personalizzare il comportamento modificando alcuni valori.

Per cambiare dopo quanti giorni il popup può ricomparire:

const HIDE_DAYS = 7;

Per cambiare il ritardo su mobile:

const MOBILE_DELAY = 15000;

Per cambiare la percentuale di scroll richiesta su mobile:

const MOBILE_SCROLL_PERCENT = 50;

Per disattivare completamente il popup su mobile:

const ENABLE_MOBILE_FALLBACK = false;

Per modificare la durata dell’animazione, devi mantenere coerenti CSS e JavaScript.

Nel CSS:

opacity 520ms

Nel JavaScript:

const POPUP_ANIMATION_DURATION = 520;

Se porti l’animazione a 700 millisecondi, aggiorna entrambi i valori.

Cosa inserire dentro il popup

Dal punto di vista del contenuto, il popup deve essere diretto.

Non dovrebbe contenere troppo testo.

Una struttura efficace potrebbe essere:

Titolo:
Prima di andare via…

Testo:
Hai bisogno di migliorare il tuo sito WordPress o vuoi capire se la tua home funziona davvero?

Pulsante:
Richiedi una valutazione

Oppure:

Titolo:
Stai pensando di rifare il tuo sito?

Testo:
Posso aiutarti a creare un sito WordPress più veloce, ordinato e ottimizzato per Google.

Pulsante:
Parliamone

Il popup deve avere una sola azione principale. Se inserisci troppi pulsanti o troppe opzioni, l’utente si distrae.

Meglio una call to action chiara e coerente con la pagina.

Quando usare un popup exit intent

Il popup exit intent può funzionare bene su:

  • articoli del blog;
  • pagine informative;
  • guide tecniche;
  • pagine servizio;
  • landing page non troppo aggressive;
  • contenuti lunghi dove l’utente arriva fino in fondo.

Può essere meno adatto su:

  • pagine privacy;
  • cookie policy;
  • pagina contatti;
  • pagina preventivo;
  • checkout;
  • pagine molto brevi;
  • pagine dove c’è già un modulo importante.

Il punto è semplice: il popup deve aiutare, non disturbare.

Se l’utente è già su una pagina di conversione, come “Richiedi un preventivo”, probabilmente non serve aggiungere un popup. Meglio lasciarlo concentrato sull’azione principale.

Perché questa soluzione è adatta a GeneratePress

GeneratePress è un tema leggero e molto ordinato. Il suo punto forte è proprio quello di non imporre strutture pesanti.

GenerateBlocks segue la stessa logica: pochi blocchi, molto controllo, codice pulito.

Creare un popup con questa combinazione ha senso perché ti permette di mantenere il sito snello.

Non stai installando un plugin solo per mostrare un box. Stai usando:

  • GenerateBlocks per costruire il contenuto;
  • CSS per gestire aspetto e animazioni;
  • JavaScript per controllare il comportamento;
  • PHP per decidere dove caricare il tutto.

È una soluzione molto più artigianale, ma anche molto più pulita.

Attenzione alla UX

Un popup può aiutare le conversioni, ma può anche peggiorare l’esperienza utente se usato male.

Il consiglio è di non usarlo ovunque.

Meglio mostrarlo solo dove ha davvero senso.

Per esempio, su un articolo tecnico, il popup potrebbe invitare l’utente a richiedere una consulenza. Su una pagina portfolio, potrebbe invitare a vedere altri progetti o a contattarti. Su una pagina già orientata al contatto, invece, potrebbe essere superfluo.

Bisogna sempre chiedersi:

questo popup aggiunge valore o interrompe l’utente?

Se aggiunge valore, ha senso.

Se interrompe soltanto, meglio evitarlo.

Conclusione

Creare un popup exit intent in WordPress senza plugin è assolutamente possibile.

Con GeneratePress, GenerateBlocks e uno snippet personalizzato possiamo ottenere un popup leggero, elegante e controllabile in ogni dettaglio.

La soluzione vista in questo articolo permette di:

  • costruire il popup graficamente con GenerateBlocks;
  • mostrarlo solo nelle pagine desiderate;
  • attivarlo su desktop quando l’utente sta per uscire;
  • gestire un fallback intelligente su mobile;
  • evitare visualizzazioni continue grazie a localStorage e sessionStorage;
  • migliorare l’accessibilità con attributi ARIA e focus trap;
  • mantenere il sito più leggero rispetto all’uso di un plugin dedicato.

È una soluzione perfetta quando si vuole aggiungere una funzione utile al sito, ma senza sacrificare performance, controllo e pulizia del codice.

Come spesso accade in WordPress, il punto non è aggiungere l’ennesimo plugin, ma capire davvero cosa serve e costruirlo nel modo più semplice possibile.

Potrebbe interessarti anche...

Tag cloud tridimensionale in WordPress senza plugin: come crearla per i singoli articoli

Tag cloud tridimensionale in WordPress senza plugin: come crearla per i singoli articoli

08/06/2026

Scopri come creare una tag cloud tridimensionale in WordPress senza installare plugin. In questa guida viene spiegato come usare uno shortcode personalizzato per mostrare una nuvola di tag animata solo nei singoli articoli, recuperando automaticamente i tag presenti nel sito e permettendo di modificare facilmente velocità di rotazione, numero di tag e dimensione della cloud.

Leggi l'articolo
Come creare uno slider con GeneratePress e GenerateBlocks senza usare page builder pesanti

Come creare uno slider con GeneratePress e GenerateBlocks senza usare page builder pesanti

01/06/2026

Creare uno slider con GeneratePress e GenerateBlocks aggiunge dinamismo senza plugin pesanti. Lo costruiremo con Container, classi CSS, pulsanti e JavaScript leggero per scorrere le slide, mantenendolo responsive e modificabile da WordPress.

Leggi l'articolo
Timeline in HTML e CSS: creare una linea temporale responsive senza plugin

Timeline in HTML e CSS: creare una linea temporale responsive senza plugin

25/05/2026

Scopri come creare una timeline responsive in HTML e CSS senza usare plugin. In questa guida vediamo passo passo come costruire una linea temporale verticale, con card alternate su desktop e layout ottimizzato per mobile, utile per raccontare la storia di un progetto, le fasi di un servizio o il percorso professionale di una persona.

Leggi l'articolo

Hai una domanda su questo articolo?

Scrivimi pure nei commenti: ti risponderò il prima possibile con un suggerimento pratico.

...aspetta un attimo!

Hai un’idea per un sito, una pagina da migliorare o un progetto ancora da mettere a fuoco? Raccontamelo: possiamo capire insieme da dove partire.

Parlami della tua idea