Come rilevare se vengono premuti più tasti contemporaneamente utilizzando JavaScript?


173

Sto cercando di sviluppare un motore di gioco JavaScript e ho riscontrato questo problema:

  • Quando premo SPACEil personaggio salta.
  • Quando premo il personaggio si sposta a destra.

Il problema è che quando premo a destra e poi premo spazio, il personaggio salta e poi si ferma.

Uso la keydownfunzione per premere il tasto. Come posso verificare se ci sono più tasti premuti contemporaneamente?


3
Ecco una demo di una pagina Web che stampa automaticamente un elenco di tutti i tasti premuti: stackoverflow.com/a/13651016/975097
Anderson Green

Risposte:


327

Nota: keyCode è ora obsoleto.

Il rilevamento di più tasti è facile se si capisce il concetto

Il modo in cui lo faccio è così:

var map = {}; // You could also use an array
onkeydown = onkeyup = function(e){
    e = e || event; // to deal with IE
    map[e.keyCode] = e.type == 'keydown';
    /* insert conditional here */
}

Questo codice è molto semplice: poiché il computer passa solo una sequenza di tasti alla volta, viene creato un array per tenere traccia di più chiavi. L'array può quindi essere utilizzato per verificare una o più chiavi contemporaneamente.

Giusto per spiegare, diciamo che premi Ae B, ciascuno genera un keydownevento che si imposta map[e.keyCode]sul valore di e.type == keydown, che viene valutato su vero o falso . Ora entrambi map[65]e map[66]sono impostati su true. Quando lo lasci andare A, l' keyupevento si attiva, facendo sì che la stessa logica determini il risultato opposto permap[65] (A), che ora è falso , ma poiché map[66](B) è ancora "inattivo" (non ha attivato un evento keyup), rimane vero .

L' maparray, attraverso entrambi gli eventi, si presenta così:

// keydown A 
// keydown B
[
    65:true,
    66:true
]
// keyup A
// keydown B
[
    65:false,
    66:true
]

Ci sono due cose che puoi fare ora:

A) Un key logger ( esempio ) può essere creato come riferimento per in seguito quando si desidera capire rapidamente uno o più codici chiave. Supponendo di aver definito un elemento html e di averlo puntato con la variabileelement .

element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
    if(map[i]){
        element.innerHTML += '<hr>' + i;
    }
}

Nota: puoi facilmente afferrare un elemento dal suo idattributo.

<div id="element"></div>

Questo crea un elemento HTML che può essere facilmente referenziato in JavaScript con element

alert(element); // [Object HTMLDivElement]

Non devi nemmeno usarlo document.getElementById()o $()afferrarlo. Ma per motivi di compatibilità, l'uso di jQuery $()è più ampiamente raccomandato.

Assicurati solo che il tag dello script segua il corpo dell'HTML. Suggerimento per l'ottimizzazione : La maggior parte dei siti web di grandi nomi mettere il tag script dopo il tag body per l'ottimizzazione. Questo perché il tag dello script blocca il caricamento di ulteriori elementi fino al termine del download dello script. Metterlo davanti al contenuto consente il caricamento anticipato del contenuto.

B (che è dove si trova il tuo interesse) Puoi controllare una o più chiavi in ​​un momento in cui si /*insert conditional here*/trovava, prendi questo esempio:

if(map[17] && map[16] && map[65]){ // CTRL+SHIFT+A
    alert('Control Shift A');
}else if(map[17] && map[16] && map[66]){ // CTRL+SHIFT+B
    alert('Control Shift B');
}else if(map[17] && map[16] && map[67]){ // CTRL+SHIFT+C
    alert('Control Shift C');
}

Modifica : non è lo snippet più leggibile. La leggibilità è importante, quindi potresti provare qualcosa del genere per rendere più facile la vista:

function test_key(selkey){
    var alias = {
        "ctrl":  17,
        "shift": 16,
        "A":     65,
        /* ... */
    };

    return key[selkey] || key[alias[selkey]];
}

function test_keys(){
    var keylist = arguments;

    for(var i = 0; i < keylist.length; i++)
        if(!test_key(keylist[i]))
            return false;

    return true;
}

