1. ホーム
  2. WordPress

【WordPress】ResizableBox の使い方|カスタムブロックをドラッグでリサイズできるようにする方法

Share

カスタムブロックの高さや幅を調整したいとき、サイドバーの RangeControl で数値を動かすのが一番簡単な方法です。ただ、実際に使う人からすると「ブロックの端をドラッグして変えたい」と思うのが自然でしょう。コア(WordPress 本体)の画像ブロックやスペーサーブロックも、まさにその操作ができます。

これを自分のブロックで実現するための部品が、@wordpress/components の ResizableBox です。この記事では、基本の組み込み方、ハンドルの位置を絞る enable、ドラッグ中の選択を止める toggleSelection、保存側への反映、そしてハンドルが出てこないときの原因までを解説します。

ResizableBox は中身を囲むだけのラッパー

ResizableBox は、中に入れた要素の周囲にドラッグ用のハンドルを表示してくれるコンポーネントです。ドラッグされたサイズを自動でブロックの属性に保存してくれるわけではなく、「どれだけドラッグされたか」を教えてくれるところまでが仕事です。受け取った値を属性に書き込むのは自分で行います。

まず、サイズを保存するための属性を block.json に用意します。

block.json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "webool/notice-box",
  "title": "お知らせボックス",
  "category": "design",
  "attributes": {
    "height": {
      "type": "number",
      "default": 200
    },
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    }
  },
  "editorScript": "file:./index.js",
  "style": "file:./style-index.css"
}

height は数値で持ちます。default を入れておくと、ブロックを挿入した直後から高さが決まるので扱いが楽です。

edit.js に組み込む

最小の実装は次のようになります。useBlockProps() を付けた外側の要素の中に ResizableBox を置き、その中に本来の中身を入れます。

edit.js
import { useBlockProps, RichText } from '@wordpress/block-editor';
import { ResizableBox } from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {
	const { height, content } = attributes;
	const blockProps = useBlockProps();

	return (
		<div { ...blockProps }>
			<ResizableBox
				size={ { width: '100%', height } }
				minHeight={ 80 }
				maxHeight={ 600 }
				enable={ {
					top: false,
					right: false,
					bottom: true,
					left: false,
					topRight: false,
					bottomRight: false,
					bottomLeft: false,
					topLeft: false,
				} }
				onResizeStop={ ( event, direction, elt, delta ) => {
					setAttributes( {
						height: parseInt( height + delta.height, 10 ),
					} );
				} }
			>
				<div className="notice-box" style={ { height: '100%' } }>
					<RichText
						tagName="p"
						value={ content }
						onChange={ ( value ) => setAttributes( { content: value } ) }
						placeholder="お知らせの本文を入力"
					/>
				</div>
			</ResizableBox>
		</div>
	);
}

ここで押さえておきたいのが onResizeStop の第4引数 delta です。delta はドラッグによる変化量であって、変更後のサイズではありません。100px の高さを 150px までドラッグしたなら、delta.height は 50 です。そのため、元の height に足してから保存しています。ここを取り違えて delta.height をそのまま代入すると、ドラッグするたびに高さが小さくなっていくという妙な挙動になります。

主なプロパティ

プロパティ説明
size現在のサイズ。{ width, height } の形で渡す。数値のほか '100%' のような文字列も使える
enable8方向それぞれのハンドルを有効にするかを真偽値で指定する
minWidth / minHeight縮められる下限
maxWidth / maxHeight広げられる上限
onResizeStartドラッグ開始時に呼ばれる
onResizeドラッグ中に連続して呼ばれる。リアルタイムに表示を更新したいときに使う
onResizeStopドラッグ終了時に呼ばれる。( event, direction, elt, delta ) を受け取る
lockAspectRatiotrue にすると縦横比を保ったままリサイズする
showHandleハンドルを表示するかどうか。ブロックの選択状態と連動させるのに使う
grid[ 10, 10 ] のように指定すると、その刻みでサイズが変わる

enable は8方向すべてを書く

enable には top・right・bottom・left の4辺と、topRight・bottomRight・bottomLeft・topLeft の4隅、合わせて8つのキーがあります。省略したキーがどう扱われるかは分かりにくいので、使う方向だけ true、残りは明示的に false と全部書いておくのが確実です。

高さだけを変えたいブロックなら bottom だけを true に。画像のように縦横を自由に変えたいなら、右下の bottomRight を有効にして lockAspectRatio と組み合わせるのが定番です。

ドラッグ中にテキストが選択されてしまうのを防ぐ

ここまでの実装で試してみると、ハンドルをドラッグしたときにエディター内のテキストが青く選択されてしまうことがあります。これはブラウザーの通常の選択動作が働いているためで、コアのスペーサーブロックなども同じ問題に対処しています。

解決には、core/block-editor ストアの toggleSelection アクションを使います。ドラッグ開始時に選択を無効化し、終了時に戻します。

