CSSだけでviewportに常に対角線を引く

画面に対角線をどうしても引きたい時が人生に一度や二度は来ると思う。

任意の角度で斜め線を引くなら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プロパティで三角形を作る方法を応用し、直角三角形を重ねて対角線を表現する。

CodePen にデモを作った。

Windows ユーザーの人は toggle menu ボタンを押すとスクロールバーが出てアレするが、それについては後述しているので一旦置いておいてほしい。

やってることを言葉で詳しく説明すると、

  • 100vw * 100vhの大きさの要素を作る
  • その要素は上下のborder-width50vhずつ、左右のborder-width50vwずつである
  • border-leftborder-bottomのみにborder-colorをつける(他はtransparent
  • 同じような要素をもう一つ重ねて、topleftから1pxずつずらす
  • 上に重なっている要素のborder-colorは、コンテンツエリアの背景色と同じにする

となる。右上から左下への対角線の場合はborder-colorで色を指定する箇所を変えればよい。

重なりのイメージ

内容物の幅と高さが共に0で、要素の大きさがborder-width分しかない場合、そのborder-width分の大きさの要素が作られ、隣接するborderの間には色の境界線ができる。そして対角に位置する方の「border同士の境界線」と直線で結ばれる。境界線の角度は要素の大きさで決まり、要素の大きさはborder-widthの合計値で100vw100vhになっているので、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 のボタンを押せばコンテンツが増えてスクロールバーが出るので、どういうことかわかると思う。

Edgeでスクロールバーが表示されていない場合

Edgeでスクロールバーが表示されている場合

問題の後者は iOS 環境下で、Mobile Safari 9 の UI(戻るボタンとかインテントボタンとかのやつ)が viewport にかぶさって「見た目の画面高」と100vhの結果が異なるというやつ。ロケーションバーの方ではなく画面の下側に出る方の UI が問題になる。ページをスクロールするとその UI は隠されて見た目と結果が揃うのでやっかい。Mobile Safari 9 に依存した問題なのでモバイル版の Google Chrome で閲覧すればこの問題は起きないが、それでいいのかと手斧が飛んできたら防げずに死ぬ。

Mobile SafariでUIが表示されていない場合 Mobile SafariでUIが表示されている場合

スクロールバーにしろボタンエリアにしろ、ブラウザ UI を除いた表示領域の大きさを CSS だけで動的に得るには限界がある。特に今回は対角線の描画にborderを使っているので、%指定ができない点が痛い。

「CSS でできる」と言いながらも、できているのは OS X でだけである。それ以上でも以下でもなく、他のことを一切解決できないまま結局死ぬ。


CSS お絵描きの未来

Pure CSS でアートワーク的なことはこれまで様々なものが紹介されてきた。しかし本気でやろうとしたら JavaScript の補助は必要になってくる。今回のように閲覧環境やコンテンツの状況によって変わる値を必要とする場合はなおさらだ。

CSS の表現の幅の拡張で、その界隈では CSS Houdini に一瞬注目が集まったような気がする。でも結局中では JavaScript で実装を書くんだから、JS からは逃れられないのは変わらない。カスタムファンクションとして CSS だけで動いているように見えるだけだ。

CSS お絵描きに未来はあるか。あるとすれば高等数学をサポートしたcalc()が登場するか、ブラウザ UI を考慮した表示領域を扱える単位かプロパティーが登場する時だろう。しかし CSS Houdini が策定されていくとなれば、そちらでやれということにしかならなそうな気もする。未来なさそう。