Uso:

test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')

È meglio questo?

if(test_keys('ctrl', 'shift')){
    if(test_key('A')){
        alert('Control Shift A');
    } else if(test_key('B')){
        alert('Control Shift B');
    } else if(test_key('C')){
        alert('Control Shift C');
    }
}

(fine della modifica)


Questo esempio assegni per CtrlShiftA, CtrlShiftBeCtrlShiftC

È così semplice :)

Appunti

Tenere traccia dei KeyCodes

Come regola generale, è buona norma documentare il codice, in particolare cose come i codici chiave (come // CTRL+ENTER) in modo da poter ricordare quali fossero.

Dovresti anche inserire i codici chiave nello stesso ordine della documentazione ( CTRL+ENTER => map[17] && map[13], NON map[13] && map[17]). In questo modo non ti confonderai mai quando dovrai tornare indietro e modificare il codice.

Un gotcha con catene if-else

Se cerchi combo di quantità diverse (come CtrlShiftAltEntere CtrlEnter), metti combo più piccole dopo combo più grandi, altrimenti le combo più piccole sostituiranno le combo più grandi se sono abbastanza simili. Esempio:

// Correct:
if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!')
}

// Incorrect:
if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!');
}
// What will go wrong: When trying to do CTRL+SHIFT+ENTER, it will
// detect CTRL+ENTER first, and override CTRL+SHIFT+ENTER.
// Removing the else's is not a proper solution, either
// as it will cause it to alert BOTH "Mr. Power user" AND "You Found Me"

Gotcha: "Questa combinazione di tasti continua ad attivarsi anche se non premo i tasti"

Quando si ha a che fare con avvisi o qualsiasi cosa che attiri l'attenzione dalla finestra principale, è possibile che si desideri includere la map = []reimpostazione dell'array al termine della condizione. Questo perché alcune cose, ad esempio alert(), allontanano lo stato attivo dalla finestra principale e impediscono l'attivazione dell'evento 'keyup'. Per esempio:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Oh noes, a bug!');
}
// When you Press any key after executing this, it will alert again, even though you 
// are clearly NOT pressing CTRL+ENTER
// The fix would look like this:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Take that, bug!');
    map = {};
}
// The bug no longer happens since the array is cleared

Gotcha: valori predefiniti del browser

Ecco una cosa fastidiosa che ho trovato, con la soluzione inclusa:

Problema: poiché il browser di solito ha azioni predefinite su combinazioni di tasti (come ad esempio CtrlDattiva la finestra dei segnalibri o CtrlShiftCattiva skynote su maxthon), potresti anche voler aggiungere return falsedopo map = [], quindi gli utenti del tuo sito non saranno frustrati quando il "File duplicato" la funzione, essendo attivata CtrlD, aggiunge invece la pagina ai segnalibri.

if(map[17] && map[68]){ // CTRL+D
    alert('The bookmark window didn\'t pop up!');
    map = {};
    return false;
}

Senza return false, la finestra dei segnalibri si aprirà, con sgomento dell'utente.

La dichiarazione di ritorno (nuova)

Va bene, quindi non sempre vuoi uscire dalla funzione in quel punto. Ecco perché la event.preventDefault()funzione è lì. Ciò che fa è impostare un flag interno che dice all'interprete di non consentire al browser di eseguire la sua azione predefinita. Successivamente, l'esecuzione della funzione continua (mentre returnuscirà immediatamente dalla funzione).

Comprendi questa distinzione prima di decidere se utilizzare return falseoe.preventDefault()

event.keyCode è deprecato

L'utente SeanVieira ha sottolineato nei commenti event.keyCodedeprecato.

Lì, ha dato un'ottima alternativa event.key:, che restituisce una rappresentazione in forma di stringa del tasto premuto, come "a"per Ao "Shift"perShift .

Sono andato avanti e ho messo a punto uno strumento per esaminare tali stringhe.

element.onevent vs element.addEventListener

I gestori registrati con addEventListenerpossono essere impilati e chiamati nell'ordine di registrazione, mentre l'impostazione .oneventdiretta è piuttosto aggressiva e annulla qualsiasi cosa in precedenza.

