右クリックで開くメニューを作りたい時がしばしばあります。この記事ではその方法の起点を大雑把に紹介し、React での実例を紹介します。
ブラウザ上の戻るなどのメニューを開くイベントには contextmenu イベントが割り当てられています。素の JavaScript で右クリックメニューに関するなんやかんやをしたい時は contextmenu イベントから順に色々と組み上げていくのが良いです。そうすれば後述のライブラリに縛られない自由なモノが作れます。
ContextMenus – Archive of obsolete content | MDN
Element: contextmenu event – Web APIs | MDN
`contextmenu ${JavaScript 関連の何か}`でググるとその JavaScript 関連の何かと右クリックメニューを組み合わせたモノが見つかります。React ならば次のリンク先の react-contextmenu ライブラリが右クリックメニュー制御に便利です。
vkbansal/react-contextmenu: Project is no longer maintainedreact-contextmenu – npm
使い方は次の引用コードの通り、ContextMenuTrigger と ContextMenu の二つのコンポーネントを定義し、それぞれに同じ id を割り当てるだけです。そうすると ContextMenuTrigger 内で contextmenu イベントが走った時、ContextMenu 内の JSX が画面上に表示されます。例では MenuItem という react-contextmenu 内のコンポーネントを使っていますが別に MenuItem を使う必要はありません。
// @see https://github.com/vkbansal/react-contextmenu/blob/master/examples/SimpleMenu.js import React, { Component } from 'react'; import ContextMenuTrigger from 'src/ContextMenuTrigger'; import ContextMenu from 'src/ContextMenu'; import MenuItem from 'src/MenuItem'; const MENU_TYPE = 'SIMPLE'; export default class SimpleMenu extends Component { constructor(props) { super(props); this.state = { logs: [] }; } handleClick = (e, data) => { this.setState(({ logs }) => ({ logs: [`Clicked on menu ${data.item}`, ...logs] })); } render() { return ( <div> <h3>Simple Menu</h3> <p>This demo simple usage of a context menu.</p> <ContextMenuTrigger id={MENU_TYPE} holdToDisplay={1000}> {/* この ContextMenuTrigger 内を右クリックでメニューを開く */} <div className='well'>right click to see the menu</div> </ContextMenuTrigger> <div> {this.state.logs.map((log, i) => <p key={i}>{log}</p>)} </div> <ContextMenu id={MENU_TYPE}> {/* この ContextMenu 内がメニュー実体 */} <MenuItem onClick={this.handleClick} data={{ item: 'item 1' }}>Menu Item 1</MenuItem> <MenuItem onClick={this.handleClick} data={{ item: 'item 2' }}>Menu Item 2</MenuItem> <MenuItem divider /> <MenuItem onClick={this.handleClick} data={{ item: 'item 3' }}>Menu Item 3</MenuItem> </ContextMenu> </div> ); } }
実行したデモは次です。
一々 id を定義するのが面倒ならば次の様に必要な情報を入れたコンポーネントを返す関数を作るのも手です。
import { ContextMenu as ContextMenuOrigin, ContextMenuProps, ContextMenuTrigger as ContextMenuTriggerOrigin, ContextMenuTriggerProps, } from 'react-contextmenu'; import React, { PropsWithChildren } from 'react'; /** * 右クリックメニュー用コンポーネント生成器 */ export function contextMenuProvider( id: string | null = null ): { ContextMenuTrigger: React.FC<PropsWithChildren<Partial<ContextMenuTriggerProps>>>; ContextMenuBody: React.FC<PropsWithChildren<Partial<ContextMenuProps>>>; } { const contextId: string = id || `${Math.random()}`; const ContextMenuTrigger = (props: PropsWithChildren<Partial<ContextMenuTriggerProps>>): JSX.Element => ( <ContextMenuTriggerOrigin id={contextId} {...props}> {props.children} </ContextMenuTriggerOrigin> ); const ContextMenuBody = (props: PropsWithChildren<Partial<ContextMenuProps>>): JSX.Element => { return ( <ContextMenuOrigin id={contextId} {...props} style={{ zIndex: 1e10 }}> {props.children} </ContextMenuOrigin> ); }; return { ContextMenuTrigger: React.memo(ContextMenuTrigger), ContextMenuBody: React.memo(ContextMenuBody) }; }
このようなどこでも開けるメニューを作ることで複雑で細かい操作項目をメニュー内に押し込めることができます。