移動端click事件有300ms延遲。
在Android上面可以通過以下兩種方式解決:
- 添加
meta標(biāo)簽:
<meta name="viewport" content="width=device-width">
- 使用css屬性
touch-action:
html {
touch-action: manipulation;
}
方案來源300ms tap delay, gone away
測試鏈接:
在最新版本的ios12上面使用 meta標(biāo)簽方式也不會出現(xiàn)延時, 據(jù)這里討論Fastclick is no longer required in iOS 10也沒有延遲了,但是在ios的UIWebView中還會有問題。
針對這種場景,可以試用fastclick來解決
fastclick思路是:
- 監(jiān)聽dom元素的
touchstart、touchmove、touchend事件; - 在touchstart中記錄移動的元素,開始位置;
- 在touchmove中判斷移動端元素是否有變化,移動位置,如果超過閾值就認(rèn)為是滑動,不過進(jìn)一步處理。
- 在touchend中判斷是否需要點擊一個元素,還是發(fā)生了滑動事件,如果是點擊就構(gòu)造一個
MouseEvent: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);
基于這種思路,如果使用vue,也可以開發(fā)一個vue的指令:
const ua = navigator.userAgent.toLowerCase();
const isIos = /ios|iphone|ipod|ipad/.test(ua);
const instances = [];
export default function install (Vue) {
Vue.directive('tap', {bind, unbind});
}
// 安卓通過添加 <meta name="viewport" content="width=device-width"> 不會有300ms的延遲
function isAvailable (modifiers) {
return modifiers.all || modifiers.press || isIos;
}
function bind (node, directive) {
const modifiers = directive.modifiers;
const useTap = directive.value !== false;
if (isAvailable(modifiers) && useTap) {
instances.push(new Tap(node, modifiers));
}
}
function unbind (node, directive) {
const modifiers = directive.modifiers;
if (isAvailable(modifiers)) {
let i = 0;
let l = instances.length;
for (; i < l; i++) {
if (instances[i].node === node) {
instances[i].handler(false);
break;
}
}
if (i !== l) {
instances.splice(i, 1);
}
}
}
const CONSTANTS = {
tapMove: 8,
tapTime: 500
};
class Tap {
constructor (node, modifiers) {
this.node = node;
this.triggerPress = modifiers.press;
this.stopPropagation = modifiers.stop;
this.handler(true);
}
handler (flag) {
const action = flag ? 'addEventListener' : 'removeEventListener';
this.node[action]('touchstart', this.start.bind(this));
this.node[action]('touchmove', this.move.bind(this));
this.node[action]('touchend', this.end.bind(this));
this.node[action]('touchcancel', this.end.bind(this));
}
start (e) {
this.stop(e);
this.moved = false;
this.coords = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
}
if (this.triggerPress) {
this.pressTimer = setTimeout(() => {
if (!this.moved) {
this.pressTrigger = true;
this.trigger('press');
}
}, CONSTANTS.tapTime);
}
}
move (e) {
this.stop(e);
const movedDistance = Math.max(
Math.abs(e.touches[0].clientX - this.coords.x),
Math.abs(e.touches[0].clientY - this.coords.y)
);
// 如果移動距離大于8
this.moved = this.moved || movedDistance > CONSTANTS.tapMove;
}
end (e) {
this.stop(e);
e.cancelable && e.preventDefault && e.preventDefault();
if (this.pressTrigger) {
this.pressTrigger = false;
} else {
if (this.triggerPress) {
clearTimeout(this.pressTimer);
}
if (!this.moved) {
this.trigger('click');
}
}
}
stop (e) {
if (e && e.stopPropagation && this.stopPropagation) {
e.stopPropagation();
}
}
trigger (eventName) {
let event;
if ('CustomEvent' in window) {
event = new CustomEvent(eventName);
} else {
event = document.createEvent('CustomEvent');
event.initCustomEvent(eventName, true, true, void 0);
}
this.node.dispatchEvent(event);
}
}