document.body.onkeydown = function(ev){
    // do some stuff
    ev.preventDefault(); // cancels default actions
    return false; // cancels this function as well as default actions
}

document.body.addEventListener("keydown", function(ev){
    // do some stuff
    ev.preventDefault() // cancels default actions
    return false; // cancels this function only
});

La .oneventproprietà sembra sovrascrivere tutto e il comportamento di ev.preventDefault()ereturn false; può essere piuttosto imprevedibile.

In entrambi i casi, i gestori registrati tramite addEventlistener sembrano essere più facili da scrivere e ragionare.

C'è anche attachEvent("onevent", callback)dall'implementazione non standard di Internet Explorer, ma è oltremodo obsoleta e non riguarda nemmeno JavaScript (si riferisce a un linguaggio esoterico chiamato JScript ). Sarebbe nel tuo interesse evitare il codice poliglotta il più possibile.

Una classe di aiuto

Per risolvere confusione / reclami, ho scritto una "classe" che fa questa astrazione ( link pastebin ):

function Input(el){
    var parent = el,
        map = {},
        intervals = {};
    
    function ev_kdown(ev)
    {
        map[ev.key] = true;
        ev.preventDefault();
        return;
    }
    
    function ev_kup(ev)
    {
        map[ev.key] = false;
        ev.preventDefault();
        return;
    }
    
    function key_down(key)
    {
        return map[key];
    }

    function keys_down_array(array)
    {
        for(var i = 0; i < array.length; i++)
            if(!key_down(array[i]))
                return false;

        return true;
    }
    
    function keys_down_arguments()
    {
        return keys_down_array(Array.from(arguments));
    }
    
    function clear()
    {
        map = {};
    }
    
    function watch_loop(keylist, callback)
    {
        return function(){
            if(keys_down_array(keylist))
                callback();
        }
    }

    function watch(name, callback)
    {
        var keylist = Array.from(arguments).splice(2);

        intervals[name] = setInterval(watch_loop(keylist, callback), 1000/24);
    }

    function unwatch(name)
    {
        clearInterval(intervals[name]);
        delete intervals[name];
    }

    function detach()
    {
        parent.removeEventListener("keydown", ev_kdown);
        parent.removeEventListener("keyup", ev_kup);
    }
    
    function attach()
    {
        parent.addEventListener("keydown", ev_kdown);
        parent.addEventListener("keyup", ev_kup);
    }
    
    function Input()
    {
        attach();

        return {
            key_down: key_down,
            keys_down: keys_down_arguments,
            watch: watch,
            unwatch: unwatch,
            clear: clear,
            detach: detach
        };
    }
    
    return Input();
}

Questa classe non fa tutto e non gestisce tutti i possibili casi d'uso. Non sono un ragazzo delle biblioteche. Ma per un uso interattivo generale dovrebbe andare bene.

Per utilizzare questa classe, creare un'istanza e puntarla sull'elemento a cui si desidera associare l'input da tastiera:

var input_txt = Input(document.getElementById("txt"));

input_txt.watch("print_5", function(){
    txt.value += "FIVE ";
}, "Control", "5");

Ciò che farà sarà collegare un nuovo listener di input all'elemento con #txt(supponiamo che sia una textarea) e impostare un punto di controllo per la combinazione di tasti Ctrl+5. Quando entrambi Ctrle 5sono inattivi, verrà chiamata la funzione di callback che hai passato (in questo caso, una funzione che si aggiunge "FIVE "alla textarea). Il callback è associato al nome print_5, quindi per rimuoverlo basta usare:

input_txt.unwatch("print_5");

Per staccare input_txtdal txtelemento:

input_txt.detach();

In questo modo, la garbage collection può raccogliere l'oggetto ( input_txt), nel caso in cui fosse gettato via, e non avresti più un vecchio ascoltatore di eventi zombi.

Per completezza, ecco un rapido riferimento all'API della classe, presentata in stile C / Java in modo da sapere cosa restituiscono e quali argomenti si aspettano.

Boolean  key_down (String key);

Restituisce truese keyè inattivo, falso altrimenti.

