Cordova iOS アプリで -webkit-overflow-scrolling:touch を使った時の備忘録

Cordova アプリはデフォルトで DisallowOverscrollfalse、つまりオーバースクロールする状態になっている。これにより慣性スクロールが効くわけだが、慣性スクロールによってページ外の背景色が見えてしまうのが気持ち悪い。この背景色を指定できればまだ良いのだが、どうも背景色を設定する方法はないらしい。

これを回避するために試行錯誤したので、その記録を紹介する。

DisallowOverscroll は true に設定する

まず DisallowOverscrolltrue に設定し、デフォルトのオーバースクロールをさせないようにする。

<!-- config.xml -->
<preference name="DisallowOverscroll" value="true" />

html・body 要素はスクロールさせない

次に、html・body 要素はスクロールさせず、画面いっぱいに表示領域を広げておく。

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;  /* スクロールをさせない */
}

慣性スクロールをさせるラッパーを作る

html・body 要素がスクロールしなくなったので、内部のコンテンツをスクロールできるようにしてやらなくてはならない。そのために、通常であれば body 要素に相当するページ全体をスクロールするラッパーを作る。

<div id="wrapper-outer">
  <!-- ココにコンテンツ -->
</div>
#wrapper-outer {
  width: 100%;
  height: 100%;
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;  /* 慣性スクロールを付ける */
  background-color: #9cf;  /* オーバースクロール時にこの色が見えるようにしたい */
}

これでコンテンツが多い場合は #wrapper-outer が慣性スクロールしてくれるようになる。

しかし、コンテンツが少ない場合はスクロールが発生せず、「何もないけどオーバースクロールする」というあの妙に気持ち良い動きをしてくれなくなってしまう。

また、WebView の問題なのか分からないが、内側のコンテンツが margin を消すなど #wrapper-outer 要素のオーバースクロールする領域に関わってくると、途端に background-color 指定が効かなくなってしまうというバグに遭遇した。

このバグの発生要因はイマイチ分からず、ただ linear-gradient を使用した子要素が居るだけでも発生したり、しなかったり、オーバースクロールする度に背景色が白と黒とで変わったりとおかしな挙動をしていた。

というワケで、コンテンツが少ない場合もオーバースクロールさせ、かつ指定した background-color が必ず見えるように細工していく。

必ずスクロールが発生するように内側のラッパーを作る

コンテンツが少ない場合もオーバースクロールさせるには、必ず overflow-y: scroll; でスクロールバーが有効な状態を作り出してやれば良い。そこで、#wrapper-outer の内側に #wrapper-inner を作り、こいつに必ず overflow が発生するように指定してやる。

<div id="wrapper-outer">
  <!-- inner を追加する -->
  <div id="wrapper-inner">
    <!-- ココにコンテンツ -->
  </div>
</div>
/* 新たに inner の指定を作る */
#wrapper-inner {
  width: 100%;
  min-height: 100%;  /* 最小の高さを 100% とする */
  margin-bottom: 1px;  /* 余分な margin により overflow を発生させる */
}

これでコンテンツ量が少ない場合もオーバースクロールさせられるようになった。

これでも上手くいく場合が多いのだが、これだと #wrapper-inner 直下の要素が持つ margin-top が効いてしまい、うまくオーバースクロール時の背景色が有効にならない場合がある。

そこで以下のようにする。

#wrapper-inner {
  width: 100%;
  min-height: 100%;
  padding-top: .5px;  /* こうする */
  padding-bottom: .5px;  /* こうする */
}

親要素に padding があると子要素の margin が相殺されなくなるようなので、padding-toppadding-bottom で合計 1px 分増やして常にスクロールバーを表示させるようにした。

オーバースクロール時の背景色を正しく認識させる

内側のコンテンツの margin 指定などにより、#wrapper-outerbackground-color の色ではなく、黒や白の背景色がオーバースクロールで表示されてしまう、WebView のバグっぽい挙動を解消する。

以下の StackOverflow にそれっぽい解決策があった。

#wrapper-outer の CSS を以下のように修正する。

#wrapper-outer {
  width: 100%;
  height: 100%;
  margin-top: -1px;  /* コレを追加 */
  padding-top: 1px;  /* コレを追加 */
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
  background-color: #9cf;
}

ネガティブマージンで要素を画面外に食い込ませ、その分の padding を指定して相殺している。

たったこれだけなのだが、「画面外にも対象の要素が存在する」状態を作り出すと、その要素の背景色を利用してオーバースクロールしてくれるようになるようだ。

コレがスターターセット

そんなこんなで、コレがスターターセットになった。

<!-- config.xml -->
<preference name="DisallowOverscroll" value="true" />
/* ページ全体はスクロールさせない */
html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

/* オーバースクロールさせる要素 */
#wrapper-outer {
  width: 100%;
  height: 100%;
  margin-top: -1px;
  padding-top: 1px;
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
  background-color: #9cf;  /* オーバースクロールで見える背景色 */
}

/* 常にスクロールバーを表示させる */
#wrapper-inner {
  width: 100%;
  min-height: 100%;
  padding-top: .5px;
  padding-bottom: .5px;
  /* background を指定して #wrapper-outer と背景色を変えることも可能 */
}
<div id="wrapper-outer">
  <div id="wrapper-inner">
    <!-- ココにコンテンツ -->
  </div>
</div>

以上。