Web制作の現場で使える最強ディスプレイ

主にフロントエンドエンジニアやウェブデザイナーはどんなディスプレイを使うべきなのか、改めて今という時代を鑑みて考えてみた。

今使っているディスプレイ

今、弊社ではLGの24UD58-Bを人数分買って各自で使っている。

物理的なサイズや解像度は満足しているし、高級なリファレンスモニターと見比べたこともないので色に関しても劣っていると感じたことはない。

このスペックで3万円程度で購入できるのであればとてもいい製品だと思っている。

ユーザーの使っているディスプレイ

今の時代、普通の人はみんなスマホを持っている。

調べていくうちに、スマホのディスプレイは実は制作現場で使っているディスプレイより遥かにハイスペックであることがわかってきた。

制作現場よりエンドユーザーのほうがスペックが高い時代なんてあっただろうか?今の状況おかしくないか?

どう考えても良くないだろう。

制作現場で使える最強のディスプレイを探す旅に出ることにした。

高画質とは

そもそも良いディスプレイとは何なのかを知る必要がある。

こちらの高画質についての解説を見るとわかりやすいのだが、
https://www.eizo.co.jp/eizolibrary/color_management/hdr/

大きく分けて5つのスペックで画質の良し悪しが決まるという。

  • 解像度
  • ビット深度
  • フレームレート(リフレッシュレート)
  • 色域
  • 輝度

ディスプレイのトレンド

現状のハイエンドに位置するスマホは、以下のようなスペックが主流だ。

  • Retinaなどの高解像度ディスプレイ
  • 10億色表示可能
  • 90Hzや120Hzなど高リフレッシュレート
  • Display P3 色域対応
  • 有機EL

弊社で使っているディスプレイは解像度と色深度こそ高いものの、それ意外は完全にスマホに劣っている。

MacbookProのディスプレイでさえ、P3に対応しているくらいで、有機ELで高リフレッシュレートなスマホには劣っていると言わざるを得ない。

制作現場として必要な要件

5つのスペック項目ごとに要求スペックを決めていく。

1. 解像度

4K がほしい。

スマホは解像度こそ高いがドットバイドットでコンテンツを表示することはあまりないので、作業スペースという意味ではそれほど広いものは必要ではない。
ただ、やはりスマホユーザーがRetina前提なので制作現場がRetina相当でないのはおかしいと思う。

また、一般的なPCユーザーはフルHDが主流だとすると、フルHDが作業スペースでは狭いだろう。
2K程度の解像度をドットバイドット以上で表示できるディスプレイが必要なら、やはり最低でも4Kはほしい。

ドットバイドットで表示する時代はとっくの昔に終わったのだ。

2. ビット深度

できれば True 10bit がいい。
妥協して8bit+FRCでもいいかもしれない。

最近のディスプレイは10億色を謳っているものがほとんどなので、少なくとも8bit+FRCではあるみたい。

3. リフレッシュレート

120Hz 程度はほしいところ。
iPad Pro シリーズがProMotionという名前で120Hzディスプレイを搭載しているので、これを基準としたい。
Pixelを始めとするAndroid機も高リフレッシュレートなデバイスがスタンダードになりつつある。
おそらく遠くない未来でiPhoneシリーズにもProMotionが搭載されるだろう。

Webでアニメーションを作る際に、一般的に60fpsを維持すると良いとされているが、今後その基準を120fpsに引き上げて作っていきたい。

4. 色域

Apple様が提唱する Display P3 への対応。

AppleがsRGBを置き換える次世代の規格として推してるの色域なので、iPhoneからMacbookまで、Apple製品はすべて対応を謳っている。
Androidもこれに追従する形でOS側が対応を始めたので、デバイスもP3対応機種が増えつつある。

100%は難しいとしても、最低でも95%以上でなるべく高い数値だととても良い。
価格との兼ね合いになりそう。

5. 輝度

ダイナミックレンジが大きいに越したことはないが、Web制作の現場では、それほど重要ではないかなと思っている。
今回は 一般的な値であれば十分 という要件にしよう。

