情報系大学院生のWebメモ

ソフトウェアや Web サービス、Windows や Mac に関する情報系大学院生の備忘録ブログ

Google Material Design Lite の TEXT-HEAVY WEBPAGE(text-only) で内部リンクによるタブパネルの切り替えを実装する

このエントリーをはてなブックマークに追加

Google Material Design Lite(mdl) のテンプレートの1つである TEXT-HEAVY WEBPAGE(text-only) で内部リンクによるタブパネルの切り替えを実装するコードを紹介します。

Google Material Design Lite(mdl) とは

TEXT-HEAVY WEBPAGE(text-only) とは

Material Design Lite のテンプレートの1つです。

Material Design Lite の未実装部分

Material Design Lite はウェブサイトに Material Design の見た目や雰囲気を与えるためのコンポーネントの集まりであり、一般的なフレームワークやテンプレートのようにすべての見た目や動作を実装しているわけではありません。Material Design Lite の What's Next? の項にも以下のように書かれています。

Material Design Lite is built to provide a lightweight and basic set of Material Design components and templates for web sites. The project does not intend to provide structures to create all possible UX needs, but to provide a low-friction Material Design implementation you can build on. Even within Material Design itself, cards specifically, it is unfeasible to provide every combination in a seamless manner. When you find something not provided, such as dropdowns in the drawer, you may need to code your own component.

つまり、Material Design Lite には未実装な部分があるので、そこは自分自身でコーディングしなければなりません。

TEXT-HEAVY WEBPAGE(text-only) の未実装部分

TEXT-HEAVY WEBPAGE(text-only) では、タブをクリックしたときに、タブパネルの切り替えを行うことはできます。例えば、「OVERVIEW」タブから「FEATURES」タブへ切り替えることはできます。

f:id:sherlock_kjs:20160527155014g:plain

しかし、タブパネルやフッターなどの内部リンクによるタブパネルの切り替えは実装されていません。例えば、トップページの「READ OUR FEATURES」をクリックしても、「FEATURES」タブパネルへ切り替わりません。

f:id:sherlock_kjs:20160527155015g:plain

TEXT-HEAVY WEBPAGE(text-only) で内部リンクによるタブパネルの切り替えを実装する

内部リンクによるタブパネルの切り替えを実装する前に、TEXT-HEAVY WEBPAGE(text-only) のディレクトリ構造と、タブとタブパネルの関係について紹介します。

ディレクトリ構造

TEXT-HEAVY WEBPAGE(text-only) をダウンロードすると、以下のようなディレクトリ構造になっています。

mdl-templete-text-only/
┣ images/
┃┣ android-desktop.png
┃┣ favicon.png
┃┗ ios-desktop.png
┣ index.html
┣ LICENCE
┗ style.css

タブとタブパネルの関係

TEXT-HEAVY WEBPAGE(text-only) のタブとタブパネルの関係について紹介します。TEXT-HEAVY WEBPAGE(text-only) のタブバー(div.mdl-layout__tab-bar)内には、「Overview」や「Features」などのタブ(div.mdl-layout__tab)があります。

<!-- index.html の73行付近を簡略化した例 -->
<div class="mdl-layout__tab-bar">
  <a href="#overview" class="mdl-layout__tab is-active">Overview</a>
  <a href="#features" class="mdl-layout__tab">Features</a>
  <a href="#details" class="mdl-layout__tab">Details</a>
  <a href="#technology" class="mdl-layout__tab">Technology</a>
  <a href="#faq" class="mdl-layout__tab">FAQ</a>
</div>

これらのタブをクリックしたときに表示される各ページをタブパネル(div.mdl-layout__tab-panel)と呼びます。

<!-- index.html の86行目 -->
<div class="mdl-layout__tab-panel is-active" id="overview">

<!-- index.html の184行目 -->
<div class="mdl-layout__tab-panel" id="features">

<!-- Details タブ、Technology タブ、FAQ タブに対応するタブパネルは実装されていません -->

タブとタブパネルを見てみると、is-active クラスが付与されているタブパネルが現在表示されているタブパネルであることが分かります。

内部リンクによるタブパネルの切り替えを実装する