Boolean  keys_down (String key1, String key2, ...);

Restituisce truese tutte le chiavi key1 .. keyNsono inattive, false in caso contrario.

void     watch (String name, Function callback, String key1, String key2, ...);

Crea un "punto di controllo" in modo tale che premendo tutto keyNsi attiverà la richiamata

void     unwatch (String name);

Rimuove detto punto di controllo tramite il suo nome

void     clear (void);

Cancella la cache "chiavi in ​​giù". Equivalente a map = {}sopra

void     detach (void);

Stacca gli ascoltatori ev_kdowne ev_kupdall'elemento genitore, rendendo possibile eliminare in modo sicuro l'istanza

Aggiornamento 02-12-2017 In risposta a una richiesta di pubblicazione su github, ho creato una sintesi .

Aggiornamento 21/07/2018 Ho giocato con la programmazione in stile dichiarativo per un po ', e in questo modo ora è il mio preferito: violino , pastebin

In generale, funzionerà con i casi che vorresti realisticamente (ctrl, alt, shift), ma se hai bisogno di colpire, diciamo, a+wallo stesso tempo, non sarebbe troppo difficile "combinare" gli approcci in un multi-key-lookup.


Spero che questa risposta completamente spiegata mini-blog sia stata utile :)


Ho appena fatto un grande aggiornamento a questa risposta! L'esempio di keylogger è più coerente, ho aggiornato la formattazione in modo che la sezione "note" fosse più facile da leggere e ho aggiunto una nuova nota su return falsevspreventDefault()
Braden Best

Che dire di quando si tiene premuto un tasto con il documento attivo, quindi si fa clic sulla casella URL e si rilascia il tasto. il keyup non viene mai attivato, tuttavia la chiave è attiva e l'elenco non è corretto. Inoltre, viceversa: premere / tenere premuto il tasto nella casella URL, il keydown non viene mai attivato, quindi mettere a fuoco il documento e lo stato del keydown non è nell'elenco. Fondamentalmente ogni volta che il documento riacquista il focus non si può mai essere sicuri dello stato della chiave.
user3015682,

3
NB: keyCodeè obsoleto - se si passa a, keysi ottiene l'attuale rappresentazione dei caratteri della chiave che può essere piacevole.
Sean Vieira,

1
@SeanVieira Quindi, di nuovo, puoi fare anche cose strane in C. Ad esempio, lo sapevi che myString[5]è lo stesso di 5[myString]e non ti darà nemmeno un avviso di compilazione (anche con -Wall -pedantic)? È perché la pointer[offset]notazione prende il puntatore, aggiunge l'offset e quindi dereferenzia il risultato, facendo myString[5]lo stesso di *(myString + 5).
Braden Best

1
@inorganik ti riferisci alla classe helper? I Gists possono essere usati come repository? Sarebbe noioso fare un intero repository per un piccolo frammento di codice. Certo, farò un'idea. Sparo per stasera. Midnight mountain Time -ish
Braden Best

30

Dovresti usare l' evento keydown per tenere traccia dei tasti premuti e dovresti usare l' evento keyup per tenere traccia di quando i tasti vengono rilasciati.

Vedi questo esempio: http://jsfiddle.net/vor0nwe/mkHsU/

(Aggiornamento: sto riproducendo il codice qui, nel caso jsfiddle.net bails :) L'HTML:

<ul id="log">
    <li>List of keys:</li>
</ul>

... e Javascript (usando jQuery):

var log = $('#log')[0],
    pressedKeys = [];

$(document.body).keydown(function (evt) {
    var li = pressedKeys[evt.keyCode];
    if (!li) {
        li = log.appendChild(document.createElement('li'));
        pressedKeys[evt.keyCode] = li;
    }
    $(li).text('Down: ' + evt.keyCode);
    $(li).removeClass('key-up');
});

$(document.body).keyup(function (evt) {
    var li = pressedKeys[evt.keyCode];
    if (!li) {
       li = log.appendChild(document.createElement('li'));
    }
    $(li).text('Up: ' + evt.keyCode);
    $(li).addClass('key-up');
});

