Create a gist now

Instantly share code, notes, and snippets.

CSS でペライチ

CSS でペライチ

これは CSS Programming Advent Calendar 2012 の 14 日目の記事です。

Sticky Positioning

8 月の末に、WebKit に position: sticky というプロパティが追加されました。 position: sticky が指定された要素は、親要素との位置関係によって、position: fixed または position: relative が指定されたかのように振る舞います。 例えば、ある程度スクロールするとサイドバーやナビゲーションが固定されるサイトをたまに見かけますが(例えば Togetter, LESS のサイト, etc.)その動作が CSS で指定できるようになったわけです。 HTML5Rocks に詳しい解説があるので、「ただしい」使用法についてはそちらを参照してください。

現在は Chrome (おそらく 23 以上)に実装が載っています。 about:flags で「試験運用版の WebKit 機能を有効にする」をオンにする必要があります。

スクロール時エフェクトが CSS でできるよ!

さて、sticky positioned な要素の親要素に、CSS Transforms を適用することを考えます。 すると、親要素との位置関係(より正しくは、親要素の bounding box との位置関係)が変化するため、sticky な要素の動作も変形されます。

これは position: fixedrelative には見られない特徴的な挙動です。 この挙動を「ただしくなく」利用して、例えばスクロール時にいろいろな方向に要素が動く効果(いわゆる「高級ペライチ」、「パララックス」)を CSS で実現できます。

ブラウザが適当に処理してくれるのか、要素が巨大だったり数が多くても、割と軽い動作になっています。 意図したとおりに動かすには計算がやや煩雑ですので、上のように CSS プリプロセッサを使うのがよいでしょう。 また、3D Transforms を組み合わせると、スクロールに応じて大きさを変化させたりということも可能です。 しかしさらに面倒になるので上の mixin にはその機能はありません……。

sticker.scss では sqrt()arctan() を自前で用意していますが、Sass の Custom Functions などで既存の実装を使うほうがよいと思います。 (ただし、SCSS で自前で書いても、コンパイル時間や精度など特に問題にはなりませんでした。)

sticky な要素の位置算出方法

sticky な要素が一体どこに配置されるのか計算してみたいと思います。 まず、要素の bounding box とは、四辺が水平または垂直な長方形で、その要素を完全に覆うもののうち、最小のものをいうことにします。

definition of bounding box

以下では、ページは次のような状態になっているとします。

<style>
  #parent {
    transform: ...; /* 実際には vendor prefix がつきます、以下同様 */
  }
  #sticky {
    position: sticky;
    left: ...; /* または right */
    top: ...; /* または bottom */
  }
</style>
<div id="parent">
  <div id="sticky"></div>
</div>

親要素(#parent)に Transform が設定されたとき、#sticky 要素の位置は次の順序で計算できます。

  1. 親要素の Transform 適用後の bounding box を算出する
  2. #sticky を、まずは fixed だと思って配置する
  3. 1 の bonding box にすっぽり入るまで最短距離で移動する。 元から入っていれば何もしない。
  4. bounding box 内での座標を調べる
  5. #sticky を親要素内のその座標にあらためて配置する
  6. Transform を適用する

sticky 要素の位置算出方法

最後の (6) で Transform がかかるために、例えば上のように #parent に回転をかければ、スクロールしたときの「移動方向」を変えることができます。 拡大または縮小をかければ、「移動量」を変えることができます。 あとは #sticky にも逆の変形をかけると、あたかも別の方向に、別の速度でスクロールしているかのように見えるわけです。

(1) で bounding box の left/top (または right/bottom)が、変形前と変わらないように適当に平行移動してやると、(3) と (5) でのずらす処理を考えなくてよく、配置しやすいと思います。 sticker.scss はそうなっているので気になる方は試してみてください。

注意

以上に述べたことは現在の WebKit の実装ではこういうことができるというだけで、将来にわたって、また他のブラウザで(実装されたとしても)同じことができる保証はもちろんありません。 スクロールに反応して変なことができそうなので、ぜひこのまま残ってくれると嬉しいのですが。 めでたし、めでたし……

15 日目は

明日は ksk1015 さんです!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment