会社のディスプレイ全種使ってみた

会社の現役ディスプレイを全部使ってみたので、その感想をまとめる。

ラインナップ

順序は基本的に試した順(一部例外あり)

メーカー 製品 解像度@fps 給電能力 公式サイト 備考
AOC AG274UXP/11 4k 65W AG274UXP/11 AGONはブランド
CUNPU S270ES 4k@144Hz(HDMI) 4k@120Hz(Type-C) 4k@160Hz(DP) CUNPU (製品ページが見当たらない)
4AM 4Z40QW 5k2k@120Hz 最大96W (2025年モデルの場合は最大65W給電) 4Z40QW | 4AM
Huawei MateView 3840 x 2560@60Hz 65W HUAWEI MateView - HUAWEI 日本 (WaybackMachine) マイク・スピーカー付き
ALIENWARE AW2725Q 3840x2160@240Hz 15W Alienware 27インチ4K QD-OLEDゲーミング モニター | Dell 日本
不明 不明 WQHD@165Hz 2560 x 1440(QHD/WQHD - Wide Quad High Definition)
Apple Macbook Pro内蔵 3024 x 1964 Retina -

それぞれのモニタについての感想

AOC AG274UXP/11

初めて実用した60fpsをこえるリフレッシュレートのモニタ。画面上で一番よく動き回るものがポインタなためあまり利点を感じなかったが、確かに家に帰って私物の60fpsモニタを使うと確かに違うとはなった。

画面の周囲にフードがついている。脚が立体的な形をしているためどうしても着座位置から近くなり、目が疲れる。輝度を思い切り下げるとよいのかもしれない。

CUNPU S270ES

ESの文字を見るとEnginnering Sampleのように見えてしまうが、大々的に売られているようなのでおそらく違う。

AOC AG274UXP/11などと比べて目の疲れは抑えられていた。また、脚が特徴的な形をしていないため壁に向かった机であっても問題なく距離を取ることができた。文字なども読みやすかったように思う。

4AM 4Z40QW

浅学ながらこのメーカーを知らず、初めはLAAと読むと思っていた。

ウルトラワイド+湾曲のため人を選ぶ。私には合わなかったが、使いこなすことができれば広い作業スペースを得ることができよう。私にとっては縦の物理的な広さがほしかった。解像度は足りているものの、dpiが大きくスケーリングをしっかりかけないと文字が小さくなりすぎてしまう。そのため、得られる縦方向の作業スペースは数字ほど大きくない。

M5 Macbook Proを用いて確認しているが、HDRを有効にすると灰がかかったような表示になってしまう。公式サイト曰く一定のHDRには対応しているようだが、互換性の問題だろうか。使用しているMacbook Proはグラフィックに関して不思議な挙動をすることがあるため、それが原因かもしれない。

Huawei MateView

以前から気になっていた機種であり、本企画で試した社内モニタの中で最も使いやすかった。縦方向に余裕があるのは正義である。ただし、最大輝度のスペックが相当高いため、文字入力をメインに行う場合は50%から高くても75%程度の輝度で十分である。

OSDのレスポンスがディスプレイとしては異常に良いため、輝度調整などもノンストレスで行うことができる。可能であればHDRを付けていないときでもmac側から制御できるとありがたいのだが……それをせずとも問題ない程度にサクサクと動く。社外品キーボードを使っている場合は輝度調整キーが無いことがあるため、本体での操作がしやすいことはとても歓迎できる。

M5 Macbook Proを接続し、クラムシェルモードにせずに使うことでHDRを選ぶことができる。選べないときはmacを再起動すると現れることがある。

充電は65Wまで。私の使い方ではM5Pro Macbook Proがこのモニタからの充電のみでバッテリ残量が100%に張り付いていた。

ALIENWARE AW2725Q

3DCGを見ると世界に穴が開いたように感じられるほどのディスプレイ。実際に穴はないのにそう見えるのは良いことなのかはわからない。少なくとも平文テキストを書くにはいろいろな部分があまりにオーバースペックに感じた。ALIENWARE好きはテンションが上がるかもしれない。

謎のモバイルモニタ

ディスプレイ自体に回転機能がついていたり、商品説明ページよりもリフレッシュレートが高かったりと高性能さについても謎が多いモニタ。モバイルモニタなので当たり前だが、持ち運びに便利そうである。

