8/30〜|ライターのダイチが運営している個人メディア「ダイブログ」とPENGIN BLOGを統合しました

スマホのhover対策を解説!(PCと同じCSS効果を実現)

お知らせ

当記事の元サイト「ダイブログ」は、8/30〜「PENGIN PLOG」と統合しました。
※元ドメイン(https://daib-log.com/〜)からはリダイレクト処理されます。

今回はWebサイトのhoverについて検証した結果をまとめます。

ホバーといえば:hover擬似クラスかと思いますが、実はPCとタッチ端末では挙動が違います。

以前それを知らずに安易に使っていたことで、スマホやタブレットで想定外の動きになり困ったことがありました。

この記事では「PC・スマホ・タブレットそれぞれ同じような挙動になるhover設定方法」をテーマに書いています。

基礎的な内容ではありますが、理解するまでに何段階かのステップが必要なので、HTML・CSS・jQueryの基礎を一通り学んだくらいの方に向けた内容となっています。

前提の基礎知識(タッチ端末での:hoverと:active)

まず先にPCとタッチ端末でどのように挙動が変わるのかをまとめます。

今回のテーマはhoverですが、ここにはactiveの動きもセットで理解しておいたほうが良いので一緒に説明を載せておきます。

hoveractive
PC要素にマウスカーソルが乗っているときのスタイル要素をクリックしたときのスタイルタ
スマホタップ後、違う要素がタップされるまでのスタイルタップ中のみのスタイル

スマホにおける:hoverは、タップした要素に装飾があたった後、別の要素に「タップされた」と判定されるまで装飾が残ります。

:hoverで指定したものは、タップして指を離した後も設定した装飾が残り続けることになるのでデザイン的・ユーザビリティ的にもあまりよくありません。

これでいくと、タッチ端末でタップする瞬間の装飾をしたいなら、:hoverではなく:activeの方が近いということになります。

では、PCではhover、スマホ・タブレットではactiveと切り分けて、同じスタイルをそれぞれ指定すればいい、という考えもできますがなかなかスッキリしませんよね。

するにしても、結局どの端末からサイトを閲覧されているかの判定が必要になります。

そもそもタッチ端末ではユーザーの動作に反応する装飾は行わない、というのも選択肢としてはありだと思いますが、今回はそこも含めてどう制御するかという観点でまとめていきます。

端末・ブラウザによる挙動の違い

ios特有のものなのか、特にsafariは他ブラウザと読み込み条件が異り、何かしらの動作を開始する要素をタップした時のみ、hoverイベントが発生するようになっています。

そこで今回はいくつかのステップに分けて挙動変化を見ていきます。

CODE PENだとちょっと動きが分かりづらかったので、今回もセクション毎にデモサイトは作成しています。

また、hover・activeどちらも同じ内容の装飾としています。

:hover指定のみ

まずは何も対策を施していない状態のものから。

<body>
  <div class="wrap">
    <h1 class="text">hoverデモサイト1</h1>
    <p class="text">hoverを指定しただけ</p>
    <div class="text-flex">
      <p>hover</p>
      <p>active</p>
    </div>
    <!-- aタグ -->
    <div class="wrap-flex">
      <a href="" class="common link link-hover">aタグhover</a>
      <a href="" class="common link link-active">aタグactive</a>
    </div>
    <!-- buttonタグ -->
    <div class="wrap-flex">
      <button type="button" class="common btn btn-hover">buttonタグhover</button>
      <button type="button" class="common btn btn-active">buttonタグactive</button>
    </div>
    <!-- divタグ -->
    <div class="wrap-flex">
      <div class="common div div-hover">divタグhover</div>
      <div class="common div div-active">divタグactive</div>
    </div>
    <!-- liタグ -->
    <div class="wrap-flex">
      <ul class="common">
        <li class="li li-hover">liタグhover</li>
        <li class="li li-hover">liタグhover</li>
        <li class="li li-hover">liタグhover</li>
        <li class="li li-hover">liタグhover</li>
      </ul>
      <ul class="common">
        <li class="li li-active">liタグactive</li>
        <li class="li li-active">liタグactive</li>
        <li class="li li-active">liタグactive</li>
        <li class="li li-active">liタグactive</li>
      </ul>
    </div>
    <!-- imgタグ -->
    <div class="wrap-flex">
      <img src="hover-img.jpg" alt="hoverイメージ画像" class="common img img-hover">
      <img src="hover-img.jpg" alt="hoverイメージ画像" class="common img img-active">
    </div>
  </div>
</body>
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  list-style: none;
}
body {
  background-color: #e7e4e4;
}
.wrap {
  width: 90%;
  max-width: 600px;
  margin: 0 auto;
}
.text {
  margin: 32px 0;
  text-align: center;
}
.text-flex {
  display: flex;
  justify-content: space-around;
  margin-bottom: 16px;
}
.text-flex>p {
  width: 100%;
  text-align: center;
}
.text-flex>p:nth-of-type(1) {
  border-right: 1px solid #333;
}
.wrap-flex {
  padding: 48px 16px;
  margin-bottom: 32px;
  display: flex;
  justify-content: space-around;
  background-color: #dadada;
}
/*********** ここからhover・active装飾 ***********/
/* 共通装飾 */
.common {
  cursor: pointer;
  transition: all .3s;
}
.common:first-child {
  margin-right: 1em;
}
/* aタグ */
.link {
  color: #333;
}
.link-hover:hover,
.link-active:active {
  color: #f95d41;
}
/* buttonタグ */
.btn {
  padding: 2em;
  border: none;
  border-radius: 4px;
  color: #f7f3f3;
  background-image: linear-gradient(#6795fd 0%, #67ceff 100%);
  box-shadow: 0px 4px 2px rgba(0, 0, 0, .3);
}
.btn-hover:hover,
.btn-active:active {
  background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
  box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
  transform: translateY(-8px);
}
/* divタグ */
.div {
  padding: 2em;
  border: none;
  border-radius: 4px;
  color: #f7f3f3;
  background-image: linear-gradient(#e49c32 0%, #ffe867 100%);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, .3);
}
.div-hover:hover,
.div-active:active {
  background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
  box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
  transform: translateY(-8px);
}
/* liタグ */
.li {
  padding: .5em 2em;
  margin-bottom: 16px;
  background-image: linear-gradient(#32e4af 0%, #9ce4ce 100%);
  box-shadow: 0px 1px 1px rgba(0, 0, 0, .3);
  border-radius: 4px;
  color: #f7f3f3;
  transition: all .3s;
}
.li-hover:hover,
.li-active:active {
  background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
  box-shadow: 1px 4px 1px rgba(0, 0, 0, .3);
  transform: translateY(-4px);
}
/* imgタグ */
.img {
  width: 50%;
  height: 100%;
  object-fit: cover;
}
.img-hover:hover,
.img-active:active {
  opacity: .8;
  box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
  transform: translateY(-8px);
}

クラス名とか諸々雑ですが、とりあえずaタグ、buttonタグ、divタグ、liタグ、imgタグの5項目に分けてそれぞれhoverとactiveの指定をしています。

activeとの挙動の違いを見せるために、並べた形でレイアウトしています。

また、一旦全体を載せましたが関係ある部分は /* ここからhover・active装飾 */ とコメントアウトしている下からだけです。

あと、今回の記事であげているデモサイトはPCと合わせてスマホから確認することをオススメします。(というかスマホから見ないと検証できないです。。。)

デモサイト1QR

(※PC閲覧中の方向け)

上のデモについて、iphoneの方であればsafariから見てもらうと分かりますが、aタグとbuttonタグ以外の要素は反応すらしていないかと思います。(ChromeやFirefoxからだと一応すべての要素で反応はしているかと思います)

ということで、まずは先にsafariも反応させるようにさせます。

ontouchstart属性の指定

<div class="wrap" ontouchstart="">
   <!-- 省略 -->
</div>

次に全体を覆っている要素に対してontouchstart=” “と記述します。

記述した状態が下記のデモとなります。

デモサイト2QR

(※PC閲覧中の方向け)

これでsafariから見ても、Chromeなど他ブラウザと同様の動きになったかと思います。

ontouchstart属性は、iOSやアンドロイドOS 上で動作するスマホやタブレット端末で実装されているもので、ユーザーがタッチ面のタッチ点に触れたときに発生するイベントを呼び出すものです。

ちょっと難しいですが、、、

ここでは中身を指定する必要は無いのでダブルクォーテーションは空白です。

また、サイト全体に適用させたいからとbody要素に記述すると、height指定している場合に上手く反映されないことがあるようなので、それ以外の個別のクラスや、今回のデモのように全体wrapクラスに指定すると良さそうです。

※上のサイト内で「これを指定しないとタッチでは:activeと:hoverは反応しない」と条件の記載がありませんが、デモ1のように、ブラウザによっては記述無しでも反応することがわかりました。(ChromeやFirefox)

これで一応hoverやactiveが反応はするようになりましたので、肝心の「PCと同じような動き」にしていきます。

メディアクエリ での制御

PCとスマホを切り分ける、と考えた時に真っ先に浮かんだのが画面幅でスタイルを制御するメディアクエリ です。

@media screen and ( max-width:960px){
/* この中にタブレットサイズ以下のスタイル */
}

こんな感じでスタイルを切り分けて、PCでは:hoverで指定して、タブレット以下では:activeで指定する。

ただこれだと問題があって、PCでもタブレットサイズ程度にビューポート幅を狭めて見ることがありますし、逆にタブレットでも1,024pxあるiPad Pro 12.9だったらPCサイズ判定されてhover効かない、、、ということが起こります。

僕が以前困ったのはこのへんの判定方法のところでした。画面幅では判定できないということですね。

ただ、メディアクエリは画面幅以外にも様々な判定ができます。

@media (hover: hover) {
/* hover指定できるPCを想定したスタイル */
}
@media (hover: none) {
/* hoverが使えないタッチ端末を想定した装飾 */
}

上記のメディアクエリを記述をすることで、ユーザーエージェントや、端末環境を判定することができます。

早速上のやり方で記述してみました。また、activeとの比較はもう不要になったので、HTMLとCSSから削除しています。

<!-- aタグ -->
    <div class="wrap-flex">
      <a href="" class="common link">aタグhover</a>
    </div>
<!-- buttonタグ -->
    <div class="wrap-flex">
      <button type="button" class="common btn">buttonタグhover</button>
    </div>
<!-- divタグ -->
    <div class="wrap-flex">
      <div class="common div">divタグhover</div>
    </div>
<!-- liタグ -->
   <div class="wrap-flex">
      <ul class="common">
        <li class="li">liタグhover</li>
        <li class="li">liタグhover</li>
        <li class="li">liタグhover</li>
        <li class="li">liタグhover</li>
      </ul>
    </div>
<!-- imgタグ -->
    <div class="wrap-flex">
      <img src="hover-img.jpg" alt="hoverイメージ画像" class="common img">
    </div>
/* aタグ */
.link {
  color: #333;
}
/* buttonタグ */
.btn {
  padding: 2em;
  border: none;
  border-radius: 4px;
  color: #f7f3f3;
  background-image: linear-gradient(#6795fd 0%, #67ceff 100%);
  box-shadow: 0px 4px 2px rgba(0, 0, 0, .3);
}
/* divタグ */
.div {
  padding: 2em;
  border: none;
  border-radius: 4px;
  color: #f7f3f3;
  background-image: linear-gradient(#e49c32 0%, #ffe867 100%);
  box-shadow: 0px 4px 2px rgba(0, 0, 0, .3);
}
/* liタグ */
.li {
  padding: .5em 2em;
  margin-bottom: 16px;
  background-image: linear-gradient(#32e4af 0%, #9ce4ce 100%);
  box-shadow: 0px 1px 1px rgba(0, 0, 0, .3);
  border-radius: 4px;
  color: #f7f3f3;
  transition: all .3s;
}
/* imgタグ */
.img {
  width: 50%;
  height: 100%;
  object-fit: cover;
}
/******* メディアクエリ での制御 *******/
/* :hoverが使える端末を想定 */
@media (hover: hover) {
  .link:hover {
    color: #f95d41;
  }
  .btn:hover {
    background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
    box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
    transform: translateY(-8px);
  }
  .div:hover {
    background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
    box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
    transform: translateY(-8px);
  }
  .li:hover {
    background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
    box-shadow: 1px 4px 1px rgba(0, 0, 0, .3);
    transform: translateY(-4px);
  }
  .img:hover {
    opacity: .8;
    box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
    transform: translateY(-8px);
  }
}
/* :hoverが使えない端末を想定 */
@media (hover: none) {
  .link:active {
    color: #f95d41;
  }
  .btn:active {
    background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
    box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
    transform: translateY(-8px);
  }
  .div:active {
    background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
    box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
    transform: translateY(-8px);
  }
  .li:active {
    background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
    box-shadow: 1px 4px 1px rgba(0, 0, 0, .3);
    transform: translateY(-4px);
  }
  .img:active {
    opacity: .8;
    box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
    transform: translateY(-8px);
  }
}

○○-hoverとしていたクラスもここでは不要なので削除しています。

hoverが反映されない端末に対しては、@media (hover: none)内で:activeの指定をすることで、PC時の:hoverと同じ装飾をあてています。

デモサイト3QR

(※PC閲覧中の方向け)

どうでしょう?これでスマホやタブレットから見ても、PCのhoverと同じような挙動になりましたね!完成です!

、、、と言いたいところですが残念なことにこのやり方はIE対応していないようです…..。

ということで、別のやり方をもう一つ見つけたので次で紹介します。今のところこれが答えなのかな、と思う方法です。

(結論)jsと専用クラスで制御

CSSで専用クラスを用意し、jsで端末を判定し、それに合わせて専用クラスを付け外しするという方法です。まず先にコードから。

<!-- aタグ -->
    <div class="wrap-flex">
      <a href="" class="common link js-link-hover">aタグhover</a>
    </div>
<!-- buttonタグ -->
    <div class="wrap-flex">
      <button type="button" class="common btn js-btn-hover">buttonタグhover</button>
    </div>
<!-- divタグ -->
    <div class="wrap-flex">
      <div class="common div js-div-hover">divタグhover</div>
    </div>
<!-- liタグ -->
    <div class="wrap-flex">
      <ul class="common">
        <li class="li js-li-hover">liタグhover</li>
        <li class="li js-li-hover">liタグhover</li>
        <li class="li js-li-hover">liタグhover</li>
        <li class="li js-li-hover">liタグhover</li>
      </ul>
    </div>
<!-- imgタグ -->
    <div class="wrap-flex">
      <img src="hover-img.jpg" alt="hoverイメージ画像" class="common img js-img-hover">
    </div>

各要素にjs-○○-hoverというクラスを追加しています。

/* メディアクエリの記述は消してます */
/********** hover用クラス(js付け外し) **********/
.link-hover {
  color: #f95d41;
}
.block-hover {
  background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
  box-shadow: 2px 8px 4px rgba(0, 0, 0, .3);
  transform: translateY(-8px);
}
.list-hover {
  background-image: linear-gradient(#f95d41 0%, #f89e87 100%);
  box-shadow: 1px 4px 1px rgba(0, 0, 0, .3);
  transform: translateY(-4px);
}

先ほどメディアクエリを記述した部分を削除し、jsで付け外しするクラスを用意しました。

今回はaタグが「link-hover」、liタグが「list-hover」、それ以外を「block-hover」という名前にしています。

次にjQueryです。

$(function () {
  var userAgent = navigator.userAgent; // ユーザーエージェント判定
  var link = $('.js-link-hover'); // aタグ要素代入
  var block = $('.js-btn-hover , .js-div-hover , .js-img-hover'); // buttonタグ、divタグ、imgタグ要素代入
  var list = $('.js-li-hover'); // liタグ要素代入
  // aタグを踏んだ時の端末判定とhover装飾
  if (userAgent.indexOf("iPhone") >= 0 || userAgent.indexOf("iPad") >= 0 || userAgent.indexOf("Android") >= 0) {
    link.on("touchstart", function () {
      $(this).addClass("link-hover");
    });
    link.on("touchend", function () {
      $(this).removeClass("link-hover");
    });
  } else {
    link.hover(
      function () {
        $(this).addClass("link-hover");
      },
      function () {
        $(this).removeClass("link-hover");
      }
    );
  }
  // buttonタグ、divタグ、imgタグを踏んだ時の端末判定とhover装飾
  if (userAgent.indexOf("iPhone") >= 0 || userAgent.indexOf("iPad") >= 0 || userAgent.indexOf("Android") >= 0) {
    block.on("touchstart", function () {
      $(this).addClass("block-hover");
    });
    block.on("touchend", function () {
      $(this).removeClass("block-hover");
    });
  } else {
    block.hover(
      function () {
        $(this).addClass("block-hover");
      },
      function () {
        $(this).removeClass("block-hover");
      }
    );
  }
  // liタグを踏んだ時の端末判定とhover装飾
  if (userAgent.indexOf("iPhone") >= 0 || userAgent.indexOf("iPad") >= 0 || userAgent.indexOf("Android") >= 0) {
    list.on("touchstart", function () {
      $(this).addClass("list-hover");
    });
    list.on("touchend", function () {
      $(this).removeClass("list-hover");
    });
  } else {
    list.hover(
      function () {
        $(this).addClass("list-hover");
      },
      function () {
        $(this).removeClass("list-hover");
      }
    );
  }
});

if関数は一つにまとめて書いたほうがスマートかと思いますが、今回はパターン毎に分かるよう分けて書いてます。

ここからは一行ずつ分解して見ていきます。

var userAgent = navigator.userAgent; // ユーザーエージェント判定

navigator.userAgentでサイトに訪れたユーザーのブラウザや端末情報が取得できるので、これを変数に代入しています。

ちなみにユーザーエージェント情報は下の一文を書くだけでデベロッパーツール のconsoleから確認できます。

console.log(navigator.userAgent);
// デベロッパーツールconsoleからこんな感じの情報が取得できる
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
var link = $('.js-link-hover'); // aタグ要素代入
var block = $('.js-btn-hover , .js-div-hover , .js-img-hover'); // buttonタグ、divタグ、imgタグ要素代入
var list = $('.js-li-hover'); // liタグ要素代入

これは後からクラスが追加された場合にも対応できるように、汎用性を考慮して変数に代入しているだけです。

if (userAgent.indexOf("iPhone") >= 0 || userAgent.indexOf("iPad") >= 0 || userAgent.indexOf("Android") >= 0) {

判定したユーザーエージェント情報に対して.indexOf(“○○○”) >= 0 と記述しています。

indexOf() メソッドは引数に与えられた内容と同じ内容を持つ配列要素の内、最初のものの添字を返します。存在しない場合は -1 を返します。

なのでユーザーエージェント情報に「iPhone」とあれば、その文字までのインデックス値を返すことになります。

これが0以上=-1でなければ、()内で指定した端末の文字があった、ということになります。

indexOf() メソッドの詳細は下から。

そして上のユーザーエージェントで判定したif式の中身です。

//////////// タッチ端末だと判定できていたら
link.on("touchstart", function () {   // 変数linkをタッチしたら
      $(this).addClass("link-hover");  // 専用のhoverクラスを付与
    });
    link.on("touchend", function () {   // 変数linkのタッチが外れたら
      $(this).removeClass("link-hover"); // 専用のhoverクラスを外す
    });
//////////// タッチ端末だと判定できなかったら = PC端末だったら
  } else {
    link.hover(                          // 変数linkにhoverイベント
      function () {
        $(this).addClass("link-hover");   // マウスを乗せた時の処理=クラス付与
      },
      function () {
        $(this).removeClass("link-hover"); // マウスを外した時の処理=クラス外し
      }
    );
  }

コメントに書いたままですが、タッチ端末の場合とそうでなかった場合で分かれています。

タッチ端末だった場合はtouchstarttouchendのイベントで、PC端末だった場合はhoverイベントで専用クラスの付け外しをしています。

あとは同じ内容をhoverの種類に合わせて記述しただけですね。

デモサイト4QR

(※PC閲覧中の方向け)

いかがでしょうか?これでPC、タブレット、スマホそれぞれ同じhoverの動きが実現できたかと思います。

CSSもhover用のクラスを用意するという一手間はありますが、逆に言えばそれぞれの要素に対して:hoverの擬似クラスを設定しなくていい訳なので、全体の記述は減るかもしれません。

このやり方なら、予めjQueryさえ用意しておけば、もしhoverパターンが増えた時も管理が楽になりそうですね。

まとめ

スマホ・タブレットでもPCと同じようなhover効果を効かせる方法ということで見ていきましたが、結論最後のセクションで記載した流れが最も無難かと思いました。

  1. CSSでhover専用のクラスを用意しておく
  2. jsでユーザーエージェントから端末を判定
  3. 判定した端末に合わせてマウス操作かタップ操作を指示
  4. 指示された操作に合わせてhover専用のクラスを付け外し

また、今回はhoverに焦点を合わせて書きましたが、activeやそれ以外の装飾もこれで制御できますので結構応用が効きそうです。

色々試してみると、新しい発見もありそうですね。


当記事を読まれている方の中にはWeb制作初学者の方もいるかと思います。デザインやコーディングの基礎知識を学びたい方向けの記事を用意しているので是非見ていってください!

PENGIN無料コーディング課題

【デザインカンプ無料配布】未経験からのコーディング学習ステップ

オススメ書籍紹介

【初学者向け】書籍でWeb制作を学ぶ!オススメ書籍をジャンル別に紹介! Web制作のおすすめ本24選!(コーディング・Webデザイン)

オススメUdemy講座紹介

Udemyオススメ講座まとめ 【2021年8月】Udemy×Web制作!オススメ優良講座を紹介!