iPhoneでhoverやclickイベントが思い通りにならない場合の処方箋【CSS】【Javascript】

Web サイトを設計する際に PC や Android 端末では想定通りの動作をするのに iPhone だけはうまくいかないことがあります。

そこで、今回はそうなってしまった場合にどのように対処するかをまとめます。

CSS の :hover 疑似クラスが解除されない場合

CSS の :hover 疑似クラスは、PC ではマウスを指定した要素に合わせている間、スマホやタブレットなどのタッチデバイスでは要素をタッチしてから他の要素をタッチするまでの間、に適応されるスタイルを指定できます。

例えば、id=”ele1″ 要素に :hover で背景色が変化するように指定してみましょう。


<CSS>
<style>
#ele1{
  background-color : white;
  width            : 300px;
  height           : 100px;
  line-height      : 100px;
  text-align       : center;
  border           : 1px solid black;
}
#ele1:hover{
  background-color : #FFAAAA;
}
</style>

<HTML>
<div id="ele1">
  :hover サンプル
</div>

:hover サンプル

このページではiPhoneでも問題なく動作していると思います。
理由は後述しますが、上記ソースを単独で使用するとiPhoneではhover状態が解除されずに背景色が赤色のままになってしまいます。

新規ページで動作確認する

:hover疑似クラスが解除されない原因

この原因は、iPhone では hover の終了条件である「(:hover が適応されている要素の) 他の要素でクリックイベントが発生した時」となっています。

クリックイベントは、Andoroid ではどの要素でもタップしたタイミングで発生します。
しかし、iPhone ではどの要素でもクリックイベントが発生するわけではありません。

iPhone は <a> タグが作る要素や onclick 属性を持つ要素などの “何かしらの動作を開始する要素” をタップした時のみクリックイベントが発生するようになっています。
従って、イベント開始の起点となっていない要素をタップしても hover が解除されないことになります。

対応方法

ではどのようにすれば良いでしょうか?

これは単純で全ての要素に onclick 属性を付与してやれば良いです。
属性値は空で良いので onclick=”” を追加することになります。

ここで注意点ですが、onclick 属性を <body> タグに追加しても「ページ全体がタップ可能な要素」として認識されないようですので、<body> 直下の各要素 (<header> や <main>、<footer> など) に設定しましょう。

以下のリンクで動作確認ができます。
画面全体を覆う要素を作成してそこに空の onclick 属性を追加しています。

新規ページで動作確認する

ある要素の背後に隠れる要素のクリックイベントが発生する場合

PC や Andoroid などであれば、ある要素の背後に隠れる (例えば z-index がより下位の) 要素に onclick イベントが設定されていても前面にイベントがない要素が重なっている状態では背後の要素のクリックイベントは発生しません。
しかし、iPhone では前面に要素があろうがなかろうが関係なくイベントが発生することがあります。

では、試しにクリックイベント (ここでは alert で警告表示) が設定されている要素に半透明の要素を重ねるようにしてみます。

<CSS>
<style>
#ele2{
  width            : 300px;
  height           : 100px;
  line-height      : 100px;
  text-align       : center;
  border           : 1px solid black;
  position         : relative;
  z-index          : 1;
}
#ele1::after{
  content          : ' ';
  background-color : rgba(0, 0, 0, 0.5);
  position         : absolute;
  top              : 0px;
  bottom           : 0px;
  left             : 0px;
  right            : 0px;
  z-index          : 2;
}
</style>

<HTML>
<div id="ele2">
  <a href="javascript: alert('クリックされました!')">クリックイベント開始!</a>
</div>

背後に隠れる要素のクリックイベントが発生する原因

この現象の原因は、明確には分かっていません。
しかし、様々な現象から以下のような推測がされており概ねこの考え方を前提に設計すれば回避可能です。

  1. iPhone の 基本ソフトである iOS は、タッチデバイスを前提に基本設計されている
  2. タッチデバイスでは、マウスカーソルがなく正確なクリック座標が不明確
  3. その為、タッチした座標付近でクリックなどのイベントが設定されている要素を探す
  4. イベントのある要素が近くにあればクリック (タッチ) されたとみなしてイベントを実行
  5. この時、要素の奥行方向の重なりは無視される (ように見えるが詳細は不明)
  6. また、時間軸的にもタッチされたと殆ど同時にクリック等のイベントのある要素を動的に追加した場合にはイベントが発生する

※ CSS で cursor : pointer; を指定した要素もイベント用の要素と認識されるようです。

このように iPhone でのタッチイベントは独自の解釈で処理されているようで PC を前提に設計していると思った動作にならない場合が発生します。
ユーザからすれば適当にタップしてもちゃんとイベントのある要素が選択されるので便利な処理方法だと云えますが、開発者は要素同士を離して配置したりするなどの考慮が必要です。