黒の表現という意味では、現状スマホのスタンダードになっている有機ELに勝るものはないので、可能なら制作環境も有機ELにしたいのだが、大きい有機ELディスプレイは量産されておらず、現実的に買える値段で売っていないので諦めざるを得ない。
今年に入ってようやくLGから発表された有機ELのPC用ディスプレイが唯一選択肢になりえるかもしれない、というレベル。
マイクロLEDなどの新しいアプローチの製品も考案されつつあるので、もう少し手に入れるには時間が必要のよう。

製品を探す

要求スペックをまとめるとこうなる。

  • 解像度:4K
  • ビット深度:10bit
  • リフレッシュレート:120Hz
  • 色域:P3 95%

これを基準に、ジャンル別にディスプレイを探してみた。

リファレンスモニター

リファレンスモニターと呼ばれるEIZOやSONYの高価なディスプレイは正しい色を表示することに関して非常に長けていて、これはこれで価値のあるものだと思うのだけど、Web制作の現場ではオーバースペックか。

  • 色域すごい
  • キャリブレーション機能がついていたりする
  • リフレッシュレートは普通
  • 高価

映像や写真のクリエイター向けに作られていることが多く、高価な割には高リフレッシュレートな製品は作られていないみたい。

ゲーミングモニター

一般的なゲーミングモニターと呼ばれるジャンルのディスプレイの特徴は、

  • 応答速度が早い
  • リフレッシュレートが高い
  • フルHD解像度
  • 色域は非公開(よいものではない)

リフレッシュレートが高いのは良いことだが、解像度がフルHDに抑えられているのは必要とされていないからだろう。
リアルタイムレンダリングを主とするゲーム用途では高リフレッシュレートな4Kは出力側のマシンスペック的にまだまだ難しい。
具体的に言うとPS5レベルの次世代ハードウェアが必要になる。
今後ハードウェアに合わせてよりハイスペックな製品が登場するのだろう。

応答速度は制作現場にはあまり重要ではない。

どうやら需要がないらしい

リファレンスモニターやゲーミングモニターを調べてみて感じたのは、要求スペックに合う方向性の製品がほぼ存在しないということ。

映像制作現場では高リフレッシュレートは不要だし、ゲーミングモニターでは高解像度と広色域は不要なのだ。

見つけた理想のディスプレイ

Eve Spectrum

クラウドファンディングで資金調達したフィンランドのeveというスタートアップの製品。

  • 4K
  • 8bit+FRC
  • 144Hz
  • P3 98%
  • DisplayHDR 600

色深度こそなんちゃって10bitだが、それ以外は完璧に要件を満たしている夢のような製品。
価格も709ドルと、AppleのPro Display XDRやDELLのUP2720Qなんかと比べても圧倒的に安い。

さっそくポチってみた。
3月頃に注文したら4月末に届くらしいが、この会社は以前の製品でとんでもない遅延をしたらしいので、気長に待つことにする。

もし無事に届いて気が向いたらレビューでもしてみようと思います。

ちなみに

後で発見したのだが、EveのSpectrumは、おそらくLGの27GN950-Bと同じパネルなのであろう。

LG 27GN950-B

スペックがほぼ同じだ。

ただし、LGのほうが価格がちょっと高く、背面がレインボーに光る。
個人的には多少のリスクを抱えてもSpectrumを選んだが、好みにあうのであればLGを選んでもいい買い物だと思う。

CSSでフォントサイズの変数名にSI接頭辞を使ってみたらどうだろう

これは今に始まったことじゃないですが、JSにしろCSSにしろ、変数名ってめちゃくちゃ悩むんですよね。

今日はCSSのフォントサイズを変数化する際にSI接頭辞を使ってみたらどうだろうか、という提案をさせていただきたい。

フォントサイズの値の特徴として、下記のようなものがあると思います

  • 数値型
  • 基準となる値があり、それより小さいものと大きいものに分けられる
  • 値そのものより、他の値より大きいのか小さいのかが重要(イメージできるので)
  • せいぜい5〜8種類程度になるはず

要するに 数値型で相対的な大きさがなんとなく想像できるような変数名 だと良い変数だと思うわけです。

そこでこちらをご覧ください

