TypeScript+Rollupでビルドが終わらない

RollupでTypeScriptのビルド設定を書いていたら、なぜかrollupのビルドコマンドがいつまで経っても終わらず、ctrl+cせざるをえない状態になり、原因調査に時間がかかったので備忘メモ

症状としてはこんな感じ

ビルド自体は終わっている(標準出力)のに、プロンプトが返ってこない

エラーも何も出ないのでなんの情報もなく、途方に暮れた挙げ句は神に祈りながらMacの再起動までしてみたけどダメでした 🤷‍♂️

どうやらtypescriptの4.4.xにバグがあるみたいで、4.3.xや4.5.0のデイリービルドなら起きないようだ。

すでに PRが作られて おり、4.4.3でリリースされるみたいなのでもう少し待ってみるか

sass-loaderとdart-sassにまつわるfibersの話

tl;dr

用語について

現在npmで sass として公開されているパッケージはdart製のsassとなっています。旧来使われていたnode製のsassは node-sass として公開されています。

ここでは区別を分かりやすくするため、dart製のsassを dart-sass、 node製のsassを node-sass と呼び分けています。

本文

そもそもsass-loaderで何を設定しているのでしょうか。sass-loaderでdart-sassを利用する際はだいたい下記のように設定しているかと思います。

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        // ...
        use: [
          `// ...
          {
            loader: `sass-loader`,
            options: {
              implementation: require('sass'), // ここで `dart-sass` を読み込んでいる
              sassOptions: {
                fiber: require(`fibers`), // 大体の人がなんとなく一緒に読み込んでいる
              },
            },
          },
        ],
      },
    ],
  },
  // ...
};

まず implementation プロパティについてはsass-loaderのドキュメント に書いてあるとおりですが簡単にまとめると

  • implementation を設定しない場合、 node-sassとdart-sassの両方インストールされている場合、自動的に後者が読み込まれる
  • implementation を設定することで明示することができる

の2点かとおもいます。この「両方がインストールされている場合」というのは、プロジェクト内の package.jsondevDependencies などに追加されている場合以外にも他のパッケージの依存に入っている場合も含まれるので、基本的には明示したほうが良いでしょう。

では、なぜ dart-sass を利用する場合に fiber プロパティにfibersパッケージを指定しているのでしょうか。

node-sassにもdart-sassにも render()renderSync() というAPIがあります。これはどちらもscssからcssへコンパイルするための関数ですが、前者が非同期に実行され、後者は同期的に実行されます。sass-loaderでは render() が利用され非同期的にscssファイルがコンパイルされることになります。

しかし、 dart-sassでは非同期にに実行する render() が非同期実行のオーバーヘッドのため renderSync() より2倍近く遅くなってしまいます。これを避けるために、利用されるのがfibresです。

fibersはもともとnode.jsに非同期に関数を実行する仕組み(Promiseやasnyc/await)がなかった頃に開発されたものです。同期関数を非同期で実行することができる Promise に相当する機能があります。(他にもGeneratorに相当する機能もありますがここでは割愛します。)

速度の早い renderSync() 関数を非同期でするために、このfibersを利用する設定が前述した webpack.config.js です。ただしfibersは作者により使用を避けるべきとされており、node 16への対応もされていません。

まとめ

つまり記事の冒頭でまとめたように、 node 16 を利用するのであればfibersを利用することはできず、コンパイル速度の低下を受け入れざるを得ません。sass-loaderではdart-sassの場合に renderSync() を利用するIssueが立っているのでもしかするとsass-loader側で対応がなされるかもしれません。

ディスプレイを接続していないWindowsにChromeRemoteDesktopで接続すると重くなる場合はHDMIダミープラグを挿すと良い

自宅のWindows環境はファイルサーバ・DLNAサーバとして常時起動させています。

Windowsでなにか作業をすることはほとんどないので、いっそのことディスプレイを接続するのをやめることにしました。

たまに触るときはChrome Remote DesktopでスマホやMacからアクセスしていたのですが、どうもディスプレイを接続していない環境にChrome Remote DesktopでアクセスするとOS全体のパフォーマンスが著しく低下するようです。

原因はよくわかりませんが、ディスプレイを繋いでいれば快適に動くので、試しにHDMIダミープラグを買って挿してみると案の定快適に動作しました。

なんのために使う製品なのかわかってなかったですが、こういう使い方もあるんですね。

Amazonレビューを見ると、解像度の高いディスプレイを持っていないけど作業スペースを広く使いたい場合に、4Kのダミープラグを指し、あえてリモートで接続することで疑似4Kで作業できるため、自宅環境が整っていないリモートワーカーに人気があるらしい。

MacBookで4K120Hzを出力できる組み合わせ

現行のMacBookシリーズは高解像度かつ高リフレッシュレートな映像出力が不安定で、マシン・OS・接続方法の組み合わせで出力できたりできなかったりする。
それを備忘録的にまとめてみます

① MacBook本体

そもそもの話なのだけど、MacBookやiMac本体のディスプレイは解像度こそ高いがリフレッシュレートは60Hzです。
FHD@120Hz程度ならどんなMacでも出力可能ですが、4K@120Hzとなると4倍の演算能力が必要になるのでマシン自体が限られてくる
具体的に言うとIntel Graphicsでは演算能力が足りず出力不可能です

4K120Hz出力ができるMacBookは、

  • M1 Mac
  • Radeonなどのグラフィックチップを積んでる Intel Mac

M1か16インチのIntelMacを選んでおけば間違いないです

6K@60Hzや8K@30Hzの出力が可能なグラフィック能力があれば理屈では4K120Hzも出力できるはずで、MacBook以外もこの理屈でOK
eGPUでもイケるみたいです

② MacOS

ここに1つ目の落とし穴があります

M1 Mac

BigSurで出力可能。それ以外に選択肢もないので迷うことはありません

Intel Mac

BigSurでは4K@120Hz出力ができません
おそらくソフトウェア的なバグがあるので、現時点ではCatalinaを使うことをオススメします

③ 接続インターフェース

ここにはThunderboltやUSB-Cの複雑すぎる規格とMacの中途半端な実装に落とし穴があります

4K@120Hzの転送をするために注目すべき箇所は

  • Thunderbolt3もしくはUSB4ケーブル
  • DisplayPort 1.4対応 (= HBR3対応)
  • ケーブルの端子は USB-C to DisplayPort

HBR3と記載のあるThunderbolt3であればスペックは足りているのですが、Macのソフトウェア的な問題なのか、 ディスプレイ側はDisplayPortでないと4K@120Hzで出力ができません
Apple純正のThunderbolt3ケーブルでも無理でした。
(M1 Macでは試せていないので、両端がUSB-CなThunderbolt3ケーブルでも出力可能かもしれません)

菅沼環境ではこのケーブルで4K@144Hzを確認してます
https://www.amazon.co.jp/gp/product/B083V6VMNM/


上記をすべて満たした環境で、対応したディスプレイに接続すればOK

システム環境設定

[ディスプレイ]

optionを押しながら 解像度の[変更]

[低解像度モードを表示] にチェック

とすると、リフレッシュレートの選択肢が現れるはずです

DisplayMenu等のアプリでも設定可能ですが、なぜか8bitカラー(ARGB8888)になってしまうので、システム環境設定から変更することをオススメします

nvmとかnodenvとかanyenvをやめてasdfにした

これまでnode環境はnvmで、他はpyenvやgoenvを利用していたのですが、nvmが .node-version ファイルに対応していませんでした。(.nvmrc で指定することはできませす。)
そこでいい加減nodenvに移行したのですが、せっかくなのでどうせなら新しい物をを使おうと思いasdfに移行しました。

anyenvの問題点ははただの「**envをインストールソフト」であり、nodenvやpyenvなどそれぞれの利用法を知っている必要があります。
asdfは実行環境ごとのプラグインを介して直接バージョンを管理するため
どの実行環境でも同様に扱うことができます。
ある日突然「PHPの5.3のバイナリが欲しい…」となっても使い方を迷う必要がありません。

例としてnodejsであれば

asdf install nodejs 16.1.0

Pythonであれば

asdf install python 3.9.5

でインストールが可能です。

また、nodejsであれば lts-fermium のようにタグで指定することも可能です。
自分はグローバルにインストールするものは「一番新しいLTSの最新バージョン」を利用することにしているので、非常に便利です。(nodenvではこれができませんでした。)
また、.asdfrclegacy_version_file = yes を追加することで .node-version なども自動的に読み込み、ディレクトリによって自動的にバージョンを切り替えることもできます。

注意点としては、ビルドする関係上、使用するプラグイン(実行環境)によって依存するライブラリがあるため、そのインストールは別途必要になります。

anyenvより圧倒的に使いやすいので、anyenvやその他のバージョン管理ツールを利用している場合は、asdfへの移行を強くおすすめします。

ランチに行く人を勝手に選出するGoogle App Scriptを作った

弊社では、コロナ禍以前からその日に最もパフォーマンスの出る場所で働くことをルールとして環境を整えているので、あまり事務所に人が集まることはありません。

自宅でも事務所でも、同じように仕事ができることは、これができない方々からすれば良いことのように思うかもしれませんが、各自宅に環境が整いすぎると出社する理由がなくなってしまい、万年リモートのようになってしまう人が一定数現れます。

リモートでは業務上の連絡はもちろん、わからないこと、知りたいこと、聞きたいことはチャットツールでもオンライン会議ツールでも、最適なものを使えば不自由なくコミュニケーションできますが、隣の席の人がどんな仕事をしているのかはわかりませんし、斜め後ろの仲良しの2人の雑談が耳に入ることもありません。

もし自分の仕事と無関係な情報だったとしても、自分と違う環境で生きている人間の話は、新たな興味を生むキッカケになり、仕事の幅や人生の歩みをわずかに変える可能性があります。

ある程度社会人経験を積んだオッサンだけの会社であれば事務所の重要性なんぞほとんどないと思うのですが、新卒を含む若年者にとっては、せっかく組織で働くのであればこういうメリットは大いに教授すべきだと思うわけです。

そんな万年リモート社員を多く抱える弊社でなんとなくアンケート調査を行ったところ、出社がイヤで万年リモートなのではなく、出社する理由がないだけであることがわかりました。

そこで、出社するキッカケづくりをするため、一緒にランチに行く人をGoogleカレンダーにランダムでアサインするGoogle App Scriptを作ってみることに。

要件は

  • このスクリプト専用のGoogleカレンダーを作成し、毎週月曜日にランチの予定を作る
  • 予定を作成するのは可能であればサービスアカウントを使いたい
  • ランダムで選出した社員3名を前述の予定に招待する
  • 毎月10日に来月の予定を自動作成するようトリガーをセットする

以下gs全文です。

こちらの記事を参考にさせて頂きました。

サービスアカウントの作り方なんかも説明するの面倒なので上記記事から辿ってください。

途中サービスアカウントを扱うためのGSAppというライブラリを使っていますが、メンテナンスされておらずライブラリとして読み込むことができなかったので元のソースを別のgsファイルとしてコピペしています。

const MEMBER_NUMBER = 3;
const USERS = [ // 社員のメールアドレス(Googleアカウント)を列挙しとく
	'staff001@nasbi.jp',
	'staff002@nasbi.jp',
	'staff003@nasbi.jp',
	'staff004@nasbi.jp',
	'staff005@nasbi.jp',
];
const SCOPE = [
  'https://www.googleapis.com/auth/calendar',
  'https://www.googleapis.com/auth/calendar.events', // たぶんいらない
  'https://www.googleapis.com/auth/admin.directory.resource.calendar', // ユーザー招待するのに必要?いらないかも
  'https://www.googleapis.com/auth/gmail.send' // 招待メールを飛ばすので必要かと思ったけど動いてない
];
const CALENDAR_ID = 'hogefugapiyo@group.calendar.google.com';

function main() {
  // カレンダーのスコープを指定
  const serverToken = new GSApp(jsonKey.private_key, SCOPE, jsonKey.client_email);

  // トークンの取得
  const tokens = serverToken.addUser(USERS[0]).requestToken().getTokens();

  const events = generateNextMonthEvents();
  events.forEach((event) => {
    insertCalendar(event, CALENDAR_ID, tokens[USERS[0]].token)
  });

  console.log('done');
}

function insertCalendar(eventData, CALENDAR_ID, token) {

  const date = Utilities.formatDate(eventData.date,'JST', 'yyyy-MM-dd');

  const attendees = eventData.members.map((email) => ({ email }))

  const payload = {
    start: { date },
    end: { date },
    summary: 'ランチ行くぞ ٩(ˊᗜˋ*)و',
    attendees,
  };

  // リクエストの設定 
  const fetchOptions = {
    method: 'post',
    payload: JSON.stringify(payload),
    contentType: 'application/json',
    headers: {
      Authorization: `Bearer ${token}`,
    },
    muteHttpExceptions: true,
  };

  //リクエストURLを作成  
  const url = `https://www.googleapis.com/calendar/v3/calendars/${CALENDAR_ID}/events`;

  try {
    const res = UrlFetchApp.fetch(url, fetchOptions);
    Logger.log(res)
  } catch(e) {
    // 例外エラー処理
    Logger.log('Error:')
    Logger.log(e)
  }
}

