CSSだけでviewportに常に対角線を引く
- 公開日
- タグ
- CSS
画面に対角線をどうしても引きたい時が人生に一度や二度は来ると思う。
任意の角度で斜め線を引くなら linear-gradient()
で <color-stop>
を工夫したり、線だけの要素を transform: rotate()
するなど手法はいくつかある。しかし viewport の対角線においては閲覧環境によってその角度が異なるので、角度を動的に算出する必要がある。
一見望みがありそうな calc()
はどうだろうか。平方根や三角関数を直接求めることは 2016 年 4 月時点ではできないが、CSS Variables を利用し、計算結果を保存して別の calc()
に渡していけばできるかもしれない。
viewport を長方形と見ると、幅 100vw
で高さ 100vh
となる。これで斜辺 C の長さがわかれば、対角線の上側と下側の角度がラジアン単位で求められるので、linear-gradient()
の第一引数や rotate()
に指定すれば解決だ。
なのだが、前述した通り平方根を直接求めることはできない。平方根を逐次計算で求めていく方法もあるようだが、100vw
の二乗、100vh
の二乗とはいったいいくつなのだろうか? Computed Value では実数になっているけど、それは Computed Value での話だし、CSS Var で --num: calc(100vw * 100vw);
としても、乗算では引数は少なくとも1つが <number>
でなければならないのでエラーになる。
ということで、どう頑張ろうとも CSS だけで動的に角度を求めることはできない。
でも CSS でできるよ
できないのは「動的に角度を求めること」なので、違うアプローチで対角線を表現する。
具体的には、border
プロパティで三角形を作る方法を応用し、直角三角形を重ねて対角線を表現する。
Windows ユーザーの人は toggle menu ボタンを押すとスクロールバーが出てアレするが、それについては後述しているので一旦置いておいてほしい。
やってることを言葉で詳しく説明すると下記の通りだ。
100vw * 100vh
の大きさの要素を作る- その要素は上下の
border-width
が50vh
ずつ、左右のborder-width
が50vw
ずつである border-left
とborder-bottom
のみにborder-color
をつける(他はtransparent
)- 同じような要素をもう1つ重ねて、
top
とleft
から1px
ずつずらす - 上に重なっている要素の
border-color
は、コンテンツエリアの背景色と同じにする
右上から左下への対角線の場合は border-color
で色を指定する箇所を変えればよい。
内容物の幅と高さが共に 0
で、要素の大きさが border-width
分しかない場合、その border-width
分の大きさの要素が作られ、隣接する border
の間には色の境界線ができる。そして対角に位置する方の「border
同士の境界線」と直線で結ばれる。境界線の角度は要素の大きさで決まり、要素の大きさは border-width
の合計値で 100vw
と 100vh
になっているので、viewport に対角線が引かれているのと同義になる。ちょっとわかりやすくなるかもしれないサンプルも作った。ウィンドウサイズをぐいんぐいんすれば対角線の維持される様子がわかると思う。
デモでは画面に対して position: fixed
で固定配置しているので、スタック文脈の解決のためにコンテンツエリアも position: relative
する必要があるので注意。
ウィンドウサイズぐいんぐいんおじさんがデスクトップ版 Safari を使っている場合、「ウィンドウサイズをぐいんぐいんすると対角線が追随しないぞ」というバグ報告を絶対にしてくる。なので強制再描画ハックもつけた。これでウィンドウサイズぐいんぐいんおじさんも満足するはずだ。Firefox や Chrome では強制再描画しなくてもウィンドウサイズぐいんぐいんに対角線が追随するので偉い。
問題点
まずブラウザの CSS 対応的に Viewport Unit が必要というのが絶対条件になる。border-width
には <percentage>型
は許容されておらず、border-width: 50%
のような指定は使えないので Viewport Unit に対応していないブラウザでは諦めざるを得ない。
また、border-color
が単色しか指定できないので、コンテンツの背景も単色でなければならない。背景画像があるとかグラデーションを入れているとかだと対応できない。
そして一番の問題点は「スクロールバーで隠れる」ことと「Mobile Safari 9 の UI で隠れる」ことだ。
問題の前者は主に Windows 環境下での話。ページにスクロールバーが現れていると「見た目の画面幅」と 100vw
の結果が異なるというやつ。Windows でデモの toggle more のボタンを押せばコンテンツが増えてスクロールバーが出るので、どういうことかわかると思う。
問題の後者は iOS 環境下で、Mobile Safari 9 の UI(戻るボタンとかインテントボタンとかのやつ)が viewport にかぶさって「見た目の画面高」と 100vh
の結果が異なるというやつ。ロケーションバーの方ではなく画面の下側に出る方の UI が問題になる。ページをスクロールするとその UI は隠されて見た目と結果が揃うのでやっかい。Mobile Safari 9 に依存した問題なのでモバイル版の Google Chrome で閲覧すればこの問題は起きないが、それでいいのかと手斧が飛んできたら防げずに死ぬ。
ブラウザ UI を除いた表示領域の大きさを CSS だけでは動的に得られない。特に今回は対角線の描画に border
を使っているので、%
を指定できない点が痛い。
「CSS でできる」と言いながらも、できているのは OS X でだけである。それ以上でも以下でもなく、他のことを一切解決できないまま結局死ぬ。
CSS お絵描きの未来
Pure CSS でアートワーク的なことはこれまで様々なものが紹介されてきた。しかし本気でやろうとしたら JavaScript の補助は必要になってくる。今回のように閲覧環境やコンテンツの状況によって変わる値を必要とする場合はなおさらだ。
CSS の表現の幅の拡張で、その界隈では CSS Houdini に一瞬注目が集まった。でも結局中では JavaScript で実装を書くんだから、JS からは逃れられないのは変わらない。カスタムファンクションとして CSS だけで動いているように見えるだけだ。
CSS お絵描きに未来はあるか。あるとすれば高等数学をサポートした calc()
が登場するか、ブラウザ UI を考慮した表示領域を扱える単位かプロパティーが登場する時だろう。しかし CSS Houdini が策定されていくとなれば、そちらでやれということにしかならなそうな気もする。未来なさそう。