【jQuery】hoverとtouchの共存について模索したこと

書きなぐりですが、メモ。

PCのホバーとiOSのタッチは問題なし

あるサイト制作でアコーディオンメニューを作成し、ホバーとタッチの両方に対応させたく、jQueryでメニュー開閉のスクリプトを書きました。PCの場合はマウスをメニュータイトルにかざすと開閉でき、iPhoneやiOSではタップで開閉できました。ところが...。

Windowsタブレットで問題発生

クリックしかできないデバイスと、タップしかできないデバイスならこれで良かったのですが...。タップもクリックもできるデバイスとなると挙動が変わりました。マウスホバーでの開閉はいいのですが、途中でタップに切り替えると、2度タップしないとメニューが開かないようになりました。閉じるのは1回でもOKでした。

問題のあるソース例

メニューのタイトル部分<li class="menu_list">タイトル</li>で、開閉して表示・非表示となる部分が.menu_contentsだった場合の例。

//PCの場合はマウスオーバーで開閉
$('li.menu_list').not('.active').hover(function () { 
  $(this).addClass('active');
  $(this).find('.menu_contents').stop().slideDown();
}, function () {
  $(this).removeClass('active');
  $(this).find('.menu_contents').hide();
});

//スマホやタブレットはタッチで開閉
var clickEventType = ((window.ontouchstart !== null) ? 'click' : 'touchend');
$('li.menu_list').on(clickEventType, function () {
  // 状態が active の場合は閉じる
  if ($(this).hasClass('.active')){
    $(this).removeClass('active');
    $(this).find('.menu_contents').hide();
    }
  // 状態が activeなしの場合は開く  
  else if (!$(this).hasClass('active') {
    $(this).addClass('active');  
    $(this).find('.menu_contents').stop().slideDown();
    }
});

hoverとtoutchの切替えがうまくいっていないのか?

仕組みがよくわからないのですが、タップで閉じた後に同じメニューを開くのは何度やってもタップ1回で繰り返し開閉できる。しかし、隣りのメニューをタップすると、2度タップしないと開かない...。タップした時点で要素を捕まえきれていない感じがします。タップからマウスホバーは問題ありませんでした。

想像ですが、2回タップしないといけないのは、指がタッチパネルから離れていてもホバー状態になっていて、1回目のタップでは以前タップした場所をからhoverを解放し、2回目のタップで現在のボタンをクリックしているのではないかと思います。しかし、初回表示時でも2回タップしないとメニューが開きませんでした。つまり、ページ表示時やタップ時にホバーを解除する命令を加えれればうまくいくような気がします。

↓このページを見てそう思いました。

Link

https://solutions.vaio.com/1759

模索した解決策の候補

クリックとタップのみでの開閉はPCでもタブレットでもうまくいくため、『hoverの開閉を諦める』これしかないかもしれません。どれも試していませんがメモ。

hoverをmouseenter、mouseleaveに変えてみる

これはダメでした。clicktoutchstartのようにon('mouseenter', function(){~のように書けるので、これならイケるかも?と思いましたが、挙動は同じでした。

Link

https://daiblog923.com/jquery-eventtrigger/

発火方法を細かく分けてみる

これもダメでした。PCのマウスホバーのみ動作やiOSでのタッチのみの動作はOKでしたが、Windowsタブレットでは2回タップしないとメニューが開きませんでした。

Link

jQuery Hover()をpcとタッチスクリーンで分ける | Ikapblog

https://blog.ikappio.com/jquery-hover-on-pc-and-touch-screen/

CSSからhoverのルールを削除して無効化する例

タップされたら、一時的にhoverを無効化。ontouchstartでhoverを無効化するという方法がありましたが、hoverを復活したいときはどうするんだろう?

Link

スマホとタブレットで:hoverの挙動を無効化する方法 | Techmemo

https://techmemo.biz/javascript/touchdevice-hover-disable/

Link

タッチデバイスでHoverを無効化(:hoverセレクタ削除) – Cmz Note

http://cmz.wp.xdomain.jp/archives/1029

単純にこれは、hoverの機能を無効化するというより、CSSにある:hoverのルールを削除するだけみたいです。

疑似的に:hoverを追加削除する方法

Link

iPhone/AndroidなどwebkitのA:hoverをタッチすると元の状態に戻らない、Hoverを無効にする方法 | Urashita.com 浦下.com (ウラシタドットコム)

https://urashita.com/archives/17532

Link

Twentyfourteenでスマホ表示のHoverが戻らない問題を解消する | Work.log

https://worklog.be/archives/2860

Link

Dotnetnuke - タッチのみのデバイスではないように見えるデバイスで、jQueryを使用してタッチイベントを適切に検出する - 初心者向けチュートリアル

https://tutorialmore.com/questions-2478488.htm

指定した要素にJavaScript で ontouchstart 属性を追加する方法

Link

スマホで Hover 対応 Ontouchstart タッチデバイス 判定

https://www.webdesignleaves.com/pr/plugins/ontouchstart-event-handler.html

onclick 属性を付与する方法

ontouchstartと同じかな。

Link

iPhoneでHoverやclickイベントが思い通りにならない場合の処方箋【css】【javascript】 | Analog Studio

https://analogstd.com/pc/iphone-hover-click-faq/

mouseenterの替わりにtoutchenterを使用してみる

Link

イベントハンドラー | Javascript逆引き | Webサイト制作支援 | Shanabrian Website

https://shanabrian.com/web/javascript/event-handler.php

初回イベント時のみ取得できない状況を解消する?

これに近い挙動のように感じます。しかし、これはAndoroidアプリ制作上での話みたいで、コマンドがJavascriptではない感じ。以下のページに関連するページのリンクがあるので、進んで行くと『初回の1タップ処理が、何か別の処理に搾取されていると推測できる。』と記載があった。まさにこの挙動だと感じていますが、これをどうやって調べるかが課題。

Link

Androidアプリで、初回のタッチイベントのみ取得できない #touchevent - Qiita

https://qiita.com/erik_t/items/7f1c05f2deb01421b2cd

Link

趣味の記録保存館 プログラム: Androidでタッチイベントが取得できないだと!/ その3

http://latache-program.blogspot.com/2017/01/android_8.html

イベントハンドラの指定方法を変えてみる

$('li.menu_list').on('toutchstart', function () {

ではなく、

$('document').on('toutchstart', 'li.menu_list', function () {

とする方法。

後日、試してみたところ、これが当たりでした!

Link

jQueryでイベントが発火しないときの簡単な対処法 | Pisukecode - Web開発まとめ

https://pisuke-code.com/jquery-solution-for-event-unfired/

click / touch / pointer イベントを完全に制御する

『タッチディスプレイを搭載したPC』について言及があったので、これが案外有力そうです。Windowsタブレットで試したところ、タップやクリックの度に取得したイベントが異なっていたので良さそうでした。しかし、私のスキルが追い付いていないため、うまくfunctionを組み立てることができず、使いこなせませんでした。使いこなせる方ならこれが一番いいような気がします。

Link

[jquery] Click / Touch / Pointer イベントを完全に制御する #javascript - Qiita

https://qiita.com/toshiharu-irie/items/83e17eace4c2c9446667

clickとtouchを交互にsettimeoutで入れ替える

なんか邪道だけどメモ。

Link

https://www.web-dev-qa-db-ja.com/ja/jquery/touchstart%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%A8click%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%92%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89%E3%81%97%E3%80%81%E5%8F%8C%E6%96%B9%E5%90%91%E3%81%AB%E5%BF%9C%E7%AD%94%E3%81%97%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF%E3%81%A9%E3%81%86%E3%81%99%E3%82%8C%E3%81%B0%E3%82%88%E3%81%84%E3%81%A7%E3%81%99%E3%81%8B%EF%BC%9F/940675911/

これ以外で役に立ちそうなついでの対策

スクロールとタッチイベントの衝突回避

Link

レスポンシブ対応時にクリックイベントが、うまく動作しない時の対処法

https://alaki.co.jp/blog/?p=2546

何のイベントがどの要素で発生しているかを調べるのに良さそうなコマンド

Link

Javascript タッチ イベント (Webkit) - Galife

https://garafu.blogspot.com/2013/09/javascript-webkit.html

解決後のソース例

結局、何とか解決できました。実際にテストしたソースとは異なりますが、解決に至ったソースを簡単にまとめました。もっと効率よく書きたいところですが、とりあえずメモ。

これでPCのマウスホバーによる開閉、Windowsタブレットでのマウスホバーとタップによる開閉、スマホのタッチによる開閉を同時に実装できました。不思議なのはクリックイベントの指定をon('click mouseend touchend'としたことです。この3つのイベントを同時に入れることで何故か理想通りの動きになりました。ここにpointerstartなどのpointer〇〇を入れると不具合が起こりました。

//マウスカーソルをかざすと開
$(document).on('mouseenter', 'li.menu_list', function () {
  // class に active がない場合は開く
  if (!$(this).hasClass('active')) {
    $('.menu_contents').hide(); //他のメニューを閉じる
    $(this).addClass('active');
    $(this).find('.menu_contents').stop().slideDown();
  }
});

//マウスカーソルが外れると閉じる    
$(document).on('mouseleave', 'li.menu_list', function () {
  // class に active がある場合は開く
  if ($(this).hasClass('open')) {
    $('.menu_contents').hide();
  }
});

//スマホやタブレットはタッチで開閉
$(document).on('click mouseend touchend', 'li.menu_list', function () {
  // class に active がある場合は閉じる
  if ($(this).hasClass('.active')){
    $(this).removeClass('active');
    $(this).find('.menu_contents').hide();
    }
  // class に active がない場合は開く  
  else if (!$(this).hasClass('active') {
    $('.menu_contents').hide(); //他のメニューを閉じる
    $(this).addClass('active');  
    $(this).find('.menu_contents').stop().slideDown();
    }
});
PAGETOP