ちなみにですが、Apple 社はタッチデバイスにおけるボタンサイズは 44px 角を最小とするようにと推奨しているようです。
当然、正確なタップエリアを確保する為です。
ちなみに、私は 50px ~ 100px にすることが多いです。

Human Interface Guidelines | Apple Developer – Visual Design > Adaptivity and Layout
(下部の “Provide ample touch targets for interactive elements” に “44pxを最小とするように努める” と記載あります)

対応方法

クリック可能な要素の前面に別の要素を重ねる場合は、主に Javascript による動的な要素操作によることが殆どだと思います。

Javascript が有効になっている前提 (現在では無効にしている人は殆ど皆無) ですので、前面に要素が存在する時には背面に来るイベントをキャンセルしてしまう方法が良いでしょう。

まずは、イベントをキャンセルしない場合です。

以下サンプルでは要素下のボタンで半透明な要素を重ねるようにします。
また、その半透明な要素をクリック (タップ) すると消えるようにしています。

PC や Android なら半透明の要素に隠れるリンクが押されないと思いますが、iPhone では半透明な要素が消えると同時にリンクのイベントも発生します。

<CSS>
<style>
#ele3{
  width            : 300px;
  height           : 100px;
  line-height      : 100px;
  text-align       : center;
  border           : 1px solid black;
  position         : relative;
  z-index          : 1;
}
#ele3_b{
  background-color : rgba(0, 0, 0, 0.5);
  position         : absolute;
  top              : 0px;
  bottom           : 0px;
  left             : 0px;
  right            : 0px;
  z-index          : 2;
}
</style>

<HTML>
<div id="ele3">
  <a href="#" onclick="alert('クリックされました!');return false;">クリックイベント開始!</a>
  <div id="ele3_b" onclick="this.style.display='none';"></div>
</div>
<div style="margin:5px;"><input type="button" value="前面要素を追加" onclick="document.getElementById('ele3_b').style.display='block';"></div>

ここで、試しに半透明な要素の有無を確認してからイベントを実行するか条件分岐させます。

<CSS>
<style>
#ele4{
  width            : 300px;
  height           : 100px;
  line-height      : 100px;
  text-align       : center;
  border           : 1px solid black;
  position         : relative;
  z-index          : 1;
}
#ele4_b{
  background-color : rgba(0, 0, 0, 0.5);
  position         : absolute;
  top              : 0px;
  bottom           : 0px;
  left             : 0px;
  right            : 0px;
  z-index          : 2;
}

<HTML>
</style>
<div id="ele4">
  <a href="#" onclick="if(window.getComputedStyle(document.getElementById('ele4_b'), null).display == 'none'){alert('クリックされました!');}return false;">クリックイベント開始!</a>
  <div id="ele4_b" onclick="this.style.display='none';"></div>
</div>
<div style="margin:5px;"><input type="button" value="前面要素を追加" onclick="document.getElementById('ele4_b').style.display='block';"></div>
<CSS>

これでもクリックイベントが発生してしまうと思います。
理由は、半透明な要素を消した瞬間の一瞬後にクリックイベントが発生しているからだと予想されます。
そこで、わずかながら動作に遅延を持たせることで回避することができます。

一番簡単な方法は setTimeout 関数で 100ms 程度の遅れを持たせて条件分岐します。
この時間をどこまで短くできるかは分かりませんので実機で確認してみて下さい。

<CSS>
<style>
#ele5{
  width            : 300px;
  height           : 100px;
  line-height      : 100px;
  text-align       : center;
  border           : 1px solid black;
  position         : relative;
  z-index          : 1;
}
#ele5_b{
  background-color : rgba(0, 0, 0, 0.5);
  position         : absolute;
  top              : 0px;
  bottom           : 0px;
  left             : 0px;
  right            : 0px;
  z-index          : 2;
}
</style>

<HTML>

<div id="ele5">
  <a href="#" onclick="setTimeout(function(){if(window.getComputedStyle(document.getElementById('ele5_b'), null).display == 'none'){alert('クリックされました!');}}, 100);return false;">クリックイベント開始!</a>
  <div id="ele5_b" onclick="this.style.display='none';"></div>
</div>
<div style="margin:5px;"><input type="button" value="前面要素を追加" onclick="document.getElementById('ele5_b').style.display='block';"></div>

まとめ

以上、iPhone での :hover 疑似クラスや click イベントでの動作が思うようにいかない場合の原因と対応方法でした。

ここで挙げた以外にも動作が想定外の動きがあるかもしれないので、もし見つけたら教えて下さい。
対応方法を検証して記事にまとめたいと思います。

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)