Modal系のライブラリはどう作られているのか調べてみた
こんにちは。株式会社 Sally エンジニアの @piesukeです。
最近面白かったマーダーミステリーは、カモメのピースが揃う時 です。
弊社ではマーダーミステリーを遊べるアプリ「ウズ」と、マーダーミステリーを制作してウズ上で遊べるアプリ「ウズスタジオ」を開発しています。
絶賛エンジニア募集中です!詳しくは こちら をご覧ください。
はじめに
弊社は Web の再利用可能なコンポーネントは出来るだけ共通コンポーネントを作成するようにしています。そして、共通コンポーネントは出来るだけサードパーティーライブラリを使わず、自前で作成するようにしています。
しかし、モーダルのような UI コンポーネントは、実装が複雑なので、サードパーティーライブラリを活用しています。
モーダルライブラリは適切な props を渡すだけで簡単に利用できますが、中身がどうなっているのか気になったので調べてみました。
ちなみに弊社ではモーダルは Chakra UI を使っているので、Chakra UI の Modal を調べました。
Chakra UI の Modal
仕様は こちら に記載されています。
わざわざ仕様のページを見に行くのが面倒なあなたの為に基本的なコードを引用すると、
function BasicUsage() {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<Button onClick={onOpen}>Open Modal</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Modal Title</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Lorem count={2} />
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" mr={3} onClick={onClose}>
Close
</Button>
<Button variant="ghost">Secondary Action</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
}
Modal
でラップして、ModalOverlay
、ModalContent
、ModalHeader
、ModalCloseButton
、ModalBody
、ModalFooter
などのコンポーネントを使ってモーダルを作成します。
Modal の実装
Modal
のソースコードは以下のようになっています。
Modal
はラッパーとして機能しつつ、子コンポーネントにコンテキストやイベントハンドリング機能を提供しています。
context は、Modal
コンポーネントの Props として渡される値と、 useModal
というカスタムフックで生成された値をマージして作成されています。
どうやら useModal
が Modal
のロジックを担っているようです。次は useModal
のソースコードを見てみましょう。
useModal の実装
useModal
のソースコードは以下のようになっています。
useModal
がやってることは、大まかに以下の通りです。
①useIds
を利用して、コンポーネント内で必要な複数のユニークな ID を一括生成し、他の要素と衝突しないようにする
②WAI-ARIA の仕様に従ってイベントや属性を設定
③useModalManager
を呼び出してダイアログに index を付与
この中で面白いなーと思ったのは WAI-ARIA の対応と modalManager の使い方です。
WAI-ARIA とは Web アクセシビリティを向上させるために W3C が策定した技術仕様で、モーダルのような UI コンポーネントを作成する際には、WAI-ARIA の仕様に従って実装することが推奨されています。
useModal
では、キーボードイベントや aria-modal
をはじめとした属性の設定を行っています。今までコンポーネントを作ってきて、WAI-ARIA の対応をあまり意識してこなかったので、今後は意識していきたいと思いました。
また、useModalManager
は、モーダルの index を管理するためのカスタムフックです。複数のモーダルが同時に開かれている場合に、それぞれのモーダルのインデックスを管理するために使われています。
こちらも、シンプルながら汎用性の高い使い方をしているなと感じました。
全画面表示を行う為に DOM ツリーの外に描画する Portal
Modal の便利な点として、コンポーネントの中のどこに書いても全画面表示になるという点があります。これは、Portal
というコンポーネントを使って実現されています。
例えば DefaultPortal
を見てみると、
useSafeLayoutEffect
の中で document.body
の中に新しく div 要素を append して、createPortal
によって Portal をレンダーする処理が行われています。
この処理によって、どこのコンポーネントでモーダルを表示しても、document.body の末尾に描画され、他のコンポーネントに影響を与えることなくモーダルを全画面表示することができます。
まとめ
ここまで複雑な処理を行っているとなると、自作で実装するのは大変そうですね。簡単に使えるくらい上手く抽象化してくれていることに感謝したいです。
WAI-ARIA の対応や、Portal の使い方など、実装の参考になる点が多かったです。今後もライブラリのソースコードを読んで、学びを得ていきたいと思います。
Discussion