function generateNextMonthEvents() {
  const events = [];

  const today = new Date();
  const firstDay = new Date();
  firstDay.setMonth(today.getMonth() + 1);
  firstDay.setDate(1);

  const thisMonth = firstDay.getMonth();
  const tmpDate = dateCopy(firstDay);
  while (tmpDate.getMonth() === thisMonth) {
    if (tmpDate.getDay() === 1) { // 月曜日は1
      events.push({
        date: dateCopy(tmpDate),
      });
    }
    tmpDate.setDate(tmpDate.getDate() + 1);
  }

  let tmpUsers = [];
  events.forEach((event) => {
    event.members = [];
    for (let i = 0; i < MEMBER_NUMBER; i++) {
      if (tmpUsers.length === 0) {
        tmpUsers = arrayShuffle([...USERS]);
      }
      while (event.members.includes(tmpUsers[0])) {
        tmpUsers = arrayShuffle(tmpUsers)
      }
      event.members.push(tmpUsers.shift());
    }
  })

  console.log(events);
  return events;
}

/**
 * 引数のDateを複製する
 * @param {Date} date
 * @return {Date}
 */
function dateCopy(date) {
	return new Date(date.getTime());
}

/**
 * 配列をシャッフルする
 * @param {Array} origin
 * @return {Array}
 */