$font-size-micro: 12px;
$font-size-milli: 14px;
$font-size-base: 16px;
$font-size-kilo: 18px;
$font-size-mega: 24px;
$font-size-giga: 32px;

基準である16pxをベースに、それより小さいものをミリやマイクロ、大きいものをキロ・メガ・ギガとすることで、相対的な大きさを想像することができる変数名とすることができたと思います

試しにこの変数名を、あえて分かりにくいセレクタで使ってみるとこんな感じになります。

.headline {
  font-size: $font-size-kilo;
}
.date {
  font-size: $font-size-milli;
}
.value {
  font-size: $font-size-giga;
}

フォントサイズの大小が明確になるだけで、随分と要素の重要度がCSSを見ただけで伝わるのではないかと思うわけです。

しかも、この程度のSI接頭辞ならエンジニアではなくても、なんなら小学生でも直感的にわかるという嬉しさ。

中間サイズの追加に弱いという欠点はあるものの、デザインシステムがカチッとしている状況であれば、十分使えるのではないでしょうか。


ちなみに、これを考えているときに思いついた別案ですが、むしろ日本語で曖昧な変数名作っちゃえばもっとわかりやすくて汎用的になるのではと思ったりしました

$font-size-結構小さい: 12px;
$font-size-ちょっと小さい: 14px;
$font-size-基準: 16px;
$font-size-ちょっとデカい: 18px;
$font-size-まあまあデカい: 24px;
$font-size-相当なデカさ: 32px;
$font-size-アホほどデカい: 64px;

これなら中間サイズが追加されても表現次第でどうにでもなるので最強です。優れたエディタなら補完もしてくれるので表記ゆれなんて気にしなくて良い。日本語すごい!

SVG要素でaltの代わりになるもの

SVGを表示するにはいくつか方法があります。

pngやjpgと同じように <img> を使って表示する場合はいつも通り代替テキストとしてalt属性を使えばよいのですが、SVGスプライトや vue-svg-loader を使うなどで <svg> をHTMLにインラインで書き出したい場合もあると思います。

<svg> には代替テキストの機能は存在しないので、代わりとなる機能がないか探してみました

<title> を使う方法

SVGファイル内に <title> を入れておくことで、マシンリーダブルにすることができます。

ただしこの方法には、

  • カーソルを合わせるとツールチップが出てしまう
  • .svg ファイル内に存在することになるので、管理がしづらい

などの取り回ししづらい部分があり、imgのaltと比べてもなんかいまいち。

WAI-ARIAを使う方法

そこでおすすめしたいのがこちら

<svg> に対して role="img" aria-label="テキスト" を付ける方法です

<svg role="img" aria-label="画像の説明" ...>
  ...
</svg>

この方法は MDNのimgロールのページ でも言及されていました。

ARIA属性は、付与できる要素に制限がないため、例えば複数の画像をまとめてひとつのものとして認識してもらいたい場合などにも使うことが出来ます。

<div role="img" aria-label="画像の説明">
  <svg ...>
    ...
  </svg>
  <svg ...>
    ...
  </svg>
</div>

<title> を使う方法と比較して管理のしやすさや組み込み方自由度が高く、非常に有用だと思います

文字を思い通りの大きさと位置で表示するCSS

基本的に文字というものは、大文字・小文字など文字の種類によって目に見える大きさ(高さ)が異なります。

例えばCSSで font-size を100pxに指定した場合でも、文字がピッタリ100pxで表示されるわけではありません。

これをCSSで思い通りに指定する方法を調べてみました。

(欧文フォントの場合)

欧文フォントの基礎知識

アルファベットでは、文字の下端( y などの下に飛び出る小文字を除く)となるベースラインを基準に、デザイン上の指標となる高さがいくつか存在します。

  • アセンダ:ベースラインから bd など小文字で上に飛び出る部分の上端までの距離
  • ディセンダ:ベースラインから jy など下に飛び出る文字の下端までの距離
  • エックスハイト:ベースラインから x など、小文字の上端までの距離
  • キャップハイト:ベースラインから大文字の上端までの距離。アセンダより少し下に位置することが多い

※こちらの図がわかりやすいです http://w3.kcua.ac.jp/~fujiwara/infosci/font.html

例えば Arial のメタデータを解析してみると、下記の値を発見できます

  • ascender: 1854
  • descender: 434
  • sxHeight: 1062
  • sCapHeight: 1467

数字の大小はさておき、これらの値でまず意識しなければならないのは単位です。

実はこの数字、単位はピクセルでもミリメートルでもありません。
各フォントが em-square という基準値を決め、それに対する相対値として定義されています。

これもメタデータを見てみると発見できます

  • unitsPerEm: 2048

要するに Arial の場合は、2048を基準に、それよりどのくらい大きいのか、小さいのか、という相対的な数字なのです

文字サイズ16pxのxの大きさ

そしてこの em-square こそがCSSの font-size と直接関わってくる部分。

例えば font-size: 16px; とする場合、xの高さは

var xHeight = (1062 / 2048) * 16px

約8.3pxとなります。

また、逆算的にキャップハイトが100pxになるためのfont-sizeは

var fontSize = 100px / (1467 / 2048)

約139.6pxになります。

font-size は、実は スケール感をふんわり指定しているだけ で、必ずしもその大きさで文字が表示されるわけではない、ということが分かって頂けたかと思います。

文字の表示位置とline-height

ここまでで表示したい大きさのfont-sizeは見いだせるようになったと思いますが、実際にこれをウェブのレイアウトで使うためには、表示位置も制御できねば意味がありません。

em-squareascender などの値はあくまで距離の数値なので、そのまま位置算出に使うことはできなさそうです。

CSSでインライン要素の表示位置といえば vertical-align ですが、これはベースラインを揃えたりなど、わりとアバウトな配置の指示しかできませんし、 line-height の設定値次第で文字の表示位置は大きく変わります。

line-height に関して言えば、実はデフォルト値である normal は、下記の計算式で算出されているみたいです

var lineHeight = (ascender + descender + lineGap) / unitsPerEm

lineGap は、他の値と同じようにフォントのメタデータとして記録されていて、例えばArialは 67 です。フォントによっては0なこともあります。

ascender + descender が文字の表示しうる範囲ですが、lineGapはその半分の数値がascenderの上とdescenderの下に割り振られるような表示になります。

上記の式をArialのメタデータで計算してみると、結果は 1.1499 になります。約1.15です。

実際に下記のようなCSSを使ってp要素の高さをDevToolsで見てみると、115pxになってるのが確認できるはずです。

p {
  font-family: 'Arial';
  font-size: 100px;
  line-height: normal;
}

ということは、 line-height: normal を指定してJSで高さを取得し、他のメタデータからBaselineの位置を算出することで最終的にはCapHeightやxHeightなどの表示位置も計算できるはずです。

var baselineY = lineHeight - (lineGap / 2 + descender) / unitsPerEm
var xHeightY = baselineY - sxHeight / unitsPerEm
var capHeightY = baselineY - sCapHeight / unitsPerEm

結論は、CSSでフォントを深く掘るのはつらいということです

理屈に合うイージングまとめ

人間は今まで経験してきた物理現象と同じ動きをするアニメーションを「自然な気持ちよさ」として感じることができるみたいです。

そのUIアニメーションが何を表しているのか?によって、なるべく理屈に合うイージングを選択することで、気持ちの良いアニメーションを作ることができるんじゃないか、と最近思うわけです。

私はこれまでイージングは「Quadだと遅いからQuartにしてみようかな〜」のようなアバウトな実装をしてました。

しかしこの方法では、実装工程で何度も動きを見ているうちにゲシュタルト崩壊して気持ちの良いアニメーションが何なのか分からなくなってきます。
しかも、そもそもQuartの速度を下げてもQuadのグラフとは一致しません。物理現象とは無関係なアニメーションになってしまうのです。

実世界の環境を忠実に再現することができない以上、物理現象と全く同じ動きを実装することは不可能ですが、理屈ありきの実装をすればある程度近い動きにはなるはずです。

また、主観的な決めつけで実装する必要がなくなるので、万人受けしやすくなります。ゲシュタルト崩壊して妙にクドいアニメーションになってしまうこともありません。

