移动端tap解决方案

2018-11-15  本文已影响28人  无米学炊

移动端click事件有300ms延迟。
在Android上面可以通过以下两种方式解决:

  1. 添加meta标签:
<meta name="viewport" content="width=device-width">
  1. 使用css属性 touch-action
html {
    touch-action: manipulation;
}

方案来源300ms tap delay, gone away
测试链接:

  1. 有延迟页面
  2. 无延迟-meta
  3. 无延迟-css方式

在最新版本的ios12上面使用 meta标签方式也不会出现延时, 据这里讨论Fastclick is no longer required in iOS 10也没有延迟了,但是在ios的UIWebView中还会有问题。
针对这种场景,可以试用fastclick来解决

fastclick思路是:

  1. 监听dom元素的 touchstarttouchmovetouchend 事件;
  2. 在touchstart中记录移动的元素,开始位置;
  3. 在touchmove中判断移动端元素是否有变化,移动位置,如果超过阈值就认为是滑动,不过进一步处理。
  4. 在touchend中判断是否需要点击一个元素,还是发生了滑动事件,如果是点击就构造一个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,也可以开发一个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);
  }
}
上一篇下一篇

猜你喜欢

热点阅读