2019.01.26

スマホサイズではスワイプ(スクロール)させてデスクトップサイズでは一覧表示する


この記事では、Web ページで写真などの要素を並べる際に、以下の画像のように画面幅に応じてレスポンシブに、スワイプ表示になったり普通に一覧表示になったりさせたい…そんなデザイン上の要請があった場合の実装方法を紹介します。

👇 スマホサイズ

スマホサイズ

👇 デスクトップサイズ

デスクトップサイズ

スワイプの実装には Swiper を使用します。
そして JavaScript によって画面の幅が変わるタイミングを検知して、

  • 一定の幅以内であれば Swiper を初期化する
  • 一定の幅以上であれば Swiper を破棄する

という処理を加えます。

この「画面の幅が変わるタイミングを検知」するテクニックを2通り紹介します。

  • MediaQueryList を使った実装
  • resize イベントを使った実装

MediaQueryList を使った実装例(IE10+)

まずは MediaQueryList オブジェクトを用いた実装例です。

See the Pen Responsive swipe by Masahiro Harada (@MasahiroHarada) on CodePen.

画面が小さい場合は右上の「EDIT ON CODEPEN」をクリックすると CodePen のサイトにジャンプして大きな画面で見られます。

コードの全体は上の CodePen をご覧いただくとして、ここではポイントのみ説明します。

MediaQueryList オブジェクト

MediaQueryList は CSS と同様のメディアクエリを用いて以下の機能を実現します。

  • 現在の画面幅がメディアクエリを満たすかを評価する
  • メディアクエリの評価の変化を検知する

オブジェクトを作成する

まず window オブジェクトの matchMedia メソッドを実行して MediaQueryList オブジェクトを生成します。

var mql = window.matchMedia('(max-width: 600px)');

引数はメディアクエリを表す文字列です。CSS で書くのと同じです。

現在の画面幅を評価する

matches プロパティにより、現在の画面幅がメディアクエリを満たすかを評価できます。

mql.matches;

今回の例では MediaQueryList オブジェクト生成時に (max-width: 600px) というメディアクエリを与えているため、matches プロパティの値は以下の通りです。

  • 画面の横幅が 600px 以下の場合に true
  • 画面の横幅が 601px 以上の場合は false

クエリの評価を監視する

さらに addListener メソッドにより、メディアクエリの評価が変化したタイミングで任意の関数を実行することができます。

mql.addListener(function () { ... });

つまり上の例で言うと、

  • 画面の横幅が 600px 以下になったときに引数の関数が実行される
  • 画面の横幅が 601px 以上になったときに引数の関数が実行される

ということです。

resize イベントと大きく異なり MediaQueryList オブジェクトが便利な点は、画面幅が変わるたびに毎回イベントが発行されるのではなく、あくまでメディアクエリの評価が変化したタイミングでだけイベントが発行されることです。

制約

MediaQueryList オブジェクトは IE では 10 以降でしか実装されていません。
https://caniuse.com/#search=MediaQueryList

どうしても IE9 もサポートしなければいけない…という不憫なプロジェクトでは、以下の resize イベントを用いた実装例を参考にしてください。

resize(IE9+)

See the Pen Responsive swipe (resize event) by Masahiro Harada (@MasahiroHarada) on CodePen.

こちらもポイントのみ説明します。

画面幅を取得する

画面幅は innerWidth プロパティにより取得します。

window.innerWidth

resize イベント

resize イベントを扱う際に注意すべき点は、画面のリサイズが行われている間じゅう、resize イベントが発行され続けるということです。

(実際にそんなことする人がいるかどうかは置いておいて、)デスクトップでブラウザの横幅を縮めていくと、連続して何度も resize イベントが発行され続けます。

以下の記述をしたとすると、initSwiper 関数が実行され続けるということです。

window.addEventListener('resize', initSwiper);

これでは無駄に処理コストがかかってしまいます。そこで、少なくとも画面のリサイズが終わったタイミングでだけ initSwiper 関数を実行するため、以下のように記述します。

var timer;

window.addEventListener('resize', function () {
  if (timer) {
    clearTimeout(timer);
  }

  timer = setTimeout(initSwiper, 200);
});

かなり複雑なコードだと思いますが、resize イベントを「間引いて」います。
画面のリサイズが行われると、

  1. initSwiper 関数を 200 ミリ秒後に実行するタイマーをセットします。
  2. 200ミリ秒以内に再度 resize イベントが発行されると clearTimeout によりタイマーはリセットされ、改めて initSwiper 関数を 200 ミリ秒後に実行するタイマーをセットします。

200ミリ秒というのは「だいたいこれくらいだろう」という数値です。上記の処理が画面がリサイズされている間じゅう繰り返されることにより、200ミリ秒間 resize イベントが発行されなくなって初めて initSwiper 関数が実行されます。200ミリ秒間 resize イベントが発行されないことをもって、リサイズが終わったと見なしているわけです。

制約

画面幅を取得するための innerWidth は IE8 では実装されていません。

IE8 をサポートする必要があれば…他の方法を探しましょう。または IE8 でリッチなレスポンシブデザインはあきらめましょう。

おまけ:Vue.js

おまけとして MediaQueryList を使ったパターンの Vue.js バージョンも載せておきます。

See the Pen Responsive swipe (Vue.js) by Masahiro Harada (@MasahiroHarada) on CodePen.


以上、ある要素をスマホサイズではスワイプ(スクロール)させてデスクトップサイズでは一覧表示するレスポンシブデザイン手法を紹介しました。