function arrayShuffle(origin) {
	const array = [...origin];
	for (let i = array.length - 1; i > 0; i--) {
		const r = Math.floor(Math.random() * (i + 1));
		const tmp = array[i];
		array[i] = array[r];
		array[r] = tmp;
	}
	return array;
}

同じ人が毎週招待されたり、招待されない人がいないよう、単純なランダム選出ではなく満遍なく招待が届くようにしてみた。

GASの発火トリガーはこんな感じ


作ってみて困ったのは、2つ。

どうせならアカウントに依存しないようサービスアカウントを予定の作成者としたかったのだけど、他のアカウントを予定に招待する場合は実物のGoogleアカウントを偽装して予定の作成をする必要があったこと。

どうやらスパム対策でこのような仕様になったみたい。

偽装する必要があるなら最初からサービスアカウントなんて使う必要なかったなあと思ったり。

もうひとつは招待メールが飛ばないこと。

これは原因がわからなかったのだけど、もしかしたらこれもスパム対策なのかもしれない。

ともあれ最低限の要件は満たせたと思うので、来月から運用を開始します。

もちろんこれはあくまでキッカケづくりであり義務ではないので、気分じゃなかったら参加しなくてもいいし、ふたり以上集まればランチ代は領収書を切ってくれて構わないです

