Questo articolo è stato pubblicato per la prima volta nel 2006 e completamente riscritto a ottobre 2026, unendo in un’unica guida due post del 2005 e del 2006 dedicati al movimento del mouse e ai popup trascinabili: i commenti più vecchi si riferiscono alla versione originale.
Nel novembre del 2005 inauguravo una «prima puntata» dedicata al drag and drop spiegando come catturare il movimento del mouse con document.onmousemove, con tanto di riga magica if (!e) var e = window.event; per far contento Internet Explorer, e pochi mesi dopo, nell’aprile del 2006, mettevo a frutto quegli esperimenti scrivendo JsPopUpBox, una funzione basata su jQuery che creava una finestra trascinabile, con gli stili definiti inline da codice, uno z-index a 100 scelto con criteri scientifici e un pulsante di chiusura che era, letteralmente, una «x» dentro un div. Il tutto introdotto da una serie di proverbi sul fatto che chi fa da sé fa per tre, che rileggo oggi con una certa tenerezza.
Oggi quasi tutto quel codice si può buttare, e per una volta è una bella notizia: il browser ci offre l’elemento <dialog>, che sa già come aprirsi sopra la pagina, gestire il focus e chiudersi con il tasto Esc, e i Pointer Events, che con un unico codice gestiscono mouse, dita e penne. In questo articolo, che riunisce e sostituisce i due post di allora, ti mostro come costruire una finestra modale trascinabile moderna, accessibile e senza librerie.
L’elemento dialog: la finestra che il browser sa già gestire
L’elemento <dialog> è il modo nativo di creare una finestra di dialogo in HTML: aperto con il metodo showModal() si posiziona sopra tutto il resto della pagina, rende inerte il contenuto sottostante, si chiude con il tasto Esc e, alla chiusura, riporta il focus sull’elemento che lo aveva aperto. È supportato da tutti i browser principali ed è considerato «Baseline widely available» dal marzo 2022, per cui puoi usarlo senza timori.
Quello che nel 2006 dovevo scrivere a mano oggi è compreso nel prezzo: lo sfondo scuro dietro la finestra si ottiene con lo pseudo elemento ::backdrop, il primo elemento attivabile riceve il focus all’apertura (oppure quello con l’attributo autofocus), la pressione di Esc genera un evento cancel seguito da close, e un modulo con method="dialog" chiude la finestra restituendo il valore del pulsante premuto in returnValue. Esiste anche il metodo show(), che apre una finestra non modale lasciando la pagina utilizzabile, utile per le palette di strumenti, ma in quel caso Esc non chiude nulla e tutto il resto è a carico tuo.
Da qualche tempo, infine, è arrivato l’attributo closedby, che permette di decidere se la finestra si possa chiudere anche cliccando fuori (any), solo con Esc o con i tuoi pulsanti (closerequest, il comportamento predefinito dei modali) oppure soltanto con i tuoi pulsanti (none): il supporto non è ancora uniforme fra i browser, per cui conviene considerarlo un miglioramento progressivo e controllare la tabella di compatibilità prima di affidargli funzioni importanti.
Pointer Events: un solo codice per mouse, dita e penne
I Pointer Events sono gli eventi che il browser genera per qualsiasi dispositivo di puntamento, e per trascinare un elemento ne bastano quattro: pointerdown quando premi, pointermove mentre ti sposti, pointerup quando rilasci e pointercancel quando il sistema interrompe il gesto. Al posto del vecchio onmousemove agganciato all’intero documento, che nel 2005 serviva a non «perdere» il mouse quando si muoveva troppo velocemente, oggi si usa setPointerCapture(): dopo la pressione tutti gli eventi di quel puntatore vengono indirizzati all’elemento che lo ha catturato, finché la cattura non viene rilasciata in modo esplicito o con il pointerup.

