今回はWebサイトのhoverについて検証した結果をまとめます。
ホバーといえば:hover擬似クラスかと思いますが、実はPCとタッチ端末では挙動が違います。
以前それを知らずに安易に使っていたことで、スマホやタブレットで想定外の動きになり困ったことがありました。
この記事では「PC・スマホ・タブレットそれぞれ同じような挙動になるhover設定方法」をテーマに書いています。
基礎的な内容ではありますが、理解するまでに何段階かのステップが必要なので、HTML・CSS・jQueryの基礎を一通り学んだくらいの方に向けた内容となっています。
前提の基礎知識(タッチ端末での:hoverと:active)
まず先にPCとタッチ端末でどのように挙動が変わるのかをまとめます。
今回のテーマはhoverですが、ここにはactiveの動きもセットで理解しておいたほうが良いので一緒に説明を載せておきます。
hover | active | |
---|---|---|
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);
}
全体を載せましたが関係ある部分は /* ここからhover・active装飾 */ とコメントアウトしている下からだけです。
aタグ、buttonタグ、divタグ、liタグ、imgタグの5項目に分けてそれぞれhoverとactiveの指定をしています。
あと、今回の記事であげているデモサイトはPCと合わせてスマホから確認することをオススメします。(というかスマホから見ないと検証できないです。。。)
(※PC閲覧中の方向け)
上のデモについて、iphoneの方であればsafariから見てもらうと分かりますが、aタグとbuttonタグ以外の要素は反応すらしていないかと思います。(ChromeやFirefoxからだと一応すべての要素で反応はしているかと思います)
ということで、まずは先にsafariも反応させるようにさせます。
ontouchstart属性の指定
<div class="wrap" ontouchstart="">
<!-- 省略 -->
</div>
次に全体を覆っている要素に対してontouchstart=” “と記述します。
記述した状態が下記のデモとなります。
(※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効かない、、、ということが起こります。
僕が以前困ったのはこのへんの判定方法のところでした。画面幅では判定できないということですね。
ただ、メディアクエリは画面幅以外にも様々な判定ができます。
【結論1】@media (hover: hover) & @media (hover: none)
@media (hover: hover) {
/* hover指定できるPCを想定したスタイル */
}
@media (hover: none) {
/* hoverが使えないタッチ端末を想定した装飾 */
}
上記のメディアクエリを記述をすることで、ユーザーエージェントや、端末環境を判定することができます。
メディアクエリーの使用 – CSS: カスケーディングスタイルシート | MDN
早速上のやり方で記述してみました。また、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と同じ装飾をあてています。
(※PC閲覧中の方向け)
どうでしょう?これでスマホやタブレットから見ても、PCのhoverと同じような挙動になりましたね!
、、、と言いたいところですが残念なことにこのやり方はIE対応していないようです…..。
IEユーザーもまだいる2021年現在では、採用は難しいでしょう。。。
↓↓↓
IEサポート終了で@media hoverも使えるように! ※2022年7月追記
2022年6月16日、IEブラウザのサポート終了しました(IEブラウザを開くとEdgeにリダイレクトされます)。
これにより上で紹介した@media (hover: hover) や @media (hover: none) が使えるようになりました!
次で紹介するjsによる制御か、メディアクエリを使った制御、どちらか選んで採用するといいでしょう。
【結論2】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です。
ここでは載せませんが、jQueryを使うのでCDNコードはHTMLのbodyの閉じタグ前に読み込んでおきましょう!詳しくはこちらから↓
$(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() メソッドの詳細は下から。
String.prototype.indexOf() – JavaScript | MDN
そして上のユーザーエージェントで判定した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"); // マウスを外した時の処理=クラス外し
}
);
}
コメントに書いたままですが、タッチ端末の場合とそうでなかった場合で分かれています。
タッチ端末だった場合はtouchstartとtouchendのイベントで、PC端末だった場合はhoverイベントで専用クラスの付け外しをしています。
あとは同じ内容をhoverの種類に合わせて記述しただけですね。
(※PC閲覧中の方向け)
いかがでしょうか?これでPC、タブレット、スマホそれぞれ同じhoverの動きが実現できたかと思います。
CSSもhover用のクラスを用意するという一手間はありますが、逆に言えばそれぞれの要素に対して:hoverの擬似クラスを設定しなくていい訳なので、全体の記述は減るかもしれません。
このやり方なら、予めjQueryさえ用意しておけば、もしhoverパターンが増えた時も管理が楽になりそうですね。
まとめ
スマホ・タブレットでもPCと同じようなhover効果を効かせる方法ということで見ていきましたが、結論最後のセクションで記載した流れが最も無難かと思いました。
- CSSでhover専用のクラスを用意しておく
- jsでユーザーエージェントから端末を判定
- 判定した端末に合わせてマウス操作かタップ操作を指示
- 指示された操作に合わせてhover専用のクラスを付け外し
@media (hover: hover) も使える ※2022年8月追記
2022年6月にIEブラウザのサポートが終了したことで、@media (hover: hover)も採用できるようになりました。
CSSだけで制御できる@media (hover: hover)も取り入れてみましょう!
また、今回はhoverに焦点を合わせて書きましたが、activeやそれ以外の装飾もこれで制御できますので結構応用が効きそうです。
色々試してみると、新しい発見もありそうですね。
当記事を読まれている方の中にはWeb制作初学者の方もいるかと思います。デザインやコーディングの基礎知識を学びたい方向けの記事を用意しているので是非見ていってください!
PENGIN無料コーディング課題
オススメ書籍紹介
オススメUdemy講座紹介