確認時に使ったM5 Macbook Pro では画面出力を色々試しているうちに出力されなくなってしまったが、それまでは元気に使えていた。

前述のALIENWAREの下に設置されていたが、そうするとDockを画面下に表示している環境においては⌘tabのアプリ切り替えがメインではなくこちらに出てきてしまい、設置位置は論理的な環境と設定も合わせて考える必要があると気づいた。デスクトップを拡張するよりは何らかのアプリのウィンドウを飛ばしておいたほうが使いやすいかもしれない。

まとめ

以上より、MateViewがとても気に入ったため、出社するたびに占拠している。使いたい人はどくので遠慮なく言ってください。
MateViewを除いた4枚(もしくは社長のデスクを除いた3枚)の中では、4AM 4Z40QWが割と人気なように見える。慣れれば使いやすいのかもしれない。

以上

スタンフォード大学 – AI時代のエンジニアのための無料講義資料「CS146S: The Modern Software Developer」

始めに

今IT業界では、生成AIによってかつてないほどの大きな変動が起きています。
「AIがコードを書く時代」において、私たちWEBエンジニアには何が求められるのでしょうか?

単にAIにコードを書かせるだけではなく、AIをツールとして使いこなし、
開発速度や精度を劇的に向上させるための「新しいスキルセット」が必要とされています。

この記事では、スタンフォード大学が無料公開している講義資料について紹介します。