edit.js
import { useBlockProps, store as blockEditorStore } from '@wordpress/block-editor';
import { ResizableBox } from '@wordpress/components';
import { useDispatch } from '@wordpress/data';

export default function Edit( { attributes, setAttributes, isSelected } ) {
	const { height } = attributes;
	const blockProps = useBlockProps();

	// エディターのテキスト選択を一時的に止めるためのアクション
	const { toggleSelection } = useDispatch( blockEditorStore );

	return (
		<div { ...blockProps }>
			<ResizableBox
				size={ { width: '100%', height } }
				minHeight={ 80 }
				showHandle={ isSelected }
				enable={ {
					top: false,
					right: false,
					bottom: true,
					left: false,
					topRight: false,
					bottomRight: false,
					bottomLeft: false,
					topLeft: false,
				} }
				onResizeStart={ () => {
					toggleSelection( false );
				} }
				onResizeStop={ ( event, direction, elt, delta ) => {
					setAttributes( {
						height: parseInt( height + delta.height, 10 ),
					} );
					toggleSelection( true );
				} }
			>
				{ /* 中身 */ }
			</ResizableBox>
		</div>
	);
}

showHandle={ isSelected } も一緒に入れました。ブロックを選択したときだけハンドルを出すようにすると、エディターの見た目がすっきりします。isSelected は edit に渡ってくるプロパティなので、そのまま使えます。

toggleSelection( true ) を書き忘れると、リサイズしたあとエディター全体でテキストを選択できなくなります。必ず対で書いてください。

保存側にサイズを反映する

ResizableBox はエディターの中だけの仕組みなので、フロントエンドの表示には一切関与しません。属性に保存した値を、自分で save の出力に書き込む必要があります。

save.js
import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function save( { attributes } ) {
	const { height, content } = attributes;

	const blockProps = useBlockProps.save( {
		style: { minHeight: `${ height }px` },
	} );

	return (
		<div { ...blockProps }>
			<RichText.Content tagName="p" value={ content } />
		</div>
	);
}

高さは height ではなく minHeight で当てておくと、中身が想定より多かったときに文字がはみ出さずに済みます。固定の高さが必要な演出でなければ、minHeight のほうが実用的です。

動的ブロックとして render.php で出力する場合は、PHP 側で属性を読んでインラインスタイルを組み立てます。

render.php
<?php
$height = isset( $attributes['height'] ) ? (int) $attributes['height'] : 200;

$wrapper_attributes = get_block_wrapper_attributes(
	array(
		'style' => sprintf( 'min-height:%dpx;', $height ),
	)
);
?>
<div <?php echo $wrapper_attributes; ?>>
	<?php echo wp_kses_post( $attributes['content'] ?? '' ); ?>
</div>

うまく動かないときに見るところ

ハンドルが表示されない

まず enable の指定を確認してください。すべて false になっていれば当然出ません。次に、showHandle を isSelected と連動させている場合は、ブロックを選択しているかを確かめます。

それでも出ないときは CSS を疑います。ハンドルは .components-resizable-box__handle というクラスの要素として絶対配置で描画されるため、親要素に overflow: hidden が当たっていると外側にはみ出す部分が切り取られて見えなくなります。ブロック側のスタイルで overflow を指定していないか確認してください。

ドラッグしても高さが変わらない

size に渡している値が属性と結びついていない場合に起こります。size={ { width: '100%', height } } のように、属性の値をそのまま渡す形になっているかを見てください。ここに固定値を書いてしまうと、属性を更新しても表示が変わりません。

もうひとつ多いのが、中身の要素に高さが伝わっていないケースです。ResizableBox は自分自身のサイズを変えますが、その中の要素は指定しなければ内容分の高さしか持ちません。上の例のように、中の要素へ style={ { height: '100%' } } を渡すか、CSS で高さを継承させてください。

ドラッグするたびに値がおかしくなる

前述のとおり delta は変化量です。setAttributes( { height: delta.height } ) と書いていないか確認してください。正しくは height + delta.height です。onResize(ドラッグ中に連続で呼ばれる)で属性を更新している場合も、毎回の delta が開始時点からの累計であることを踏まえないと値がずれていきます。素直に onResizeStop だけで更新するのが安全です。

まとめ

ResizableBox は、カスタムブロックにドラッグでのリサイズ操作を追加するためのコンポーネントです。size で現在のサイズを渡し、enable でハンドルの方向を決め、onResizeStop で受け取った変化量を属性に書き戻す、というのが基本の流れになります。

実装するうえでの要点は3つです。delta は変化量なので元のサイズに足すこと、toggleSelection でドラッグ中のテキスト選択を止めること、そしてエディター専用の仕組みなので save や render.php で自分でサイズを出力することです。この3点さえ押さえておけば、コアの画像ブロックやスペーサーブロックと同じ操作感を自分のブロックにも持ち込めます。

参考ページ