義務や個人の負担はチリツモでモチベーション低下に繋がりますからね。

事務所に二酸化炭素濃度センサーを導入した

弊社事務所の入居する建物は、築50年近くになるマンションです。

オフィス向けに内装をリノベーションこそされているものの、壁がコンクリートに直接壁紙を貼ってあるような状態なので、冬場はエアコンなんかじゃ全然暖まりません。

幸いにもガスコンセントが設置されていたので、入居当初からガスファンヒーターを設置して越冬しています。

ガスファンヒーターはエアコンとは比にならないくらい暖かく、ガス燃焼の副産物として加湿機能も付いているので、とても快適な冬を過ごせています。

ただし、やはり空気中の酸素を使って燃焼させる仕組み上、定期的な換気が欠かせません。

最長でも8時間しか連続稼働できない仕様になっていますが、使い方を間違えると一酸化炭素中毒で死亡事故にも繋がります。

そこまでじゃないにしろ、しばらく稼働して酸素濃度が減ってくると頭痛や軽い吐き気を催すなどの体調不調が起きるので、そのタイミングで空気の入れ替えをする運用をしていたのですが、体調不調がおきてから行動するのはどう考えても危険なので、できれば事前に換気のタイミングを知ることのできる手段を用意する必要性を常々感じていました。

