右クリックで開くメニューを作りたい時がしばしばあります。この記事ではその方法の起点を大雑把に紹介し、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) };
}
このようなどこでも開けるメニューを作ることで複雑で細かい操作項目をメニュー内に押し込めることができます。