In questo esempio, sto usando un array per tenere traccia di quali tasti vengono premuti. In un'applicazione reale, potresti volerlodelete ciascun elemento una volta rilasciata la chiave associata.

Nota che mentre ho usato jQuery per rendere le cose facili per me stesso in questo esempio, il concetto funziona altrettanto bene quando si lavora in Javascript "grezzo".


Ma come ho pensato c'è un bug. Se tieni premuto un pulsante, passa a un'altra scheda (o messa a fuoco libera) mentre tieni ancora premuto il pulsante quando ti concentri nuovamente sullo scrit, mostrerà che il pulsante è premuto anche se non lo è. : D
XCS

3
@Cristy: quindi è anche possibile aggiungere un onblurgestore eventi, che rimuove dall'array tutti i tasti premuti. Una volta persa la messa a fuoco, sarebbe logico dover premere nuovamente tutti i tasti. Sfortunatamente, non esiste un JS equivalente a GetKeyboardState.
Martijn,

1
Problemi con Incolla su un Mac (Chrome). Ottiene correttamente il keydown 91 (comando), il keydown 86 (v), ma poi esegue solo il keydown del 91, lasciando 86 down. Elenco di tasti: Su: 91, Giù: 86. Questo sembra accadere solo quando si rilascia il secondo tasto di comando - se lo lascio andare prima registra correttamente il keyup su entrambi.
James Alday,

2
Sembra che quando si premono tre o più tasti contemporaneamente, smette di rilevare altri tasti verso il basso fino a quando non si solleva uno. (Testato con Firefox 22)
Qvcool,

1
@JamesAlday Stesso problema. Apparentemente influenza solo il tasto Meta (OS) sui Mac. Vedi il numero 3 qui: bitspushedaround.com/…
Don McCurdy,

20
document.onkeydown = keydown; 

function keydown (evt) { 

    if (!evt) evt = event; 

    if (evt.ctrlKey && evt.altKey && evt.keyCode === 115) {

        alert("CTRL+ALT+F4"); 

    } else if (evt.shiftKey && evt.keyCode === 9) { 

        alert("Shift+TAB");

    } 

}

1
Era tutto ciò che desideravo, la migliore risposta
Randall Coding,

7

Ho usato in questo modo (ho dovuto controllare ovunque sia premuto Maiusc + Ctrl):

// create some object to save all pressed keys
var keys = {
    shift: false,
    ctrl: false
};

$(document.body).keydown(function(event) {
// save status of the button 'pressed' == 'true'
    if (event.keyCode == 16) {
        keys["shift"] = true;
    } else if (event.keyCode == 17) {
        keys["ctrl"] = true;
    }
    if (keys["shift"] && keys["ctrl"]) {
        $("#convert").trigger("click"); // or do anything else
    }
});

$(document.body).keyup(function(event) {
    // reset status of the button 'released' == 'false'
    if (event.keyCode == 16) {
        keys["shift"] = false;
    } else if (event.keyCode == 17) {
        keys["ctrl"] = false;
    }
});

5

per chi necessita di un codice di esempio completo. Destro + Sinistro aggiunto

var keyPressed = {};
document.addEventListener('keydown', function(e) {

   keyPressed[e.key + e.location] = true;

    if(keyPressed.Shift1 == true && keyPressed.Control1 == true){
        // Left shift+CONTROL pressed!
        keyPressed = {}; // reset key map
    }
    if(keyPressed.Shift2 == true && keyPressed.Control2 == true){
        // Right shift+CONTROL pressed!
        keyPressed = {};
    }

}, false);

document.addEventListener('keyup', function(e) {
   keyPressed[e.key + e.location] = false;

   keyPressed = {};
}, false);

3

Rendi il keydown anche chiamare più funzioni, con ciascuna funzione che controlla un tasto specifico e risponde in modo appropriato.

document.keydown = function (key) {

    checkKey("x");
    checkKey("y");
};

2

Proverei ad aggiungere un keypress Eventgestore keydown. Per esempio:

window.onkeydown = function() {
    // evaluate key and call respective handler
    window.onkeypress = function() {
       // evaluate key and call respective handler
    }
}

