CSSの先頭に書くやつ
// Stylus * position relative
リセット系CSSと併用することを前提にしています。
使用するライブラリにbox-sizing
の設定がない場合は以下も追加します。
// Stylus :root box-sizing border-box * box-sizing inherit
box-sizing
に関してはsanitaze.css
を参考にしています。
Eric Meyer’s Reset CSS、normalize.cssなどにはbox-sizing
の設定は含まれていませんでした。
position
を設定しない時の問題
CSSのプロパティーの中にはposition
の値に依存しているものがいくつかあります。
position
の初期値はstatic
という値ですが、
MDNの説明では、
- static
規定の振る舞いです。フロー内の現在の位置に配置されます。top、right、bottom、left、z-index プロパティは適用されません。
MDN: position
とあります。
上記に加え、
overflow
scroll
、auto
に設定してもスクロールしないhidden
を設定してもはみ出した部分が表示される- つまりオーバーフローしていると見なされなくなる?
- 子要素の
width
、height
にパーセント指定static
に設定された要素は無視される- 親を辿って
static
以外に設定された要素の値から算出される
などの影響も受けます。
多分、他にもあると思います。
はっきり言って、あるプロパティーがうまく効かなかった時に、それがposition
のせいかどうか調べるのはめちゃくちゃしんどいです。
ていうかstatic
ってなんなんだほんとに。意味あんのか。
なので今は全ての要素にposition: relative
をあてています。
*
を使った指定は何かと問題も引き起こしやすいですが、このrelative
の指定に関してはメリットのほうが大きいかなと思います。
flexアイテム内のスクロールを有効にする
やりたいこと
display: flex
内の各要素(以下flexアイテム)は、自分の中のコンテンツに合わせて大きさが変わります*1。
そのため、コンテンツが画面からはみ出すような大きさの時はスクロール表示になって欲しいなーって時でも、そのままではスクロール表示になりません。
各flexアイテムの大きさはいい感じに分配されつつ、コンテンツがはみ出す時はスクロール表示になってほしいわけです。
試した結果これとほぼ同じ方法で出来ました。
やり方
flexアイテム内のコンテンツをラッパーで包みposition: absolute
にする*2。
// Jade .foo .bar .baz .qux ul.wrapper li: i.material-icons looks_one li: i.material-icons looks_two li: i.material-icons looks_3 li: i.material-icons looks_4 li: i.material-icons looks_5 li: i.material-icons looks_6
position: absolute
- looks_one
- looks_two
- looks_3
- looks_4
- looks_5
- looks_6
// Stylus @import 'nib' * position relative .foo size 300px display flex flex-flow column nowrap .bar, .baz, .qux flex 1 1 auto .bar background-color #3399ac .baz background-color #cc3a20 .qux background-color #bee overflow-y auto ul.wrapper absolute top 0 left 0 // ラッパーにposition: absoluteを適用する padding 10px
position: relative
- looks_one
- looks_two
- looks_3
- looks_4
- looks_5
- looks_6
// Stylus @import 'nib' * position relative .foo size 300px display flex flex-flow column nowrap .bar, .baz, .qux flex 1 1 auto .bar background-color #3399ac .baz background-color #cc3a20 .qux background-color #bee overflow-y auto ul.wrapper // position: absoluteの指定無し padding 10px
width、heightがautoの要素にtransitionを適用する
CSS3のtransition。
一般的なウェブアプリのUIで動きが必要になる時って、始点と終点がある程度決まっていて、animation
の@keyframe
を駆使するような動きは限定的なことが多いと思うのだけど。
このtransitionを使っていていつも悩ましいのが、width
やheight
がauto
に設定されている要素にtransition
を適用したい時です。
transition
を有効にする場合、始点と終点の値を100px
のように絶対値で指定するか、50%
(もちろんheightなどで親のautoを引継いでしまっている場合はダメ)のように相対値で指定することはできるけど、auto
だとtransition
が効かない。
// Stylus .foo width 200px height 0 transition height .2s &.opened height auto
上の例の場合、
// JavaScript document.querySelector('.foo').classList.add('opened');
のようにclass="opened"
になるとheight
の値は0
からauto
に変わるが、transition
による効果は適用されない。
同様に、
// JavaScript document.querySelector('.foo').classList.remove('opened');
のようにopened
を取り去ると、height
の値はauto
から0
に変わるが、この場合もtransition
の効果は発生しない。
ドロップダウンメニューや開閉可能なペインのように、コンテンツの内容に応じてリサイズしたい場合に困る。
これを解決する良い方法はまだよく分からないのですが、実際に試してみた幾つかのアプローチをメモしておきます。
基本方針
JavaScriptによる操作は最低限にしたい。
class
の付け外しなどにとどめ、インラインスタイルの値を直接いじるのは最終手段にしたい。
auto
を使わない
いきなり解決して無いですが、サイズを明示的に指定できる場合は極力指定する、という方法。
例えば、(折り返しを許可しない)一行だけのインラインが含まれるような要素の場合line-height
+padding-top
+padding-bottom
でheight
を指定できる。
インラインが取り得る文字数の範囲が予測できる場合は、width
の値も明示できる可能性がある。
// Stylus .foo line-height 16px padding 6px 0 width 200px height 0 transition height .2s &.opened height @line-height+@padding-top*2
20px
のような絶対値の指定でなくても、80vh
、calc(50% + 20px)
のような指定であればtransition
は効くので、それらで解決できないかも検討してみる。
max-height
、max-width
を使用する
height
、width
の代わりにmax-height
、max-width
を使用する方法。
transition-property
にはmax-height
、max-width
を指定する。
height
の場合
以下のようにmax-height
を100vh
のように指定しておくと、コンテンツの高さに合わせて.foo
の高さが決まる(ただしコンテンツが100vh
以上の場合は100vh
になる)。
// Stylus .foo width 200px max-height 0 transition max-height .2s &.opened max-height 100vh
width
の場合
display: block
の場合、width
は親要素いっぱいに広がろうとするので、コンテンツのサイズに合わせたい場合はdisplay: inline-block
かdisplay: flex
にする必要がある。
// Stylus .foo display inline-block max-width 0 height 500px transition max-width .2s &.opened max-width 100%
ただし、以下のようなデメリットがある為、きめ細く動きを調整したい場合には向いていない。
transition
の始点、終点はmax-height
、max-width
の値で計算される- そのため、実際の高さ、幅と
max-height
、max-width
の差が大きいほどtransition
の効果は不自然になる- 基本的に設定した
transition-duration
より短くなる(早くtransition
が終了する) transition-timing-function
もheight
、width
の始点、終点とズレるので綺麗に適用されない- サイズが小さくなる動きの場合、クリックなどのユーザアクションに対して反応が遅れる
- 基本的に設定した
- そのため、実際の高さ、幅と
簡単なので間に合わせでとりあえず動きを付けたいという時は良いかも知れない。
JavaScriptでコンテンツサイズを調べる
getComputedStyle
、getBoudingClientRect
などでコンテンツのサイズを調べる方法。
ここではgetBoundingClientRect
を使うようにする。(getComputedStyle
だとauto
の値がうまく取れなかった事があったはずだけどうまく再現できず)
ラッパー要素を用意する
コンテンツ全体を包む要素を用意し、position: absolute
を適用する。
この要素のheight
、width
にauto
を指定して、ドロップボックスがエキスパンドした状態などを維持させておき、常にコンテンツサイズに合わせておく。
このラッパー要素からgetBoundingClientRect
でサイズを取得してリサイズする要素に適用する。
今のところこの方法が一番良さそうに感じる。
auto
の値を正しく得るために、外側のheight
、width
が固定値の場合はそれをラッパー要素にも適用する。
// Stylus .foo width 200px height 0 transition height .2s .bar position absolute width @width height auto
// JavaScript const foo = document.querySelector('.foo'); const bar = document.querySelector('.bar'); document.documentElement.addEventListener('click', e => { if(foo.classList.contains('opened')) { foo.classList.remove('opened'); foo.style.height = ''; } else { const contentHeight = bar.getBoundingClientRect().height; foo.style.height = `${contentHeight}px`; foo.classList.add('opened'); } });
唯一の欠点は、ドロップダウンメニューなどが開閉する際、コンテンツ自体はサイズが伸び縮みしないので、もしそういう風に動きを付けたい場合は、もう一手間必要になる、という点。
個人的な感覚では、コンテンツのサイズを伸び縮みさせたくないことのほうが多い(トランジションの途中で文字の折り返し部分が変わっていったりするとキモい)ので、あまり困らないかなと思う。
transition
を一旦無効にしてサイズを調べる方法
やってみたけど、ボツにした方法。
あえて利点を挙げるとすれば、コンテンツがテキストノードだけの場合でも使えるので、ラッパー要素を用意する必要が無い。
ブラウザ間の誤差を気持ち悪いハックで誤魔化しているので、いつ動かなくなっても驚かない。
使わないほうが良い。
- インラインスタイルで
transition
にnone
を設定 height
、width
にauto
を適用getBoundingClientRect
でheight
、width
を調べるheight
、width
を元の値に戻すtransition
を元の値に戻すsetTimeout
で非同期にheight
、width
に適用する
FireFox、ChromeではsetTimeout
で非同期に適用しないとtransition
が効かない、またChromeでは第二引数を0
にすると効かないことがある。
Safariでは同期処理中にもチラつきがある。
また、元の値を0
以外にすると、auto
の値を適用した時に、一度0
になってしまう。
// Stylus .foo width 200px height 0 transition height .2s
// JavaScript const foo = document.querySelector('.foo'); document.documentElement.addEventListener('click', e => { if(foo.classList.contains('opened')) { foo.classList.remove('opened'); foo.style.height = ''; } else { foo.style.transition = 'none'; foo.style.height = 'auto'; const contentHeight = foo.getBoundingClientRect().height; foo.style.height = ''; foo.style.transition = ''; setTimeout(() => { foo.style.height = `${contentHeight}px`; foo.classList.add('opened'); }, 30); } });
今日の結論
height
、width
にauto
が設定してある要素にtransition
を適用したい場合は、ラッパー要素を用意してJavaScriptでサイズを取得するのが良いと思う。
CSSだけだと厳しそうなので、JavaScriptで少しカバーしてあげるとバランスが良い。