【Material Design Lite】スクロールが出来ない。など、ハマッたポイントと解決策
Material Design Lite (マテリアルデザインライト)でWordPressのテーマを作り直しました。使い方は公式サイトに詳しいですが、いくつかハマったところをメモしておきます。
- 開発終了?
https://github.com/google/material-design-lite/releases/tag/v1.3.0にあるように、新たにMaterial Designを採用するのであれば、Material Components for the Webを利用したほうがよさそうです。
グリッドデザイン
Material Design LiteはグリッドデザインにFlexを使用しています。HTMLに指定のクラスを追加するだけで、レスポンシブなグリッドデザインが簡単に出来上がります。また、特定デバイスでの表示・非表示や、マークアップとは異なる順序でのカラム表示も、クラス追加で簡単にできます。
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--6-col mdl-cell--8-col-tablet">6 (8 tablet)</div>
<div class="mdl-cell mdl-cell--4-col mdl-cell--6-col-tablet">4 (6 tablet)</div>
<div class="mdl-cell mdl-cell--2-col mdl-cell--4-col-phone">2 (4 phone)</div>
</div>
ブレークポイントとカラム数
各デバイス(画面幅)のブレークポイントと一行の最大カラム数は以下となっています。(Ver 1.2.1)
デバイス | カラム数 | ブレークポイント |
---|---|---|
phone | 4 | max-width: 479px |
tablet | 8 | (min-width: 480px) and (max-width: 839px) |
desktop | 12 | min-width: 840px |
ブレークポイントに合わせた Mixin(SCSS)をつくる
CSSのメディアクエリ簡略化のため、Mixinを作りました。
$bp-pc: 840px;
$bp-tablet_max: 839px;
$bp-tablet_min: 480px;
$bp-sp: 479px;
@mixin media($media-width)
{
@if $media-width == pc {
@media only screen and (min-width: $bp-pc) {
@content;
}
}
@else if $media-width == tablet {
@media only screen and (min-width: $bp-tablet_min) and (max-width: $bp-tablet_max) {
@content;
}
}
@else if $media-width == sp {
@media only screen and (max-width: $bp-sp) {
@content;
}
}
}
ページ内スクロール
当ページサイドバーの目次がそうですが、ページ内リンクの移動にスクロールを用いています。そのためのコードを下のように書いたのですが、ピクリとも動いてくれません。またoffset().top
の挙動もおかしい(数値が変動する)ようです。
以前のテーマでは動作に問題が無かったので、Material Design Liteに原因がありそうです。
html,bodyのスクロールがうまく動かない
Layoutページのサンプルでは、以下のようにマークアップがされています。(material.jsによる要素追加後)
<body>
<div class="mdl-layout__container">
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-drawer">
<header class="mdl-layout__header mdl-layout__header--transparent">
<div class="mdl-layout__header-row">
...
</div>
</header>
<main class="mdl-layout__content">
<div class="page-content"><!-- Your content goes here --></div>
</main>
</div>
</div>
</body>
続いてCSSを見ます。
html {
width: 100%;
height: 100%;
-ms-touch-action: manipulation;
touch-action: manipulation; }
body {
width: 100%;
min-height: 100%;
margin: 0; }
.mdl-layout__container {
position: absolute;
width: 100%;
height: 100%; }
.mdl-layout {
width: 100%;
height: 100%;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
position: relative;
-webkit-overflow-scrolling: touch; }
.mdl-layout__content {
-ms-flex: 0 1 auto;
position: relative;
display: inline-block;
overflow-y: auto;
overflow-x: hidden;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1;
z-index: 1;
-webkit-overflow-scrolling: touch; }
.mdl-layout--fixed-drawer > .mdl-layout__content {
margin-left: 240px; }
.mdl-layout__container.has-scrolling-header .mdl-layout__content {
overflow: visible; }
@media screen and (max-width: 1024px) {
.mdl-layout--fixed-drawer > .mdl-layout__content {
margin-left: 0; }
.mdl-layout__container.has-scrolling-header .mdl-layout__content {
overflow-y: auto;
overflow-x: hidden; } }
html
body
.mdl-layout__container
.mdl-layout
らがウィンドウ幅固定になっています。
従って
-
$(window).height()
(ウィンドウ縦幅)が1000px -
.mdl-layout__content
の縦幅が2000pxだった場合...
html
body
.mdl-layout__container
.mdl-layout
の縦幅は1000px、.mdl-layout__content
の縦幅が2000pxとなります。
その際、ウィンドウ幅を超えた.mdl-layout__content
の扱いは、ヘッダー固定の有無によって変わります。ヘッダー固定時はoverflow-y: auto;
が、非固定時はoverflow: visible;
が適応されます。
つまり、ヘッダー固定時は.mdl-layout__content
、非固定時は.mdl-layout
のスクロールによってウィンドウ幅を超えた.mdl-layout__content
の視認が可能になります。
offset().top
の取得値がおかしい原因はここにあったようです。
offset().topで取得値がズレる原因
以下、ヘッダー非固定を想定し、.mdl-layout
をスクロールする前提で進めます。
ウィンドウが画面幅固定でスクロールされないため、$(window).scrollTop()
は常に0となります。ページ読込み後のウィンドウをスクロールしていない状態であれば、.mdl-layout
のポジションとdocument(offsetの基準)は0で重なっており、offset().top
は想定通りの値を返してくれます。問題はページを少しでもスクロールした後です。例えば、ページ最下部までスクロールした後に、指定要素のoffset().top
を取得すると、値はマイナスを示します。原因は、ウィンドウではなく.mdl-layout
をスクロールさせるため、指定要素がウィンドウ外(上部)へと突き抜けた状態になるからです。
offset().topを正確に取得する
ページスクロール後に、指定要素の正確な位置を取得したい場合は
$('selector').offset().top
ではなく$('.mdl-layout').scrollTop() + $('selector').offset().top
とします。
スクロールを正確に
以上を踏まえて、Material Design Liteでページ内スクロールをする場合のコードは以下となります。
let targetY = $('.mdl-layout').scrollTop() + $('selector').offset().top;
$('.mdl-layout').animate({
scrollTop: targetY
}, duration);
これによって意図通りにスクロールできました。
アドセンス、SNSボタンが表示されない
SNSボタンなどiframe
を使用したコンテンツが表示直後に消えてしまいます。原因と解決策はMaterial Desgin Lightでいろいろ困る。iframeとかスクロールとかに詳しく書かれています。私はドロワーメニューを使用する予定はないので、mdl-layout
mdl-js-layout
mdl-layout__content
のクラスを使用しないことにしました。これによって、offset().top
の問題も発生しなくなりました。
Material Design 関連サイト
- マテリアル デザインのガイドライン(日本語版)
- Material Design Lite
- Material Design
- Material icons
- Material Design Color Palette Generator
終わりに
グリッド、ボタンなどのマテリアルなアニメーション。これらが簡単に再現できるのは魅力ですが、独特の挙動が厄介でした。煩わしさを考慮するとBootstrap 4正式公開後も使用するかは微妙なところです。