そこでIoTの力を借りるべく、Smart Indoor Air Quality Monitor をで購入。

Netatmoは、スマートホームデバイスを製造しているフランスの会社です。

日本で取り扱いはありませんが(以前は代理店があったみたいだけど、なくなったっぽい)、AmazonUKで購入可能で、日本国内へ送ってくれます。

本体は想像のふた周りは小さく、iPhoneXと並べてもそれほど大きさに差はありません。

付属のACアダプターはBFタイプと呼ばれる形で、日本で使うには変換プラグが必要になりますが、本体差込口はUSB type-Bなので適当な給電環境で賄えることが多いと思います。MacbookProとのUSB接続でも問題なく動きました。

センサーは4つあって

  • 気温
  • 湿度
  • 二酸化炭素濃度
  • 騒音レベル

が専用のアプリから確認できます。

二酸化炭素濃度に関しては、ExcellentやBadなど、4段階程度の閾値があり、それを跨ぐタイミングでアプリに通知が来る仕組みになっています。

菅沼個人の感覚では5000ppmを超えたあたりから(センサーの上限が5000ppm)明らかな体調不調が起こるので、それに達する前に換気するよう心がけることができるようになりました。

できれば「3000ppmを超えたら通知」みたいな設定を自由に指定できたら良かったのですが、決められたタイミングで通知されるものを受け取るか否かしか決められないのが少し残念なところ。

連携できるアプリ(デバイス)数に制限がないので、スタッフ全員がそれぞれ通知を受け取れ、各々のタイミングで換気ができるのは良いところでした。

Smart Indoor Air Quality Monitorは、Weather Stationの廉価版のような製品で、こちらにはより詳細なWebインターフェースがあるみたいなので、もしかしたらこっちなら詳細な通知設定ができるのかもしれません。


二酸化炭素濃度が高い状態だと、頭痛や吐き気の他にも、眠気や集中力の低下に繋がるらしいので、見に覚えがある方は換気を心がけるか、このようなセンサーを導入してみると面白いかもしれません。

ちなみに、弊社環境ではガスファンヒーターを点火してから2時間程度で5000ppmを超えます。

ガスファンヒーターを付けずとも、35平米程度の部屋に2〜3人いるだけで二酸化炭素濃度はグングン上がっていきます。

酸欠状態は思ってたよりも身近にあって、チーム全体の仕事のパフォーマンスに直結するのであまり蔑ろにできないというのがこの件の気づきです。

@nuxtjs/apolloでレスポンスが空だったら404を出す

Vue ApolloがNuxtで利用可能になる @nuxtjs/apollo を使うときレスポンスが空だったら404を出したい場合があります。

下記のようにSmart Queryを使うと簡単にサーバーサイドとクライアントサイドでfetchする機能を書くことができます。

// pages/blog/_id.vue
import GetPosts from '~/apollo/GetPost.gql';

export default {
  name: 'BlogIndex',
  apollo: {
    posts: {
      query: GetPost,
      variables () {
        return {
          pageId: 123,
        };
      },
    },
  },
}

しかしSmartQueryはレスポンスのHTTPステータスコードを設定できないという問題があります。
その場合は素直に asyncData() を利用しましょう

// pages/blog/_id.vue
import GetPost from '~/apollo/GetPost.gql';

export default {
  name: 'BlogPost',
  async asyncData ({ error, app, params }) {
    const response = await app.apolloProvider.defaultClient.query({
      query: GetPost,
      variables: { pageId: params.id },
    });
    if (!response.data.post) {
      error({ statusCode: 404, message: 'Not Found' });
    }
    return { post: response.data.post };
  },
  data () {
    return {
      post: {},
    };
  },
};

ただそうなると @nuxtjs/apollo を使う意義も薄くなってしまいますね。
vue-apolloの開発もあまり活発でないので何か別の手段を使ったほうがいいんでしょうか…。

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;

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