そういうことで、物理現象ごとに理屈に合うイージングをまとめてみました。

(半分ギャグです)

自由落下

モノが上から降ってくるような演出の場合。
これは一定の加速度で速度を上げ続ける運動(等加速度直線運動)なので、移動距離は経過時間の2乗に比例します。
(空気抵抗は無視)

当てはまるイージングは easeInQuad です。

Quadだと動きが鈍くて気持ちよくないと感じることが多いですが、Quadの計算式は単純に時間の2乗しか使っておらず、重力加速度が加味されていませんので、そのへんはdurationで調節してあげます。

どういうことかというと、動かす要素が実世界でどのくらいの大きさで、どのくらい離れて観測しているのかを考えます。
エヴァの空から降ってくる使徒サハクィエルは、随分ゆっくり降りてくるように感じますね。
あれはドでかいものを遠くから観測しているからです。

具体的には、落下距離を決めてそれをピクセル換算できれば、下記計算式でdurationを求められます。
(重力加速度は9.8としてます)

[時間] = √([距離] / 4.9)

CSSプリプロセッサでMixinでも作ったら捗るかもしれません。

スライドイン

例えば、ハンバーガーメニューをクリックして画面外からオーバーレイ要素が滑り込んでくるようなアニメーションの場合。

摩擦によって常に加速度がマイナス方向にかかっていると考えられるので、自由落下の逆で easeOutQuad を使います。
(速度によって摩擦係数は変わってくるらしいが、その辺は無視)

durationの算出にも自由落下と同じ式を使えますが、スケール感に加えて加速度(摩擦係数)もイメージする必要があります。
これには物体と接地面の素材を想像して、摩擦がどの程度あるかをテキトーに仮定します。

[時間] = √([距離] / ([摩擦係数] / 2))

動摩擦係数でググるとけっこう見つかるので参考にしましょう

徐々に消える、フワッと色が変わる

これもよく使うアニメーションです。hoverでボタンの色を変えたりとか。

色が変わる遷移をミクロな世界で物理的に説明してみると、たぶん分子レベルで特性が変わっていくんじゃないかなと思います。

そんなときはこれ。放射性同位体の原子数の時間的変化の式。
放射性物質の半減期を計算するときに使うような式です。

原子的に不安定な状態である放射性物質は、放射線の放出とともに原子の崩壊をして、いずれは安定した原子に変化していきます。
毎秒10%ずつ変化する原子が2000個あると、1秒後には残り1800個、2秒後には1620個、というように。
こちらの説明が分かりやすいです)

