bgm: https://music.163.com/#/song?id=490182455&userid=361029804
劇場版『NO GAME NO LIFE ゲーム人生 ZERO』テーマ曲
前言及紹介#
今日のフロントエンド開発において、浮動要素はますます重要な役割を果たしています。これらはユーザーに追加のインタラクションや情報を提供し、ページ全体のレイアウトに影響を与えません。Floating UIは、浮動要素の位置決めと作成を容易にするための JavaScript ライブラリです。これを使用することで、浮動要素の位置とインタラクション効果を簡単に制御し、ユーザーエクスペリエンスを向上させることができます。
もし、シンプルで使いやすい浮動要素のソリューションを探しているのであれば、Floating UI は最適な選択ではないかもしれません。このライブラリの主な目的は、アンカーポイントの位置決め機能を提供することであり、事前に構築されたスタイルや他の高度なインタラクション効果を提供することではありません。しかし、React を熟知していて、このような高度にカスタマイズ可能なライブラリを使用したいのであれば、より良く活用できるでしょう。
このライブラリは意図的に「低レベル」であり、その唯一の目的は「アンカーポイントの位置決め」を提供することです。これを欠けている CSS 機能のポリフィルとして考えてください。事前に構築されたスタイルは提供しません。ユーザーインタラクションは React ユーザーのみに適用されます。
もし、すぐに使えるシンプルな機能を探しているのであれば、他のライブラリの方があなたのユースケースに適しているかもしれません。
この記事を書くにあたり、私は React で Floating UI を使用する経験の共有と呼ぶことを好みます。特にReact でこのライブラリを使用する方法についてであり、チュートリアルではありません。実際に使用してみると、Floating UI のドキュメントと例はかなり詳細ですが、Floating UI に関する中国語の資料は非常に少ないため、自分のために整理し、後で思い出すための参考にしたいと思います。また、必要な人に少しでも助けや参考を提供できればと思います。(もちろん、最も早いのは直接英語のドキュメントや例を見て、API の使用を検索することです。)
インストール#
Floating UI は、パッケージマネージャーまたは CDN を通じてインストールできます。npm、yarn、または pnpm を使用している場合は、次のコマンドを実行できます。
npm install @floating-ui/dom
CDN を使用している場合は、HTML ファイルに次のタグを追加できます。
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/dom"></script>
詳細については、Getting Started | Floating UIを参照してください。
React でのインストール#
React でのインストールは、@floating-ui/react パッケージをインストールするだけです。
yarn add @floating-ui/react
Popover#
この記事では、Floating UI を使用して一般的な浮動 UI コンポーネントである **Popover(ポップオーバー)** を作成する方法を共有します。Popover は、ユーザーが特定の要素にマウスをホバーしたりクリックしたりすると表示され、追加の情報やオプションを提供する一般的な浮動 UI コンポーネントです。
ケースデモ#
以下の学習を通じて、クリックで表示されるバルーン / ポップアップを簡単に構築できます。図を参照してください。
デモデモ 👉 CodeSandbox
useFloating#
まずはコアフック ——useFloating
useFloating
フックは、浮動要素に位置決めとコンテキストを提供します。いくつかの情報を渡す必要があります:
open
:ポップアップの開いている状態。onOpenChange
: ポップアップが開いたり閉じたりする際に呼び出されるコールバック関数。floating-ui 内部でこれを使用してisOpen
状態を更新します。placement
:浮動要素が参照要素に対してどの位置に配置されるか、デフォルト位置は'bottom'
ですが、ツールチップをボタンに関連する任意の位置に配置したい場合があります。そのために、Floating UI にはplacement
オプションがあります。- 使用可能な基本位置は
'top'
、'right'
、'bottom'
、'left'
です。 - これらの基本位置のそれぞれは、
-start
と-end
の形式で整列します。たとえば、'right-start'
や'bottom-end'
です。これにより、ツールチップをボタンの端に合わせることができ、中央に配置するのではなくなります。
- 使用可能な基本位置は
middleware
:ミドルウェアをインポートし、配列に渡して、ポップアップが画面上に留まることを保証します。最終的にどこに配置されるかに関係なく。autoPlacement
浮動要素に最適な位置がわからない場合や、明示的に指定したくない場合に便利なミドルウェアです。- Middleware | Floating UI 他のミドルウェアについては、ドキュメントを参照してください。これには、offset(オフセットを設定)、arrow(小さな矢印を追加)、shift(指定された軸に沿って浮動要素を移動させて可視性を保つ)、flip(浮動要素の位置を反転させて可視性を保つ)、inline(複数行にわたるインライン参照要素の位置決めを改善)などの便利なミドルウェアがあります。
whileElementsMounted
:参照要素と浮動要素の両方がマウントされている場合にのみ、必要に応じて位置を更新し、浮動要素が参照要素に固定され続けることを保証します。- autoUpdate ユーザーがスクロールしたり画面サイズを変更したりすると、浮動要素が参照要素から外れる可能性があるため、再度位置を更新して固定状態を保つ必要があります。
import { useFloating, autoUpdate, offset, flip, shift } from '@floating-ui/react';
function Popover() {
const [isOpen, setIsOpen] = useState(false);
const { x, y, strategy, refs, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [offset(10), flip(), shift()],
placement: 'top',
whileElementsMounted: autoUpdate,
});
}
Interaction hooks - useInteractions#
useInteractions
を使用して、構成オブジェクトを渡すことで、浮動要素が開閉動作やスクリーンリーダーへのアクセスなどの追加機能を拡張できるようになります。この例では、useClick()
が参照要素をクリックしたときにポップアップを開閉する機能を追加します。useDismiss()
は、ユーザーがesc
キーを押したりポップアップの外をクリックしたときにポップアップを閉じる機能を追加します。useRole()
は、ポップアップと参照要素に正しいARIA
属性を追加します。最後に、useInteractions()
は、すべての props を prop getters に統合し、レンダリングに使用できます。[^1]
いくつかの構成オブジェクト。浮動要素が開閉動作やスクリーンリーダーへのアクセスなどの追加機能を拡張できるようにします。
import {
// ...
useClick,
useDismiss,
useRole,
useInteractions,
} from '@floating-ui/react';
function Popover() {
const [isOpen, setIsOpen] = useState(false);
const { x, y, reference, floating, strategy, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [offset(10), flip(), shift()],
whileElementsMounted: autoUpdate,
});
const click = useClick(context);
const dismiss = useDismiss(context);
const role = useRole(context);
// すべてのインタラクションをprop gettersに統合
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
}
useClick()
は、参照要素をクリックしたときにポップアップを開閉する機能を追加します。useDismiss()
は、ユーザーがesc
キーを押したりポップアップの外をクリックしたときにポップアップを閉じる機能を追加します。useRole()
は、ポップアップと参照要素にdialog
の正しいARIA
属性を追加します。
最後に、useInteractions()
は、すべての props を prop getters に統合し、レンダリングに使用できます。すべての props をレンダリングに使用できる prop getters に統合します。
レンダリング#
すべての変数とフックを設定したので、要素をレンダリングできます。
function Popover() {
// ...
return (
<>
<button ref={refs.setReference} {...getReferenceProps()}>
参照要素
</button>
{isOpen && (
<FloatingFocusManager context={context} modal={false}>
<div
ref={refs.setFloating}
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
width: 'max-content',
}}
{...getFloatingProps()}
>
ポップオーバー要素
</div>
</FloatingFocusManager>
)}
</>
);
}
getReferenceProps
&getFloatingProps
はuseInteractions
によって返され、関連要素に伝播されます。これらには、onClick
、aria-expanded
などの関連する props が含まれています。<FloatingFocusManager />
は、モーダルまたは非モーダルの動作(modal and non-modal)のポップアップフォーカスを管理するコンポーネントです。これは浮動要素を直接包むべきであり、ポップオーバーがレンダリングされるときのみレンダリングされるべきです。FloatingFocusManager
——FloatingFocusManager
docs.
モーダルと非モーダルの動作 モーダルまたは非モーダルの動作#
Modal and non-modal behavior モーダルまたは非モーダルの動作
上記の例では、非モーダルのフォーカス管理を使用しましたが、ポップアップのフォーカス管理の動作はモーダルまたは非モーダルのいずれかです。それらの違いは次のとおりです:
モーダル#
- ポップアップとその内容は、唯一のフォーカスを受け取ることができる要素です。ポップアップが開いているとき、ユーザーはページの残りの部分(スクリーンリーダーも含む)とは相互作用できません、ポップアップが閉じるまで。
- 明示的な閉じるボタンが必要です(視覚的に隠すこともできます)。
この動作はデフォルトの動作です:
<FloatingFocusManager context={context}>
<div />
</FloatingFocusManager>
非モーダルの動作#
- ポップアップとその内容はフォーカスを受け取ることができますが、ユーザーはページの残りの部分と相互作用することができます。
- 外部で Tab キーを押すと、ポップアップはフォーカスを失ったときに自動的に閉じ、自然な DOM 順序の次のフォーカス可能な要素がフォーカスを受け取ります。
- 明示的な閉じるボタンは必要ありません。
この動作は、次のようにmodal
プロパティを使用して構成できます:
<FloatingFocusManager context={context} modal={false}>
<div />
</FloatingFocusManager>
完全なコード#
少しの最適化を経て、シンプルにこのような Popover コンポーネントを作成できます。
import {
FloatingFocusManager,
Placement,
autoUpdate,
useFloating,
useInteractions,
shift,
offset,
flip,
useClick,
useRole,
useDismiss,
} from '@floating-ui/react';
import { cloneElement, useEffect, useId, useState } from 'react';
type PopoverProps = {
disabled?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
render: (props: { close: () => void }) => React.ReactNode;
placement?: Placement;
children: JSX.Element;
className?: string;
};
const Popover = ({ disabled, children, render, placement, open: passedOpen, onOpenChange }: PopoverProps) => {
const [open, setOpen] = useState(false);
const { x, y, reference, floating, strategy, context } = useFloating({
open,
onOpenChange: (op) => {
if (disabled) return;
setOpen(op);
onOpenChange?.(op);
},
middleware: [offset(10), flip(), shift()],
placement,
whileElementsMounted: autoUpdate,
});
const { getReferenceProps, getFloatingProps } = useInteractions([useClick(context), useRole(context), useDismiss(context)]);
const headingId = useId();
useEffect(() => {
if (passedOpen === undefined) return;
setOpen(passedOpen);
}, [passedOpen]);
return (
<>
{cloneElement(children, getReferenceProps({ ref: reference, ...children.props }))}
{open && (
<FloatingFocusManager context={context} modal={false}>
<div
ref={floating}
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
}}
className="z-10 bg-yellow-400 p-2 outline-none"
aria-labelledby={headingId}
{...getFloatingProps()}
>
{render({
close: () => {
setOpen(false);
onOpenChange?.(false);
},
})}
</div>
</FloatingFocusManager>
)}
</>
);
};
export default Popover;
結語#
次回はDialog | Floating UIの作成と封装について紹介します。FloatingPortal と FloatingOverlay の紹介も含まれ、ポップアップと似たインタラクションがありますが、2 つの主な違いがあります:
- それはモーダルであり、ダイアログの背後に背景を表示し、後ろの内容を暗くし、ページの残りの部分にアクセスできなくします。
- それはビューポートの中央に配置され、特定の参照要素に固定されません。
ぜひ、Floating UI 公式ドキュメントを読むことをお勧めします。その思想が非常に好きで、中間ウェアやフックの抽象度も素晴らしいです。