「READ OUR FEATURES」をクリックしたら、「FEATURES」タブへ切り替わるように実装する例を紹介します。

初めに、リンク元の a 要素(97行目)に mdl-js-internal-link クラスを付与します。また、a 要素の href 属性の値も付与されていないので、href 属性の値をリンク先の id 属性の値(features)と合わせます。

<!-- index.html の97行目 -->
<!-- Before -->
<a href="#" class="mdl-button">Read our features</a>

<!-- After -->
<a href="#features" class="mdl-button mdl-js-internal-link">Read our features</a>

次に、すべてのタブ(74行目~78行目)に href属性の値-tab ID を付与します。例えば、href 属性の値が #overview であるタブ(74行目)には overview-tab ID を、href 属性の値が #features であるタブ(75行目)には features-tab ID を付与します。

<!-- index.html の73行目付近 -->
<!-- Before -->
<div class="mdl-layout__tab-bar">
  <a href="#overview" class="mdl-layout__tab is-active">Overview</a>
  <a href="#features" class="mdl-layout__tab">Features</a>
</div>

<!-- After -->
<div class="mdl-layout__tab-bar">
  <a href="#overview" class="mdl-layout__tab is-active" id="overview-tab">Overview</a>
  <a href="#features" class="mdl-layout__tab" id="features-tab">Features</a>
</div>

次に、内部リンクによるタブパネルの切り替えを行う以下の JavaScript コードを mdl-templete-text-only/ ディレクトリ内に internal-links.js として保存します。

'use strict';

(function() {
  var internalLinks = document.getElementsByClassName('mdl-js-internal-link');
  for (var i = 0, l = internalLinks.length; i < l; i++) {
    internalLinks[i].addEventListener('click', navigateLink, false);
  }
}());

// リンク先まで遷移する
function navigateLink(e) {
  /**
   * `生きている`ノードリストを for 文内で削除すると、
   * ノードリストの要素数 length も動的に減少するので、
   * 条件式の判定で意図しない結果になりやすい
   * (高速化のためにも)`生きている`ノードリストを静的な配列に変換する
   */
  var array = [].slice.call(document.getElementsByClassName('is-active'));
  // リンク元の href 属性から、リンク先の ID 名を抽出する
  var targetIdName = this.href.split('#')[1];
  var panel = findAncestor(document.getElementById(targetIdName));
  var tab = document.getElementById(panel.id + '-tab');

  // イベントのアクションと伝搬を無効にする
  e.preventDefault();
  e.stopPropagation();

  // タブとタブパネルから is-active クラスを削除する
  for (var i = 0, l = array.length; i < l; i++) {
    array[i].classList.remove('is-active');
  }

  // タブとタブパネルに is-active クラスを付与する
  tab.classList.add('is-active');
  panel.classList.add('is-active');

  // リンク先の要素までスクロールする
  document.getElementById(targetIdName).scrollIntoView(true);
}

// リンク先の要素が属するタブパネルの要素(祖先要素)を取得する
function findAncestor(targetElement) {
  var ancestorClassName = 'mdl-layout__tab-panel';
  while (!targetElement.classList.contains(ancestorClassName))
    targetElement = targetElement.parentElement;
  return targetElement;
}

最後に、index.html の最終行付近にある material.min.js の読み込みの下に <script src="./internal-links.js"></script> と記述して、上記の JavaScript ファイルを読み込みます。

<!-- index.html の最終行付近 -->
    <script src="https://code.getmdl.io/1.1.3/material.min.js"></script>
    <script src="./internal-links.js"></script> <!-- この行を追加する -->
  </body>
</html>

以上の操作を行うことによって、「READ OUR FEATURES」をクリックすることで、「FEATURES」タブパネルへ切り替わることができます。

f:id:sherlock_kjs:20160527155016g:plain

以降は、初めの手順と同様にリンク元の a 要素に mdl-js-internal-link クラスを付与することによって、内部リンクによるタブパネルの切り替えを行うことができます。

雑感

とりあえず動くコードを書いたので、改良すべき点がいくつかあるのは認識しています。JavaScript 初心者なので、おかしな点、改良すべき点、綺麗に書ける点などがあれば、バシバシ指摘してください><