5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

0. INDEX

  1. 概要
  2. 前準備
  3. 実装
  4. あとがき

1. 概要

スマホ向けWebアプリ開発で、ロングタップ(押しっぱなし)的なトリガーがあると思い込んでいたが為にやらかしました。

longtap.jpg
1 2

という事で、スマホアプリのファイラー等でよく見かける、ロングタップをするとファイルを選択する という感じの事をWebアプリでもやりたくて、 ロングタップを判断するユーティリティ を作りゴリ押しました。

2. 前準備

ロングタップとは何か? という事をユーザーとシステムの目線で見て実装内容を考えてみます。

2.1. ユーザー目線のロングタップ操作

多分、こんな感じ。

  • スマホ画面を指で触り始める
    ※指を動かさない(ドラッグしない)ってのもあるかもしれないですが、今回は割愛します
  • 触ったまま一定時間経ったら、長押し用のメニュー等が出てくる

2.2. システム目線のロングタップ操作

上記を踏まえて、こういう感じで実装できそうです。

  • touchstart トリガーを捕まえて処理を開始する
  • 一定時間押しっぱなしだったと判断する為に、setTimeout()を仕掛ける … (1)
  • 一定時間押しっぱなしじゃなかったと判断する為に、touchendトリガーを仕掛ける … (2)

もうちょっと分解

  • (1)が先に発動したら、ロングタップをした
  • (2)が先に発動したら、ロングタップではなかった

2.3. つまりどーゆー事だっぺよ?(図解)

そういえば私は弊社の大バカ野郎担当なので、言葉だけではイマイチ理解できません。なのでお絵描き3をします。

image.png

image.png

3. 実装

前準備を元に実装しました。

3.1. 動作サンプル

https://jsfiddle.net/mahny/mg48pub3/11/
※JSFiddleの方はPCでも確認できるように「onMouseDown」の処理も入っています。

3.2. コード抜粋

ユーティリティ全文
function isHoldDown(targetElement, thresholdMsec = 1000) {
  return new Promise((resolve) => {

    const timerId = setTimeout(() => {
      resolve(true);
      removeListener();
    }, thresholdMsec);

    const touchendHandler = () => {
      resolve(false);
      removeListener();
    };

    const contextHandler = (event) => {
      event.preventDefault();
    }

    const beforeTargetStyle = targetElement.style.userSelect;

    const removeListener = () => {
      clearTimeout(timerId);
      targetElement.removeEventListener('touchend', touchendHandler);
      targetElement.removeEventListener('contextmenu', contextHandler);
	  targetElement.style.userSelect = beforeTargetStyle;
    };

    targetElement.addEventListener('touchend', touchendHandler);
    targetElement.removeEventListener('contextmenu', contextHandler);
    targetElement.style.userSelect = 'none';
  });
}
使い方(html)
<h2>押しっぱなし判定習作</h2>
<div>
  <button onTouchStart="test(event)">押しっぱなし!</button>
  <div id="result-console">(判定結果)</div>
</div>
使い方(JavaScript)
async function test(event) {
  if (await isHoldDown(event.target)) {
    console.log('ロングタップ');
  } else {
    console.log('普通のタップ');
  }
}

3.3. コード解説

このコードは、onTouchStartをトリガーにするなど、ユーザーの指が既に画面に触れている事を前提としています。
以降、isHoldDown()について解説します。

インターフェース周り

function isHoldDown(targetElement, thresholdMsec = 1000) {
  return new Promise((resolve) => {
    // ~(略)~
  }
}
  • ユーティリティとしては、単純にtrue/falseで判定したかったので、Promiseを使っています
    • その為、呼び出し側は async/await を使う作りになっている
  • サンプルだしエラー処理もいらなかったので、rejectを入れていませんが、必要なら追加する事

ハンドラ周り

    // ロングタップであると判定する為のハンドラ
    const timerId = setTimeout(() => {
      resolve(true);
      removeListener();
    }, thresholdMsec);

    // ロングタップではないと判定する為のハンドラ
    const touchendHandler = () => {
      resolve(false);
      removeListener();
    };

    // コンテキストメニューが出てくるのを抑制する為のハンドラ
    const contextHandler = (event) => {
      event.preventDefault();
    }
  • 重要なのは、setTimeouttouchendHandlerで以下の処理を担います
    • setTimeout
      • touchendが発生しなかったので、タップし続けた⇒ロングタップである ので、resolve(true) で返しています
      • 呼び出し元で await を使っていれば、戻り値として true が得られますね
    • touchendHandler
      • setTimeoutが発生する前に指を離してしまったので、ロングタップではないresolve(false)で返しています
      • 同様に、呼び出し元で await を使っていれば、戻り値として false が得られますね
    • contextHandler
      • 単に、コンテキストメニューを封じたかったので、contextmenuトリガーに紐づけるハンドラとして定義しています。
        スマホ専用サイトの場合は contextmenu 自体が無いので不要です

トリガー周り、他

    const beforeTargetStyle = targetElement.style.userSelect;

    const removeListener = () => {
      clearTimeout(timerId);
      targetElement.removeEventListener('touchend', touchendHandler);
      targetElement.removeEventListener('contextmenu', contextHandler);
      targetElement.style.userSelect = beforeTargetStyle;
    };

    targetElement.addEventListener('touchend', touchendHandler);
    targetElement.removeEventListener('contextmenu', contextHandler);
    targetElement.style.userSelect = 'none';
  • ロングタップではないと判断する為の touchendトリガーにハンドラを定義しています
  • beforeTargetStylecontextmenu は、ロングタップ時にコンテキストメニューが出てきてしまうのを抑制する為に定義しています

4. あとがき

という事で、思い込みで安請け合いをしたら苦労した話…ではなく、ロングタップを判断するユーティリティ作った話でした。

そろそろ、標準でロングタップトリガーを準備してくれてもいいと思うわけですが、テキスト選択等のコンテキストメニューとぶつかりやすそうで二の足を踏んでいるのでしょうか。

良い子の皆様におかれましては、安請け合い等をして泥沼にハマらない事を祈っております。

  1. 弊社にはこのような営業はおりません。全ては私の中にいる悪魔(サービス精神旺盛)の所業であり、ただの自爆です。

  2. マンガは 社畜ちゃん台詞メーカー で作りました。
    https://blog.oukasoft.com/OS/scenemake/

  3. お絵描き後、先に書いていたテキストは要らねーじゃんと思ったり思わなかったりしましたω

5
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?