C’è un solo dettaglio che non va dimenticato: sui dispositivi touch il browser interpreta il trascinamento del dito come scorrimento o zoom della pagina, e per evitarlo l’area di trascinamento deve avere la proprietà CSS touch-action: none. Senza questa riga il codice funziona benissimo con il mouse e in modo misterioso sul telefono, che è esattamente il genere di bug che si scopre il giorno della demo al cliente.
Il codice completo della finestra trascinabile
Il codice che segue crea una finestra modale con una barra del titolo trascinabile, un pulsante di chiusura e un pulsante di spostamento utilizzabile da tastiera; non ha dipendenze e puoi incollarlo in una pagina HTML o, in WordPress, in un blocco HTML personalizzato o nel tuo tema. Partiamo dal markup.
<button type="button" id="apri-finestra">Apri le impostazioni</button>
<dialog id="finestra" class="finestra" aria-labelledby="finestra-titolo">
<div class="finestra-barra">
<!-- Pulsante di spostamento: alternativa da tastiera al trascinamento. -->
<button type="button" class="finestra-sposta" aria-label="Sposta la finestra"
aria-describedby="finestra-aiuto">⠿</button>
<h2 id="finestra-titolo">Impostazioni</h2>
<!-- autofocus: all'apertura il focus va sul pulsante di chiusura. -->
<button type="button" class="finestra-chiudi" aria-label="Chiudi" autofocus>×</button>
</div>
<p id="finestra-aiuto" class="screen-reader-text">
Trascina la barra del titolo oppure usa i tasti freccia su questo pulsante. Maiuscolo e freccia spostano di più.
</p>
<div class="finestra-corpo">
<p>Qui va il contenuto della finestra.</p>
</div>
</dialog>Gli stili sono pochi: la barra ha il cursore di spostamento, touch-action: none per i dispositivi touch e user-select: none per non selezionare il testo durante il trascinamento.
.finestra {
width: min(28rem, calc(100vw - 2rem));
padding: 0;
border: 1px solid #1d2b4f;
border-radius: 8px;
}
/* Sfondo semitrasparente dietro la finestra modale. */
.finestra::backdrop {
background: rgb(0 0 0 / 0.4);
}
.finestra-barra {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: #1d2b4f;
color: #fff;
cursor: move;
touch-action: none; /* Indispensabile per trascinare con le dita. */
user-select: none;
}
.finestra-barra h2 {
flex: 1;
margin: 0;
font-size: 1rem;
}
.finestra-corpo {
padding: 1rem;
}Infine il JavaScript, che apre e chiude la finestra, gestisce il trascinamento con i Pointer Events e lo spostamento con i tasti freccia. La posizione viene applicata con la proprietà CSS translate, che sposta la finestra rispetto alla posizione centrata scelta dal browser senza dover calcolare coordinate assolute.
( () => {
const finestra = document.getElementById( 'finestra' );
const apri = document.getElementById( 'apri-finestra' );
const barra = finestra.querySelector( '.finestra-barra' );
const sposta = finestra.querySelector( '.finestra-sposta' );
const chiudi = finestra.querySelector( '.finestra-chiudi' );
// Spostamento corrente rispetto alla posizione iniziale centrata.
let x = 0;
let y = 0;
// Punto di partenza del trascinamento: null quando non si sta trascinando.
let inizio = null;
const posiziona = ( nuovaX, nuovaY ) => {
x = nuovaX;
y = nuovaY;
finestra.style.translate = `${ x }px ${ y }px`;
};
// Apertura modale: Esc, focus iniziale e ripristino del focus li gestisce il browser.
apri.addEventListener( 'click', () => finestra.showModal() );
chiudi.addEventListener( 'click', () => finestra.close() );
// 1. Pressione: memorizza il punto di partenza e cattura il puntatore.
barra.addEventListener( 'pointerdown', ( evento ) => {
// Solo il tasto principale, e mai dal pulsante di chiusura.
if ( evento.button !== 0 || evento.target.closest( '.finestra-chiudi' ) ) {
return;
}
inizio = { px: evento.clientX, py: evento.clientY, x, y };
barra.setPointerCapture( evento.pointerId );
} );
// 2. Movimento: sposta la finestra della stessa distanza percorsa dal puntatore.
barra.addEventListener( 'pointermove', ( evento ) => {
if ( ! inizio ) {
return;
}
posiziona( inizio.x + evento.clientX - inizio.px, inizio.y + evento.clientY - inizio.py );
} );
// 3. Rilascio o interruzione: fine del trascinamento (la cattura si rilascia da sola).
const termina = () => {
inizio = null;
};
barra.addEventListener( 'pointerup', termina );
barra.addEventListener( 'pointercancel', termina );
// Alternativa da tastiera: frecce di 10 pixel, con Maiuscolo di 50.
sposta.addEventListener( 'keydown', ( evento ) => {
const passo = evento.shiftKey ? 50 : 10;
const direzioni = {
ArrowLeft: [ -passo, 0 ],
ArrowRight: [ passo, 0 ],
ArrowUp: [ 0, -passo ],
ArrowDown: [ 0, passo ],
};
const delta = direzioni[ evento.key ];
if ( ! delta ) {
return;
}
evento.preventDefault(); // Evita lo scorrimento della pagina.
posiziona( x + delta[ 0 ], y + delta[ 1 ] );
} );
} )();Confrontato con JsPopUpBox, questo codice è più corto pur facendo molto di più: non ci sono variabili globali sparse come dragEnable e objDrag, non serve distinguere fra e e window.event, non c’è bisogno di agganciare e sganciare gli eventi dal body e funziona allo stesso modo con il mouse, con il dito e con la penna.
Accessibilità: focus, Esc e un’alternativa al trascinamento
Una finestra modale è accessibile quando ha un nome, quando sposta il focus al suo interno all’apertura e lo restituisce alla chiusura, quando si chiude con Esc e quando ogni sua funzione è utilizzabile anche senza mouse. Con <dialog> e showModal() le prime quattro condizioni sono soddisfatte quasi gratis: il nome arriva dal titolo collegato con aria-labelledby, il browser gestisce focus ed Esc e il resto della pagina diventa inerte, per cui il focus non può «scappare» dietro la finestra.
Il trascinamento merita invece un’attenzione in più, perché le WCAG 2.2 hanno introdotto il criterio 2.5.7 sui movimenti di trascinamento, di livello AA, che chiede di offrire un’alternativa con un singolo puntatore e senza trascinare. Nel nostro esempio il pulsante di spostamento risponde ai tasti freccia ed è descritto da un testo nascosto che spiega come usarlo; per soddisfare pienamente il criterio anche per chi usa un singolo tocco puoi aggiungere quattro piccoli pulsanti direzionali, oppure un menu con le posizioni predefinite.
Il miglior codice di accessibilità è quello che non devi scrivere: ogni comportamento che il browser offre nativamente è un comportamento testato da milioni di utenti prima di te.
Infine, qualche regola di buon senso: il pulsante di chiusura deve essere un vero <button> con un nome comprensibile, come il nostro aria-label="Chiudi", e non una «x» in un div come quella del 2006; la finestra non deve aprirsi da sola al caricamento della pagina; e il contenuto deve restare leggibile anche su schermi piccoli, motivo per cui la larghezza è limitata a quella della finestra del browser.
Quando una finestra trascinabile ha senso, e quando no
Una finestra trascinabile ha senso quando l’utente deve vedere contemporaneamente il contenuto della finestra e quello della pagina, come in un editor, in una dashboard o in uno strumento di configurazione, mentre per conferme, avvisi e moduli brevi un normale modale centrato è quasi sempre la scelta migliore. Spostare una finestra è un’azione in più che chiedi all’utente, e conviene chiederla solo quando porta un vantaggio concreto.
Sui telefoni, poi, lo spazio per trascinare è poco e lo spostamento diventa più un ostacolo che un aiuto: valuta di disattivarlo sotto una certa larghezza, lasciando la finestra centrata o a tutto schermo. E se la tua finestra deve restare aperta mentre l’utente continua a lavorare sulla pagina, prendi in considerazione il metodo show() al posto di showModal(), ricordando però che in quel caso la gestione del focus e della chiusura con Esc tornano a essere un compito tuo.
Tirando le somme
Fra il 2005 e oggi una finestra trascinabile è passata da un esercizio di equilibrismo fra browser incompatibili, librerie e variabili globali a poche decine di righe di codice standard: <dialog> con showModal() per la finestra, i Pointer Events con setPointerCapture() e touch-action: none per il trascinamento, e un’alternativa da tastiera per non lasciare indietro nessuno. Il proverbio «chi fa da sé fa per tre» vale ancora, ma con un’aggiunta: prima di fare da sé conviene controllare che cosa il browser sa già fare.
Per approfondire ti consiglio le pagine di MDN sull’elemento dialog e su setPointerCapture() e il criterio 2.5.7 delle WCAG 2.2; se ti interessa lo stesso approccio applicato ai dati, leggi l’articolo sulle select dipendenti con fetch e REST API, e trovi altri spunti nella categoria WordPress. Se usavi anche tu window.event o hai scritto il tuo JsPopUpBox, raccontamelo nei commenti e condividi l’articolo con chi sta per installare una libreria solo per spostare una finestra. Se invece hai un progetto da rimettere in ordine, puoi prenotare una call conoscitiva gratuita o passare dai contatti.


