リポジトリ
https://github.com/shoet/web-ui-trace/tree/main/expo-console
使用した技術
Next.js 15 / TailwindCSS v4 / Storybook 9 / Lucide(アイコン)
今回取り上げたい技術要素
ZennやQiitaといった技術記事のサイトなどには、
記事の横に目次として、
画面の表示領域と目次のハイライトが追従しているUIがあると思いますが、


IntersectionObserverAPIを使うと割と簡単に実装できるみたいなので、今回挑戦してみました。
詳しい実装はこちら(https://github.com/shoet/web-ui-trace/blob/main/expo-console/src/components/IntersectionObserverContext/index.tsx)
になりますが
監視したいDOMに対してObserverを発行し、コールバック関数を渡してあげることで、
その中でDOM要素が表示領域に含まれているかや、表示領域に入ったか、出ていったか、などを監視することができます。
今回の実装としては、それらの状態をReactのContextに保持し、メニュー側でHookを通して状態を反映させる(ハイライトを付ける)実装をしました。
実装の核となる部分は以下になります。
idでDOMを取り出して、IntersectionObserverを付与しています。
IntersectionObserverに渡すコンストラクタで状態を監視することができます。
const createObserver = useCallback(
(id: string) => {
let element = document.getElementById(id);
if (!element) {
return;
}
// entry.target, // 対象要素
// entry.boundingClientRect, // 対象要素の外接矩形
// entry.intersectionRect, // 表示領域の矩形。画面から見切れていたら表示されている分の高さで反映される。
// entry.intersectionRatio, // intersectionRatio と boundingClientRect の比率。何%表示されているかを判別できる
// entry.isIntersecting, // 交差交差常態か。rootがnullで領域がビューポートの場合、その中に入っているか。
// entry.time, // 交差されたタイミング
// entry.rootBounds, // 交差しているルート矩形。rootがnullだったらビューポートの領域になる
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
updateIntersections(entry.target.id, entry.intersectionRect);
} else {
updateIntersections(entry.target.id, undefined);
}
});
},
{
root: null,
threshold: 0,
},
);
observer.observe(element);
return observer;
},
[updateIntersections],
);
最終的に仕上がったもの
静止画ですが、思った以上にトレースができ、学びも多かったです。
今回得られた示唆
若干雑ですが得られた知見を載せたいと思います。
- ページごとの、新規作成するためのボタンは、だいたい右上にある
- 一つの画面で複数のコンポーネントで構成されておりそれぞれのコンポーネントごとに
Submitの役割を持つボタンが有り、そのコンポーネントの中で成否がわかるUIになっている。
- ページごとにナビゲーションの内容が変わっている
- ページ内の各コンポーネントは、同じBorder、Paddingで統一されている
- ボタンの色は、ダークモードでもライトモードでも、どちらでも使える色を選択している
- コンポーネント同士のパディングを大きめに取ると、本格的な感じになる
- 均等に間隔を開ける、だいたい40pxくらい
- borderの色はかなり薄い、
- border-gray-700と800の間ぐらいで、最低限線と認識できるかなくらい