English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
近期使用tap事件为老夫带来了这样那样的问题,其中一个问题是解决了点透还需要将原来一个个click变为tap,这样的话我们就放弃了ie用户
当然可以做兼容,但是没有人想动老代码的,于是今天拿出了fastclick这个东西,
这是最近第四次发文讨论tap的点透事件,我们一直对解决“点透”的蒙版念念不忘,于是今天老大提出了一个库fastclick,最后证明解决了我们的问题
而且click不必替换为tap了,于是我们老大就语重心长地对我说了一句,你们就耽误我吧,我邮件都发出去了......
于是我下午就在研究fastclick这个库,看看是否能够解决我们的问题,于是我们开始吧
阅读fastclick源码
尼玛使用太简单了,直接一句:
FastClick.attach(document.body);
于是所有的click响应速度直接提升,刚刚的!什么input获取焦点的问题也解决了!!!尼玛如果真的可以的话,原来改页面的同事肯定会咬我
一步一个脚印,我们深入进去,入口就是attach方法:
FastClick.attach = function(layer) { 'use strict'; return new FastClick(layer); };
Questo fratello ha solo istanziato il codice, quindi dobbiamo guardare il nostro costruttore:
function FastClick(layer) {
'use strict';
var oldOnClick, self = this;
this.trackingClick = false;
this.trackingClickStart = 0;
this.targetElement = null;
this.touchStartX = 0;
this.touchStartY = 0;
this.lastTouchIdentifier = 0;
this.touchBoundary = 10;
this.layer = layer;
if (!layer || !layer.nodeType) {
throw new TypeError('Layer must be a document node');
}
this.onClick = function() { return FastClick.prototype.onClick.apply(self, arguments); };
this.onMouse = function() { return FastClick.prototype.onMouse.apply(self, arguments); };
this.onTouchStart = function() { return FastClick.prototype.onTouchStart.apply(self, arguments); };
this.onTouchMove = function() { return FastClick.prototype.onTouchMove.apply(self, arguments); };
this.onTouchEnd = function() { return FastClick.prototype.onTouchEnd.apply(self, arguments); };
this.onTouchCancel = function() { return FastClick.prototype.onTouchCancel.apply(self, arguments); };
if (FastClick.notNeeded(layer)) {
return;
}
if (this.deviceIsAndroid) {
layer.addEventListener('mouseover', this.onMouse, true);
layer.addEventListener('mousedown', this.onMouse, true);
layer.addEventListener('mouseup', this.onMouse, true);
}
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, false);
layer.addEventListener('touchmove', this.onTouchMove, false);
layer.addEventListener('touchend', this.onTouchEnd, false);
layer.addEventListener('touchcancel', this.onTouchCancel, false);
if (!Event.prototype.stopImmediatePropagation) {
layer.removeEventListener = function(type, callback, capture) {
var rmv = Node.prototype.removeEventListener;
if (type === 'click') {
rmv.call(layer, type, callback.hijacked || callback, capture);
} else {
rmv.call(layer, type, callback, capture);
}
};
layer.addEventListener = function(type, callback, capture) {
var adv = Node.prototype.addEventListener;
if (type === 'click') {
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
if (!event.propagationStopped) {}}
callback(event);
}
}), capture);
} else {
adv.call(layer, type, callback, capture);
}
};
}
if (typeof layer.onclick === 'function') {
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
}
Guarda questo pezzo di codice, molte proprietà non so cosa facciano... quindi l'ho ignorato
if (!layer || !layer.nodeType) { throw new TypeError('Layer must be a document node'); }
Attenzione qui, dobbiamo passare un nodo al costruttore, altrimenti ci saranno problemi
Poi questo tipo ha registrato alcuni eventi di mouse di base nei suoi metodi di proprietà, cosa esattamente fa lo vedremo più tardi
C'è un metodo notNeeded dopo il punto:
FastClick.notNeeded = function(layer) {
'use strict';
var metaViewport;
if (typeof window.ontouchstart === 'undefined') {
return true;
}
if ((/Chrome\/[0-9]+/).test(navigator.userAgent)) {
if (FastClick.prototype.deviceIsAndroid) {
metaViewport = document.querySelector('meta[name=viewport]');
if (metaViewport && metaViewport.content.indexOf('user-scalable=no') !== -1) {
return true;
}
} else {
return true;
}
}
if (layer.style.msTouchAction === 'none') {
return true;
}
return false;
};
Questo metodo viene utilizzato per determinare se è necessario utilizzare fastclick, il senso del commento non è chiaro, guardiamo il codice
La prima frase:
if (typeof window.ontouchstart === 'undefined') { return true; }
Se non supporta l'evento touchstart, restituisce true
PS: Attualmente, la sensazione è che fastclick dovrebbe anche simulare gli eventi touch, ma non ha problemi di trasparenza
Dopo di che ci sono anche alcune valutazioni sui problemi di Android, non mi interessa qui, il senso dovrebbe essere che è supportato se è possibile supportare i touch, quindi torniamo al codice principale
Nella parte principale del codice, vediamo che se il browser non supporta gli eventi touch o altri problemi, esce direttamente
Poi c'è una proprietà deviceIsAndroid, andiamo a guardare (in realtà non c'è bisogno di guardare, è chiaro che è per determinare se il dispositivo è Android)
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
Associare eventi
Bene, questo tipo ha iniziato a associare eventi di registrazione, e fino ad ora non si sono rilevate differenze
if (this.deviceIsAndroid) {
layer.addEventListener('mouseover', this.onMouse, true);
layer.addEventListener('mousedown', this.onMouse, true);
layer.addEventListener('mouseup', this.onMouse, true);
}
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, false);
layer.addEventListener('touchmove', this.onTouchMove, false);
layer.addEventListener('touchend', this.onTouchEnd, false);
layer.addEventListener('touchcancel', this.onTouchCancel, false);
La funzione dell'evento è stata sovrascritta in precedenza, per ora ci limitiamo a ignorarla e guardiamo oltre (diciamo, questo tipo ha molti eventi associati)
stopImmediatePropagation
C'è un attributo in più:
Blocca la propagazione dell'evento corrente e blocca la prosecuzione dell'esecuzione di tutti i listener dello stesso tipo dell'elemento corrente.
Se un elemento ha più listener per lo stesso tipo di evento, quando viene scatenato quell'evento, i vari listener vengono eseguiti in sequenza. Se un listener esegue il metodo event.stopImmediatePropagation(), oltre a bloccare la propagazione dell'evento (effetto del metodo event.stopPropagation()), blocca anche l'esecuzione degli altri listener dello stesso tipo associati all'elemento.
<html>
<head>
<style>
p { height: 30px; width: 150px; background-color: #ccf; }
div {height: 30px; width: 150px; background-color: #cfc; }
</style>
</head>
<body>
<div>
<p>paragraph</p>
</div>
<script>
document.querySelector("p").addEventListener("click", function(event)}
{
alert("Sono il primo listener associato all'elemento p");
}, false);
document.querySelector("p").addEventListener("click", function(event)}
{
alert("Sono il secondo listener associato all'elemento p");
event.stopImmediatePropagation();
//Esegui il metodo stopImmediatePropagation per bloccare la propagazione dell'evento click e per bloccare l'esecuzione di altri listener di eventi click associati all'elemento p.
}, false);
document.querySelector("p").addEventListener("click", function(event)}
{
alert("Sono la terza funzione di ascolto vincolata all'elemento p");
//Questa funzione di ascolto è posta dietro la precedente funzione, questa funzione non verrà eseguita.
}, false);
document.querySelector("div").addEventListener("click", function(event)
{
alert("Sono l'elemento div, sono il livello superiore dell'elemento p");
//L'evento click dell'elemento p non si propaga verso l'alto, questa funzione non verrà eseguita.
}, false);
</script>
</body>
</html>
if (!Event.prototype.stopImmediatePropagation) {
layer.removeEventListener = function(type, callback, capture) {
var rmv = Node.prototype.removeEventListener;
if (type === 'click') {
rmv.call(layer, type, callback.hijacked || callback, capture);
} else {
rmv.call(layer, type, callback, capture);
}
};
layer.addEventListener = function(type, callback, capture) {
var adv = Node.prototype.addEventListener;
if (type === 'click') {
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
if (!event.propagationStopped) {}}
callback(event);
}
}), capture);
} else {
adv.call(layer, type, callback, capture);
}
};
}
Poi questo tipo ha ridefinito i metodi di registrazione e注销 degli eventi
Prima di tutto, guardiamo l'evento di registrazione, tra cui è stato utilizzato addEventListener di Node, cos'è questo Node?
Da questo punto di vista, Node è una proprietà di sistema, che rappresenta il nostro nodo, quindi qui abbiamo sovrascritto l'evento di注销
qui, scopriamo che in realtà tratta solo lo special handling del click
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) { if (!event.propagationStopped) {}} callback(event); } }), capture);
Tra cui c'è un hijacked che fa cosa non è chiaro, sembra che sia il significato di se viene modificato nel mezzo
Poi qui abbiamo riscritto, hijacked sembra essere un metodo, esiste per prevenire che un evento venga registrato più volte su un dom e eseguito più volte
注销和注册差不多我们就不管了,到此我们其实重写了我们传入dom的注册注销事件了,好像很厉害的样子,意思以后这个dom调用click事件用的是我们的,当然这只是我暂时的判断,具体还要往下读,而且我觉得现在的判断不靠谱,于是我们继续吧
Quando disattiviamo l'evento possiamo usare addEventListener o dom.onclick=function(){} quindi abbiamo il seguente codice:
if (typeof layer.onclick === 'function') { oldOnClick = layer.onclick; layer.addEventListener('click', function(event) { oldOnClick(event); }, false); layer.onclick = null; }
Qui, la sua struttura principale sembra essere finita, il che significa che tutti i suoi logici sono qui, sia l'ingresso che l'uscita dovrebbe essere la registrazione degli eventi, quindi scriviamo un codice per vedere
Punto di ingresso di test
<input type="button" value="addevent">
<input type="button" value="addevent1">
$('#addEvent').click(function () {
var dom = $('#addEvent1')[0]
dom.addEventListener('click', function () {
alert('');
var s = '';
})
});
Vediamo questo breakpoint per vedere cosa facciamo dopo aver cliccato, ora cliccando sul pulsante 1 registriamo un evento per il pulsante 2:
Ma è un peccato, non possiamo testare sul computer, quindi è aumentata la difficoltà di leggere il codice, dopo aver testato sul telefono, ho scoperto che il pulsante 2 risponde molto rapidamente, ma qui non si vede chiaramente il problema
Alla fine ho alertato un'Event.prototype.stopImmediatePropagation che ha rilevato che sia il telefono che il computer sono false, quindi ciò che abbiamo fatto sopra è temporaneamente inutile
FastClick.prototype.onClick = function (event) {
'use strict';
var permitted;
alert('Finalmente siamo entrati');
if (this.trackingClick) {
this.targetElement = null;
this.trackingClick = false;
return true;
}
if (event.target.type === 'submit' && event.detail === 0) {
return true;
}
permitted = this.onMouse(event);
if (!permitted) {
this.targetElement = null;
}
return permitted;
};
Poi finalmente siamo entrati, ora dobbiamo sapere cosa significa trackingClick
/** * Se un click è attualmente in tracciamento. * @type Boolean */ this.trackingClick = false;
All'inizio questa proprietà era impostata su false, ma adesso è impostata su true e si è usciti direttamente, il che significa che la bind dell'evento è stata interrotta, quindi non ci preoccupiamo di questo per ora, facciamo qualcos'altro,
Perché, penso che l'attenzione dovrebbe essere principalmente sull'evento touch
PS: Arrivati a questo punto, abbiamo scoperto che questa libreria non dovrebbe solo accelerare il click, ma anche accelerare tutte le risposte
Ho messo delle cose nel log in ogni parte dell'evento, e ho scoperto che ovunque c'è un click viene eseguito solo touchstart e touchend, quindi penso che la mia opinione sia giusta fino a questo punto
Lui usa l'evento touch per simulare un click, quindi possiamo seguire solo questo:
FastClick.prototype.onTouchStart = function (event) {
'use strict';
var targetElement, touch, selection;
log('touchstart');
if (event.targetTouches.length > 1) {
return true;
}
targetElement = this.getTargetElementFromEventTarget(event.target);
touch = event.targetTouches[0];
if (this.deviceIsIOS) {
selection = window.getSelection();
if (selection.rangeCount && !selection.isCollapsed) {
return true;
}
if (!this.deviceIsIOS4) {}}
if (touch.identifier === this.lastTouchIdentifier) {
event.preventDefault();
return false;
}
this.lastTouchIdentifier = touch.identifier;
this.updateScrollParent(targetElement);
}
}
this.trackingClick = true;
this.trackingClickStart = event.timeStamp;
this.targetElement = targetElement;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
if ((event.timeStamp - this.lastClickTime) < 200) {
event.preventDefault();
}
return true;
};
Tra cui un metodo:
FastClick.prototype.getTargetElementFromEventTarget = function (eventTarget) { 'use strict'; if (eventTarget.nodeType === Node.TEXT_NODE) { return eventTarget.parentNode; } return eventTarget; };
È per ottenere l'elemento corrente di touchstart
Poi vengono registrate le informazioni del mouse, principalmente in touchend in base a x, y per determinare se è un click
In caso di iOS si sono fatti anche alcune cose, qui salto
Poi qui vengono registrati alcuni eventi e si esce, non ci sono cose particolari, ora entriamo nel nostro uscita touchend
FastClick.prototype.onTouchEnd = function (event) {
'use strict';
var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
log('touchend');
if (!this.trackingClick) {
return true;
}
if ((event.timeStamp - this.lastClickTime) < 200) {
this.cancelNextClick = true;
return true;
}
this.lastClickTime = event.timeStamp;
trackingClickStart = this.trackingClickStart;
this.trackingClick = false;
this.trackingClickStart = 0;
if (this.deviceIsIOSWithBadTarget) {
touch = event.changedTouches[0];
targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
}
targetTagName = targetElement.tagName.toLowerCase();
if (targetTagName === 'label') {
forElement = this.findControl(targetElement);
if (forElement) {
this.focus(targetElement);
if (this.deviceIsAndroid) {
return false;
}
targetElement = forElement;
}
}
if ((event.timeStamp - trackingClickStart) > 100 || (this.deviceIsIOS && window.top !== window && targetTagName === 'input')) {
this.targetElement = null;
return false;
}
this.focus(targetElement);
if (!this.deviceIsIOS4 || targetTagName !== 'select') {
this.targetElement = null;
event.preventDefault();
}
return false;
}
if (this.deviceIsIOS && !this.deviceIsIOS4) {
scrollParent = targetElement.fastClickScrollParent;
if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
return true;
}
}
if (!this.needsClick(targetElement)) {}}
event.preventDefault();
this.sendClick(targetElement, event);
}
return false;
};
Questo tipo ha fatto molte cose
Correggo un errore qui, ora anche quei东西onclick viene eseguito... Potrebbe essere cambiato il mio schermo (sfioramento)
if ((event.timeStamp - this.lastClickTime) < 200) { this.cancelNextClick = true; return true; }
Questo codice è molto importante, la nostra prima click eseguirà la logica qui sotto, se si clicca di seguito direttamente fallisce, la logica qui sotto non viene eseguita...
Questo non viene eseguito, allora cosa ha fatto questa cosa lì?
In realtà, non c'è logica qui sotto, il che significa che se il click è troppo veloce, i due click eseguiranno solo uno, questo valore di soglia è 200ms, sembra non ci siano problemi al momento
Bene, continuiamo a scendere, mi sono reso conto di aver raggiunto un altro punto chiave
Poiché non possiamo ottenere l'attenzione dell'input con l'evento tap, ma fastclick può ottenere l'attenzione, forse è una chiave, diamo un'occhiata a alcune funzioni correlate all'ottenimento dell'attenzione
FastClick.prototype.focus = function (targetElement) {
'use strict';
var length;
if (this.deviceIsIOS && targetElement.setSelectionRange) {
length = targetElement.value.length;
targetElement.setSelectionRange(length, length);
} else {
targetElement.focus();
}
};
setSelectionRange è la nostra chiave, forse è così che ottiene l'attenzione... Devo ancora testare, rimandiamo al prossimo volta
Poi, se l'intervallo di tempo è troppo lungo, il codice non considera l'operazione come una struttura DOM identica
Infine, è arrivato il punto chiave di questa sessione: sendClick, sia touchend che onMouse si riuniscono qui
FastClick.prototype.sendClick = function (targetElement, event) {
'use strict';
var clickEvent, touch;
// Su alcuni dispositivi Android, activeElement deve essere sfocato altrimenti il click sintetico non avrà alcun effetto (#24)
if (document.activeElement && document.activeElement !== targetElement) {
document.activeElement.blur();
}
touch = event.changedTouches[0];
// Synthesizza un evento click con un attributo aggiuntivo in modo che possa essere tracciato
clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
};
Ha creato un evento mouse e poi ha inviato l'evento dispatchEvent (simile a fireEvent)
// Il evento personalizzato ondataavailable è attaccato al documento
document.addEventListener('ondataavailable', function (event) {
alert(event.eventType);
}, false);
var obj = document.getElementById("obj");
// Il click evento è attaccato all'elemento obj
obj.addEventListener('click', function (event) {
alert(event.eventType);
}, false);
// Ottiene un'istanza dell'oggetto event utilizzando il metodo createEvent del documento.
var event = document.createEvent('HTMLEvents');
// initEvent accetta 3 parametri:
// Tipo di evento, se bolle, se blocca il comportamento predefinito del browser
event.initEvent("ondataavailable", true, true);
event.eventType = 'message';
// Attivare l'evento personalizzato ondataavailable collegato al document
document.dispatchEvent(event);
var event1 = document.createEvent('HTMLEvents');
event1.initEvent("click", true, true);
event1.eventType = 'message';
// Attivare l'evento click collegato all'elemento obj
document.getElementById("test").onclick = function () {
obj.dispatchEvent(event1);
};
Fino ad ora, sappiamo che abbiamo collegato prima l'evento mouse al dom, poi è stato attivato durante touchend, e per quale motivo il click registrato non è stato attivato, dobbiamo tornare al codice sopra
Risolvere il "trasparenza del punto" (risultato)
Con questa idea, proviamo a eseguire il codice astratto che abbiamo creato:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="larghezza=device-larghezza, iniziale-scale=1, massimo-scale=1">
<style>
#list { display: blocco; posizione: assoluto; alto: 100px; sinistra: 10px; larghezza: 200px; altezza: 100px; }
div { display: block; border: 1px solid black; height: 300px; width: 100%; }
#input { width: 80px; height: 200px; display: block; }
</style>
</head>
<body>
<div>
</div>
<div>
<div>
<input type="text" />
</div>
</div>
<script type="text/javascript">
var el = null;
function getEvent(el, e, type) {
e = e.changedTouches[0];
var event = document.createEvent('MouseEvents');
event.initMouseEvent(type, true, true, window, 1, e.screenX, e.screenY, e.clientX, e.clientY, false, false, false, false, 0, null);
event.forwardedTouchEvent = true;
return event;
}
list.addEventListener('touchstart', function (e) {
var firstTouch = e.touches[0]
el = firstTouch.target;
t1 = e.timeStamp;
})
list.addEventListener('touchend', function (e) {
e.preventDefault();
var event = getEvent(el, e, 'click');
el.dispatchEvent(event);
})
var list = document.getElementById('list');
list.addEventListener('click', function (e) {
list.style.display = 'none';
setTimeout(function () {
list.style.display = '';
, 1000);
})
</script>
</body>
</html>
In questo modo, non c'è trasparenza del clic, poiché tutti gli eventi touch di Zepto sono assegnati al document, quindi e.preventDefault(); è inutile
Il risultato è che qui stiamo direttamente sul dominio, e.preventDefault();
Ha funzionato senza attivare l'evento predefinito del browser, quindi non c'è problema di trasparenza del clic, quindi la questione della trasparenza del clic è stata risolta...
Immagine per aiutare a comprendere
Il codice è stato scritto in ufficio, non so dove sia andato a casa, spero che possiate guardare bene
Perché Zepto ha il problema di trasparenza del clic / come risolvere il problema di fastclick
Ho già detto al capo che Zepto non gestisce bene l'evento tap, ha creato molti problemi
Poiché l'evento è assegnato al document, touchstart poi touchend, secondo il parametro event di touchstart determiniamo se il dominio ha registrato l'evento tap, e se lo ha, lo attiviamo
Quindi è venuto fuori un problema, nel touchend di Zepto c'è un parametro event, chiamiamo event.preventDefault(), qui siamo già al livello più alto, questo codice non serve a nulla
Ma la soluzione di fastclick non può essere definita non ingegnosa, questa libreria attiva direttamente l'evento click sul dominio al momento del touchend e sostituisce il tempo di attivazione originale
Questo significa che il codice che doveva essere eseguito in 350-400ms è stato spostato immediatamente a 50-100ms, quindi anche se qui si utilizza l'evento touch, l'evento touch è assegnato a un dominio specifico invece che al document
Quindi e.preventDefault() è efficace, possiamo prevenire la propagazione e anche prevenire l'evento predefinito del browser, questa è la parte essenziale di fastclick, non può essere definito non alta!!
Il codice di fastclick è stato letto con grande soddisfazione, oggi ho guadagnato molto, e lo registro qui
Postilla
L'affermazione precedente ha un problema, correggiamola:
Prima di tutto, torniamo al vecchio schema di Zepto per vedere quali sono i suoi problemi:
Poiché lo standard JavaScript non supporta l'evento tap, Zepto tap è una simulazione di touchstart e touchend. Zepto assegna già gli eventi touch al document durante l'inizializzazione, e quando clicchiamo, secondo il parametro event, otteniamo l'elemento corrente e salviamo la posizione del mouse quando clicchiamo e quando lasciamo andare. Secondo la gamma di movimento del mouse dell'elemento corrente, determiniamo se è un evento di clic, e se lo è, attiviamo l'evento tap registrato.
Poi Fastclick gestisce in modo simile a zepto, ma anche diverso
Fastclick binda l'evento a un elemento che hai trasmesso (di solito document.body)
② Dopo touchstart e touchend (manualmente recuperiamo l'elemento cliccato corrente), se è un evento di tipo click, scateniamo manualmente l'evento click dell'elemento DOM
Quindi l'evento click viene scatenato immediatamente dopo touchend, la velocità di risposta aumenta, lo scatenamento è uguale al tap di zepto
Bene, perché codice praticamente identico, zepto lo farà trasparente mentre fastclick no?
La ragione è che c'è un setTimeout nel codice di zepto, e anche se eseguiamo e.preventDefault() in questo codice non sarà utile
Questo è la differenza fondamentale, perché setTimeout avrà una priorità più bassa
Con il timer, quando il codice esegue setTimeout, mette questo codice all'ultimo della macchina virtuale JS
E il nostro codice rileverà immediatamente e.preventDefault, una volta aggiunto setTimeout, e.preventDefault non funzionerà, questa è la causa fondamentale del trasparenza di zepto
Conclusione
Anche se questa volta abbiamo percorso molte strade tortuose, alla fine abbiamo risolto il problema
Dichiarazione: il contenuto di questo articolo è stato tratto da Internet, il diritto d'autore spetta ai rispettivi proprietari, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, questo sito non detiene i diritti di proprietà, non è stato editato manualmente e non assume responsabilità legali correlate. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare il problema e fornire prove pertinenti. Una volta verificata, questo sito rimuoverà immediatamente il contenuto sospetto di violazione del copyright.