このような遷移を表すイージングは easeOutExpo
(出典はこちら

イージング関数を最初に作った人はやっぱりちゃんと理屈ありきで作ってたんだなあと関心しますね。

durationは半減期一覧を参考に近しい値を計算します。

と言いたいところですが、比較対象の想像ができる人なんてまず居ないと思うので適当な値でイイ感じにしてください。

もはや主観入ってますが、難しいので諦めました。

事務所を移転しました

10月から渋谷区千駄ヶ谷に事務所機能を移転しました。
https://nasbi.jp/about

これまで事務所機能は菅沼の自宅に置き、作業自体は完全リモートで各自の自宅から作業していましたが、主に下記3点の理由によりちゃんと事務所を構えることにしました。

  • 営業が安定してきた
  • 集まって仕事をすることで得られる知識もある
  • 信頼度アップ

これからもリモートで仕事してたりすることも多くあるとは思いますが、基本的に余裕のある日は出社して、パツってるときは自宅作業という分け方になるんじゃないかなと思っています。

業務自体はこれまでと何ら変わりませんので、関係者様に於かれましては、引き続きよろしくお願いいたします。

スマートにiPhoneXの対応のためのpaddingを設定する

iPhoneXには、画面上部にセンサーや前面カメラが仕込まれているディスプレイの切り欠きがあります。

safariではviewportを下記のように指定することで、横持ちした際にこの切り欠きの周りまでレンダリング領域とすることができます。

<meta name="viewport" content="viewport-fit=cover">

この指定をすると、ディスプレイ全体に背景色等を引き伸ばせるため、より一体感のあるデザインにすることができるのですが、切り欠き部分がコンテンツと重なってしまうことが起こり得るため、CSS定数を使ってpadding等を余分に持たせてあげることで、これを回避してあげる必要があります。

設定できるCSS定数は以下の通り

constant(safe-area-inset-top)
constant(safe-area-inset-right)
constant(safe-area-inset-bottom)
constant(safe-area-inset-left)

しかし、このCSS定数で使われる constant 関数ですが、他のブラウザでは一切実装されていない機能になります。
そのため、既にpaddingが設定されている要素にcalc等を使って切り欠き分をプラスしてあげる方法だと、構文解釈ができずにpaddingが未設定とされてしまいます。
とはいえわざわざiPhoneXのために新しい要素で囲ってあげるのもバカらしい…。

そんなときは下記の様に同じセレクタ内でpaddingを複数記述するとスマートに解決することができます。

.container {
	padding: 10px 20px;
	padding-right: calc(20px + constant(safe-area-inset-right)); // for iPhone X
	padding-left: calc(20px + constant(safe-area-inset-left)); // for iPhone X
}

safariでは padding-rightpadding-left を上書きするように後述しているので、切り欠き分も余分に余白が適用されますが、それ以外のブラウザでは constant を使っている行は解釈されないので1行目のみ適用されます。

要は記述順が重要で、ベンダープレフィックスのような用法で使ってあげればよいのです。

iOS10のSafariではVideo要素のインライン再生が可能になった

iOS9までのSafariでは、基本的にvideoのインライン再生はできず、再生すると強制的にフルスクリーンになっていました。

iOS10からはvideo要素にplaysinline属性をつけてあげることでインライン再生が可能になってます。

<video src="path/to/video.mp4" controls playsinline></video>

※ただしiOS9でもWebViewでは webkit-playsinline でインライン再生できました。

そしてこの仕様に併せて、JSからvideoのフルスクリーン制御もできるようになってます。

const video = document.querySelector('video');

// フルスクリーンにする
if(video.webkitSupportsFullscreen){
  video.webkitEnterFullscreen();
}

// フルスクリーンから離脱する
if(video.webkitSupportsFullscreen){
  video.webkitExitFullscreen();
}

video以外の要素で webkitRequestFullscreen を使ってフルスクリーンするのはまだiOSでは解禁されていませんが、videoのみ webkitEnterFullscreen でフルスクリーンできるようになった、ということです

SSRしないNuxt.jsでページロード時に非同期部分を更新する方法

Nuxt.jsでは、外部サーバ等から非同期でデータを取得・表示する場合にも asyncData を使ってあげることでクライアントサイドはもちろん、サーバサイドでもデータを取得し、HTMLで出力することが出来ます。

SSRできる本番環境があれば何の問題もないのですが、SSRせずプリレンダリングのみで運用する場合には、 asyncData は静的ファイルをgenerateした時点でしか走っておらず、ページロード時にデータが更新されていない問題が起こり得ます。

例えばコーポレートサイト等で、別のサーバにWordPressが設置してあり、トップページのお知らせとしてWordPressから記事を取得するような場合を想定すると、こんな感じで作ってあげることで解決できます

// 非同期でデータを取ってくる関数
async function getPostData() {
  return { data } = await axios.get('https://blog.example.com/wp-json/wp/v2/posts');
}

export default {
  async asyncData(context) {
    const obj = {};
    if (context.isServer) { // asyncDataするのはサーバサイド(プリレンダリング時)のみ
      obj.posts = await getPostData();
    }
    return obj;
  },
  async created() {
    if (!this.$isServer) { // クライアントサイドはここで更新をかける
      this.posts = await getPostData();
    }
  },
};

要は asyncData を使うのはサーバサイド(プリレンダリング時)のみ。
クライアントサイドでは created でデータの更新をかけてあげるわけです。

こうすることでプリレンダリング運用でも、一応(少し古いかもしれないけど)非同期データのHTMLもプリレンダリングされていて、ブラウザで訪れた際にはちゃんと最新の情報に更新されます。