公式サイト(https://themodernsoftware.dev)

サイト内の「Syllabus」タブをクリックすると、各週の講義スライドやおすすめの関連記事を閲覧できます。
無料公開されているのはスライドと記事のみですが、非常に参考になる内容のため、今回紹介いたしました。

CS146S: The Modern Software Developer とは?

このコースは、スタンフォード大学で2025年秋学期に開講される「現代のソフトウェア開発者」のための授業です。

従来のプログラミング教育が「ゼロからコードを書く力」に重点を置いていたのに対し、
このコースではAIツールを前提とした開発ワークフローへと完全にシフトしています。
「これからの時代に必須となるスキルを身につけたい」というエンジニアの方は必見の内容です。

各週のテーマを日本語でまとめてみました:
Week 1: Introduction to Coding LLMs and AI Development
(コーディングLLMとAI開発への導入)
Week 2: The Anatomy of Coding Agents
(コーディング・エージェントの解剖学)
Week 3: The AI IDE
(AIネイティブなIDE)
Week 4: Coding Agent Patterns
(コーディング・エージェントのパターン)
Week 5: The Modern Terminal
(モダンなターミナル環境)
Week 6: AI Testing and Security
(AIによるテストとセキュリティ)
Week 7: Modern Software Support
(モダンなソフトウェア・サポート / レビュー・ドキュメント)
Week 8: Automated UI and App Building
(UIとアプリ構築の自動化)
Week 9: Agents Post-Deployment
(デプロイ後のエージェント活用 / 運用監視)
Week 10: What's Next for AI Software Engineering
(AIソフトウェアエンジニアリングの未来)

最後

今回の紹介は以上です。
サイトは英語のみですが、講義資料はスライド形式なので、翻訳ツールなどを使いながら読むだけでも非常に勉強になると思います。
また時間が取れたときに、それぞれの講義内容をまとめ記事も書くかもしれません。

参照リンク
公式サイト: themodernsoftware.dev
コースの課題:https://github.com/mihail911/modern-software-dev-assignments/tree/master

IntelliJ IDEA(JetBrains製IDE)でChromeのリモートデバッグ

IntelliJ IDEAでChromeのリモートデバッグ

console.logデバッグはもう辛い!IntelliJ IDEA(などのJetBrains製IDE)でもちゃんとChromeのリモートデバッグの設定をして、良いデバッグライフを送りましょう

基本的な手順

  1. /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir="/path/to/user-data" でChromeを起動します(/path/to/user-data は任意のディレクトリ)
  2. IDEAの「実行」→「構成の編集...」から「Node.js/Chromeへの接続」を設定します
  3. 接続するタブを指定します

これでブレークポイントを使った本格的なデバッグができるようになります!

もう少し詳細な手順

まずChromeでリモートデバッグできるようにするには、

  • --remote-debugging-port リモートデバッグのポート
  • --user-data-dir ユーザーディレクトリの位置

の2つの起動オプションが必須です。

--remote-debugging-port はリモートデバッグ接続用のポート設定です。適当なポート番号を指定すればOKです。
--user-data-dir はChromeのプロファイルデータを保存するディレクトリです。通常使用しているChromeとは別のプロファイルで起動することで、既存のChromeセッションと競合せずにリモートデバッグ用のインスタンスを立ち上げることができます。

ディレクトリ自体は任意の場所で良いですが、/tmp 以下など揮発性のディレクトリに設定すると、再起動した場合にプロファイルが消えてしまいます。プロファイルが消えるとChromeの初回起動のメッセージが表示されたりログインセッションが切れたりします。これは煩わしいので、ユーザーディレクトリ(~/)などに適当なディレクトリを作って、必要に応じて消す方が使い勝手が良いです。

IntelliJ IDEAの設定

実行の構成にChromeの起動コマンドも登録してしまいましょう!

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222  --user-data-dir="/path/to/user-data"

そうすれば、「実行...」からChromeをリモートデバッグのオプション付きで起動できます。

次にリモートデバッグの設定を行います。この時「自動的に再接続」のチェックを入れておきましょう。
また「起動前(B)」に先程設定したChromeの実行を登録すると便利そうですが、起動タイミングの問題なのかうまく行かないので、個別に起動することをおすすめします。

Chromeを起動してリモートデバッグを開始すると、少々見づらいですが接続するタブが選択肢として表示されるので選択しましょう

これでリモートデバッグが開始できます!良いデバッグライフを!

package-lock.jsonがコンフリクトした場合の正しい解消方法

ここにブランチが3つあります

  • main
  • featureA
  • featureB

featureAとBそれぞれpackage.jsonにアプデがあり、featureAを先にmainにマージ済みの状態

main←featureBでpackage.jsonないしpackage-lock.jsonにコンフリクトが発生する。

普通に考えたらfeatureB←mainして npm install すれば解決するが、パッケージのマイナーバージョンを変えたくない場合はこの方法だとNG。package-lock.jsonが更新されて依存パッケージのマイナーバージョンが変更されてしまう。

そんなときは、mainをmainBとしてフォークして、mainBにfeatureBで追加したパッケージを改めてインストール。 npm install hogehoge@1.2.3

featureB←mainBするとやっぱりpackage-lock.jsonがコンフリクトするのでmainBのものを使う git checkout --theirs package-lock.json

で、 npm ci などすれば正常化できるのだ

mainも取り込み済みなので、main←featureBがコンフリクトすることもない。

我々はいつからimportをファイルの先頭に書かなければいけないと勘違いしていたのか

自分だけかもしれないのだが、 import はファイルの先頭に書かなければシンタックスエラーになるのだと勝手に勘違いをしていた。

下記のソースコードはエラーではないのだ。

import hoge from 'hoge';

export function hogefuga() {
  hoge();
  fuga();
}

import fuga from 'fuga';

export function fugahoge() {
  fuga();
  hoge();
}

ただし import をファイルの途中に書くメリットはまったく思いつかないので、先頭に書いたほうが良いだろうね

なぜこんな勘違いをしていたのかと考えてみたけど

  • トップレベルに書かなければいけないという仕様をファイルの先頭と勘違いしていた
  • importはエディタが勝手にファイルの先頭に書いてくれるものだから
  • ES Modules 覚え始めのころのプロジェクトで、eslintで禁止されていた

こういう理由があったのかもしれない。

各種ブラウザのフレームレートを調べた

ちょっと気になることがあって各主要ブラウザのフレームレートを調べてみたのでその結果をメモ。

調べ方

PCのChromeであれば、DevToolsで下記のようにリアルタイムのフレームレートを表示する事が可能です。

今回はChrome以外のブラウザや、スマホでも調べたかったので下記のようなコードを用意してみました。

requestAnimationFrame() を1秒間回して、その回数を調べる単純なコードです。

async function measureFPS() {
    return new Promise((resolve) => {
        let count = 0;
        const start = performance.now();
        function frame() {
            if (performance.now() - start >= 1000) {
                resolve(count);
                return;
            }

            count = count + 1;
            requestAnimationFrame(frame);
        }
        frame();
    });
}

それをHTML上に実装したものが こちらのページ で確認できます

Macに144Hzのディスプレイを繋いでChromeで試した結果がこちら

どうやら1〜2fpsほど大きい数字が出てしまうようです。
コードの問題かもしれませんが、まあいいでしょう。なんとなく測れればよいのです。

このHTMLを使って各ブラウザで計測していきます。

計測環境

結果発表の前に、計測した環境について書いておきます

  • Macbook Pro M1 Max
    - 内蔵ディスプレイ(ProMotionの120Hz)
    - 外部ディスプレイ:AG274UXP/11(144Hzで接続)
    - Sidecar の iPad Pro (ProMotion対応だが、Sidecar接続のため60Hz)

  • iPad Pro 11インチ 第4世代 M2チップ 
    - 内蔵ディスプレイ(ProMotion 120Hz)
    - 外部ディスプレイ:AG274UXP/11(ステージマネージャ有効)

  • Xiaomi 13T Pro
    - 内蔵ディスプレイ(最高144Hzまでの動的フレームレート設定)
    - 内蔵ディスプレイ(固定144Hz)
    - 内蔵ディスプレイ(固定60Hz)

AG274UXP/11が長いので以下AGONと表記します

計測した結果

Macbook Pro M1

ブラウザ ディスプレイ FPS
Chrome 内蔵 122
Chrome AGON 145
Chrome Sidecar 62
Safari 内蔵 62
Safari AGON 74
Safari Sidecar62
Firefox 内蔵 145
Firefox AGON 146
Firefox Sidecar 144

この結果から、Chromeはディスプレイのリフレッシュレートに合わせてレンダリングのフレームレート制限を可変させることがわかった。

Safariも基本的にはChromeと同じくディスプレイのリフレッシュレートを最大値としてフレームレート制限をかけるようだが、

  • 最高でも72FPS(?)までに制限される
  • ProMotionの下では60FPSに制限される

という条件がつくようだ。

Firefoxだけ理解できなかったのでAGONを物理的に切断して、MacとSidecarだけ繋がってる状態で再計測

ブラウザ ディスプレイ FPS
Firefox 内蔵 61
Firefox Sidecar(プライマリ) 62

なるほど、どうやらプライマリディスプレイのリフレッシュレートでレンダリングされるらしい。(↑の計測時はSidecarがプライマリになっていた)

今度は内蔵をプライマリにしてみる

ブラウザ ディスプレイ FPS
Firefox 内蔵(プライマリ) 120
Firefox Sidecar 120

なるほどね

iPad Pro 11インチ

ブラウザ ディスプレイ FPS
Safari 内蔵 61
Safari AGON 62

ステージマネージャの外部ディスプレイはブラウザの外側でリフレッシュレートがどのくらいなのか調べられなかったのだけど、マウスポインターをグルグル動かしたときの軌跡がProMotionな内蔵ディスプレイと比べると明らかに少ないので、おそらく60Hzで接続されてるのだと思う。

ちなみにAGONを接続せずにiPad単体で測っても62FPSでした。

Xiaomi 13T Pro

ブラウザ ディスプレイ FPS
Chrome 内蔵・動的Hz 122
Chrome 内蔵・固定144Hz 121
Chrome 内蔵・固定60Hz 62

Mac+Chromeと同じ結果を予想していたけど、144Hz固定にしても120相当しか出なかったのは予想外。この端末の特有な現象の可能性もある。

もしかしたらAndroidのChromeは上限120FPSで制限されているのかもしれない。

結論

今回iPhone実機では調査しなかったけど、まあ間違いなくiPadと同じで60FPSに制限されていると思います。

フロントエンド界隈では「ヌルサクアニメーションにするために60FPSを死守すべし!」というのはよく聞く話ですが、実際に調べてみた結果Safariをターゲットとするならばやはり60FPSがひとつの基準になるのは間違いないようです。
ただ、Safari以外はハードウェアさえ対応していれば少なくとも120FPSでレンダリングできるので、これからは120FPSを基準にしていくべきなのかもしれません。

iPhoneだってハードウェア的には120Hzなわけで、Apple的にハイリフレッシュレートに対して否定的な立場ではないハズ。
OS/Safariのアップデートで突然60FPS制限を引き上げるのも時間の問題なのでは?と思いました。

スタイルを隔離してHTMLを埋め込むにはShadowDOMを使うとスマートかもしれない

管理画面があるようなWebサイト・Webアプリを作るとき、稀によくHTMLテキストを値として入力したいという要件があります。

Webサイトの運用者がある程度自由に内容を書き換えられる必要があるコンテンツ、例えばWordPressの記事本文などがそうですが、管理画面ではWYSIWYGエディタのようなものだったり、場合によってはmarkdownを書く仕様だったりすると思います。

これを表示する側の実装ではその部分にも当然スタイルを当てなければなりませんが、多くの場合は出力されるHTMLに自由にクラス名などを付与することができないため、 <h1><p> など、クラス名のついていない素のタグに対してスタイルを適用することになります。

例えば、下記のような。

<div class="post_content">
  <h1>見出し1</h1>
  <p>ほげほげ</p>
  <h2>見出し2</h2>
  <ul>
    <li>リスト1</li>
    <li>リスト2</li>
  </ul>
</div>
.post_content h1 {
  font-size: 24px;
  font-weight: bold;
}
.post_content h2 {
  font-size: 20px;
  font-weight: bold;
}

.post_content p {
  margin-top: 12px;
  margin-bottom: 12px;
}

.post_content ul {
  list-style: disc;
}

...

私はこういうスタイルを書くとき、このサイトがベースのスタイルとしてtailwindやリセットCSSを使っているとしたら、わりと気を使うんですよね。

h1やpのようなよく使うタグはもちろんデザインに合わせてスタイルを書いておきますが、 <blockquote> みたいな普段使わないような、デザインの想定にもないようなタグを使われた場合、スタイルを用意できておらず見た目ではdivと同じになってしまいます。

normalize.cssをベースにしていれば少なくともデフォルトのスタイルが存在するので、スタイルがないよりマシだと思いますが、ベースがリセットCSSだと前後の余白すらなくなってしまって酷い見た目になります。

上記の例で言うところの .post_content 以下だけCSS的に隔離ができて、normalize.cssもしくは独自のCSSを適用できるのだとしたら、外からの影響を受けずに堅牢なスタイルが作れるのではないかと思いました。

親要素からの隔離だったら ShadowDOM が使えるのでは? というのが今回の話の趣旨になります。

ShadowDOMとは?

詳しい説明はみんなだいすき MdN を見てもらうとして、一言でいうならばDOMツリーの中に独立したDOMツリーを構築する機能のこと。

その影響としてCSSのスコーピングが可能になります。

ShadowDOMの外はtailwind、中はnormalizeができるわけですね。

<div class="js-dom-prison">
  <h1>見出し1</h1>
  <p>ほげほげ</p>
  <h2>見出し2</h2>
  <ul>
    <li>リスト1</li>
    <li>リスト2</li>
  </ul>
</div>

.js-dom-prison をShadowDOMのrootにして、内容物をすべてShadowDOMの中に配置し直します。

const container = document.querySelector('.js-dom-prison');
const children = container.childNodes;
const shadowRoot = container.attachShadow({mode: 'open'});
shadowRoot.append(...children);

ShadowDOMの中にnormalize.css(もちろん、独自のCSSでも可)を適用したい場合は、こう↓

const container = document.querySelector('.js-dom-prison');
const children = container.childNodes;
const shadowRoot = container.attachShadow({mode: 'open'});
const stylesheet = document.createElement('link');
stylesheet.setAttribute('href', 'https://necolas.github.io/normalize.css/8.0.1/normalize.css');
stylesheet.setAttribute('rel', 'stylesheet');
shadowRoot.append(stylesheet, ...children);

これをDevToolsでみるとこうなります↓

あえてinnerHTMLなどを使わずに最初から .js-dom-prison にHTMLを展開しておくことで、たぶんSEO的にも問題にならない。(たぶん)

querySelector等で探せなくなるなど、デメリットがないわけではないので使い所が選ぶ必要がありそうですが、iframeなどと違ってオーバーヘッドも小さいですし、もののJS数行でCSS的に隔離ができるので、ハマる状況ならとてもスマートなんじゃいかと思ってます

Alpine.jsの$storeはいつ使うのか?

Alpine.jsでは x-data="{open: false}" というように x-data 属性を付与することで、その要素の内側で x-show="open" のように参照することができます。

<div x-data="{open: false}">
  <button @click="open = !open">開閉するよ</button>
  <div x-show="open">
    開いてるね
  </div>
</div>

とまあこれはAlpine.jsを知っている人なら全員知ってることなんですが、 x-data でなくても $store で同じようなことができます。

例えばこんな感じですね

<button x-data @click="$store.menu.open = !$store.menu.open">開閉するよ</button>

<div x-data x-show="$store.menu.open">
  開いてるね
</div>

<script>
  document.addEventListener('alpine:init', () => {
    Alpine.store('menu', {
      open: false,
    })
  })
</script>

コードでみると結構違いますが、やってることは一緒で、openという変数の定義場所が違うだけなんですよね。

$store はコンポーネントを跨いで参照することができます。便利。

ただし、v3から x-data入れ子になっていても変数名がカブらない限り祖先の変数も参照できるという仕様なので、

<body x-data="{open: false}">
  ...
  <button @click="open = !open">開閉するよ</button>
  ...
  <div x-show="open">
    開いてるね
  </div>
  ...
</body>

このように <body>x-data しちゃえばどこからでも参照できるグローバル変数を定義できちゃうんですよね。これでは$storeの立場がありません。

じゃあ $store っていつ使うの?って話なんですが、おそらく主に下記2点に注目すべきポイントがあります

外部のJSと連携したい!

UI系のライブラリを利用する場合など、Alpine.js外のJSとステートの共有をしたい場合は往々にしてあると思います。

そんな場合には x-dataAlpine.data で頑張るより素直にstoreを使ったほうが良いでしょう。

<script>
    Alpine.store('menu').open = true;
</script>

パフォーマンスへの影響

x-data を付与すると Alpine.start() を実行したときに、その要素以下がコンポーネントとして初期化されます。

もしその要素が巨大なツリーだった場合、初期化にかかるコストが大きいものになるため、できる限りコンポーネントは小さくするのがベター。

ちょっとした状態管理のためだけにbodyで x-data をしてしまうのはコストに見合わないので、$storeを使うのがよいでしょう。

Figmaの「マルチ編集」はズボラデザイナーの救世主となるか

Figmaのアップデートで「マルチ編集」機能が追加されました。

編集したいオブジェクト(またはテキストなど)を選択したあとに
Command+Option+Aを押すとフレーム間をまたいで一括で選択されるという、
今までありそうでなかった代物です。

フレームをまたげると、たくさんのフレームを置いてるデザインファイルの作業時には特に便利そうです。

↓公式のFigmaファイルはこちら
Multi-edit playground

まずは選択したいオブジェクトを選ぶ

Command+Option+Aを押すと、
フレームをまたいで同じオブジェクトが選択される
その後は任意で色変えたり色々できる

オブジェクトを選択してShiftを押すと
どこにあるかが青色のボックスで表示される
確認するだけならこれでもいいかも

これコンポーネントでよくね?

しかし、この機能を知ったFigmaユーザーはこう思うでしょう。

「これ編集するなら最初からコンポーネントにてしおけばよくね?」

…私も、最初はそう思いました。
コンポーネント化しとけば編集も楽じゃん? と。

しかし使い所がちょっと違うのです、こいつは…

コンポーネント化する前に役立つんだ

このマルチ機能、私のようなズボラデザイナーの活用方法としては
このような場面で役立つと思っています

このアイコンこのページでしか使わないから
コンポーネント化しなくていっか!

なんだかんだ複数ページに使用することになるが、
そのまま複製して使う

やっぱコンポーネントにしとけばよかったと後悔しつつ
置き換え作業をする

こういった雑なデザインファイルを作った時に役立つのです。

一括で選択できるから、同じオブジェクトがどこに置いてあるかかすぐにわかるのです
ズボラデザイナー(主に私)にとっては、救世主のような存在になるかもしれません

後々困るのは自分だとはめちゃくちゃ分かっているんですが、
勢いでガーッと作ってる時はコンポーネント化めんどい!後でやろう!と、
ついその場のライブ感を優先してしまうこと、ありますよ…ね?

ファイルやコンポーネント管理をきっちりしている方も、
マルチ機能に救われる場面があるとおもいます。

あなたもFigmaを信じましょう。Figma is GOD.

Google Map APIの高度なマーカーのクリックイベントに関するリファレンスが間違ってる

下記はリファレンスのイベントから抜粋したもの。

イベント 説明
click (前略) addEventListener() では使用できません(代わりに gmp-click を使用してください)。
gmp-click (前略) (addListener() ではなく)addEventListener() で使用することをおすすめします。

これを見ると addEventListener('gmp-click') とするのが正しそうだが、これは動かない。
ドキュメントを見ると addListener('click')addListener('gmp-click') で良さそう。(欲しい引数の型でどちらか好きな方を選ぶ)