window.onkeyup = function() {
    window.onkeypress = void(0) ;
}

Questo ha solo lo scopo di illustrare uno schema; Non entrerò nei dettagli qui (soprattutto non nel livello 2 + Eventregistrazione specifico del browser ).

Pubblica di nuovo per favore se questo aiuta o no.


1
Questo non avrebbe funzionato: pressione di un tasto non attiva su un sacco di chiavi che keydown e keyup fare grilletto. Inoltre, non tutti i browser attivano ripetutamente eventi di keydown.
Martijn,

Quirksmode dice il tuo errore: quirksmode.org/dom/events/keys.html . Ma non sosterrò questo dato che non ho testato la mia proposta.
FK82,

Citato da quella pagina: "Quando l'utente preme tasti speciali come i tasti freccia, il browser NON deve attivare eventi di pressione dei tasti" . Per quanto riguarda le ripetizioni, elenca Opera e Konqueror come non lo fanno correttamente.
Martijn,

2

Se uno dei tasti premuti è Alt / Crtl / Shift è possibile utilizzare questo metodo:

document.body.addEventListener('keydown', keysDown(actions) );

function actions() {
   // do stuff here
}

// simultaneous pressing Alt + R
function keysDown (cb) {
  return function (zEvent) {
    if (zEvent.altKey &&  zEvent.code === "KeyR" ) {
      return cb()
    }
  }
}

2
    $(document).ready(function () {
        // using ascii 17 for ctrl, 18 for alt and 83 for "S"
        // ctr+alt+S
        var map = { 17: false, 18: false, 83: false };
        $(document).keyup(function (e) {
            if (e.keyCode in map) {
                map[e.keyCode] = true;
                if (map[17] && map[18] && map[83]) {
                    // Write your own code here, what  you want to do
                    map[17] = false;
                    map[18] = false;
                    map[83] = false;
                }
            }
            else {
                // if u press any other key apart from that "map" will reset.
                map[17] = false;
                map[18] = false;
                map[83] = false;
            }
        });

    });

Grazie per il tuo contributo si prega di provare a non solo inserire il codice, aggiungere qualche spiegazione.
Tim Rutter,

2

Questo non è un metodo universale, ma è utile in alcuni casi. È utile per combinazioni come CTRL+ somethingo Shift+ somethingo CTRL+ Shift+ something, ecc.

Esempio: quando si desidera stampare una pagina utilizzando CTRL+ P, il primo tasto premuto è sempre CTRLseguito da P. Lo stesso vale per CTRL+ S, CTRL+ Ue altre combinazioni.

document.addEventListener('keydown',function(e){
      
    //SHIFT + something
    if(e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('Shift + S');
                break;

        }
    }

    //CTRL + SHIFT + something
    if(e.ctrlKey && e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('CTRL + Shift + S');
                break;

        }
    }

});


1
case 65: //A
jp = 1;
setTimeout("jp = 0;", 100);

if(pj > 0) {
ABFunction();
pj = 0;
}
break;

case 66: //B
pj = 1;
setTimeout("pj = 0;", 100);

if(jp > 0) {
ABFunction();
jp = 0;
}
break;

Non è il modo migliore, lo so.


-1
Easiest, and most Effective Method

//check key press
    function loop(){
        //>>key<< can be any string representing a letter eg: "a", "b", "ctrl",
        if(map[*key*]==true){
         //do something
        }
        //multiple keys
        if(map["x"]==true&&map["ctrl"]==true){
         console.log("x, and ctrl are being held down together")
        }
    }

//>>>variable which will hold all key information<<
    var map={}

//Key Event Listeners
    window.addEventListener("keydown", btnd, true);
    window.addEventListener("keyup", btnu, true);

    //Handle button down
      function btnd(e) {
      map[e.key] = true;
      }

    //Handle Button up
      function btnu(e) {
      map[e.key] = false;
      }

//>>>If you want to see the state of every Key on the Keybaord<<<
    setInterval(() => {
                for (var x in map) {
                    log += "|" + x + "=" + map[x];
                }
                console.log(log);
                log = "";
            }, 300);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.