Ciao,
ho trovato molto interessante questo articolo e volevo gentilmente chiederti, se e come era possibile poter salvare anche la posizione della window una volta definita e posizionata, associandola all’utente che la generata, like live.com o netvibes.com.
Grazie
Pietro
la prima cosa che mi viene in mente e salvarlo direttamente nel cookie tramite javascript creando una funzione che va eseguira dopo ogni movimento.
Questo però nel caso di pulizia dei cookie da parte del client, o cambio macchina, ci fa perdere tutte le posizioni.
Quindi anche se decisamente più complicato, una soluzione basata su autenticazione utente + ajax ci permette di salvare i dati lato server, magari organizzando un database a questo proposito.
Questa seconda strada penso sia quella adottata dai siti da te citati
Ciao,
è possibile tramite jquery creare una Slide show con dissolvenza?
grazie
Ho scritto un post su jquery e sui suoi plugin giunsto qualche giorno fa … ( https://maurizio.mavida.com/2006/08/21/jquery-piu-lo-uso-e-piu-mi-piace/ )
Il plugin è già bello che fatto e se non ti piace ci sono i sorgenti per personalizzartelo ….
Questi sono i link:
http://www.eyecon.ro/interface/
http://www.eyecon.ro/interface/slideshow.html
Sembra ottimo, ma non riesco a farlo funzionare, forse perchè non riesco a fare un copia e incolla corretto del codice. Se puoi inviarmelo. Ti ringrazio.
@Cat,
qui hai la pagina della demo…
se copi quello sono sicuro che funzionerà anche a te 😉