社区新闻

基于区块的大型菜单入门指南

查看官方原文 ↗ 发布于

大型菜单在网页设计中应用广泛,随着区块主题的出现,我一直在寻找一种方法,能将其无缝集成到 WordPress 的导航区块中。即将于三月底发布的 WordPress 6.5 包含了交互性 API 等功能,这将最终助力实现基于区块的大型菜单。在本文中,我将引导你使用这些新工具完成一种实现方案。

并非所有站点都需要大型菜单。即使本教程乍看之下与你的工作流无关,我仍建议你阅读一下。本文更多是关于如何利用 WordPress 6.5 的新功能来架构一个区块插件。我们将讨论的许多概念远不止适用于大型菜单。以下是一些例子:

  • 如何创建自定义模板部件区域
  • 如何向导航区块添加自定义区块
  • 如何设置使用交互性 API 的项目
  • 如何使用核心组件来简化区块开发

在深入之前,先来看看使用 Twenty Twenty-Four 主题的效果。为了控制文章篇幅,我们不会构建一个可直接用于生产的区块,但它将为后续迭代提供一个坚实的基础。

构建大型菜单区块有多种方法,因此在开始之前,我们先来看看我在构建这个项目时的前提条件。

  • 大型菜单区块需要直接与导航区块集成
  • 其体验应与添加任何其他链接相同
  • 用户将大型菜单区块添加到导航区块后,可以从可用的“菜单模板”列表中选择一个在前端显示
  • 菜单本身是模板部件
  • 菜单模板部件在站点编辑器中创建和设计

我尽可能从核心代码中汲取灵感,最终的区块与导航链接区块非常相似。区块越像原生的 WordPress,效果越好。

环境设置

第一步是使用 @wordpress/create-block 包来搭建一个区块插件。这里我不会详述,你可以参考入门文档了解更多。

以下命令将创建一个支持使用 wp-env 并注册动态区块 mega-menu-block 的插件。你可以自由使用你偏好的插件别名和本地开发环境;wp-env 不是必需的。只需确保你运行的是 WordPress 6.5。

npx @wordpress/create-block@latest mega-menu-block --variant=dynamic --wp-env
cd mega-menu-block

在本教程中,除非另有说明,所有编辑都将在插件的 /src 文件夹中的文件上进行。

添加自定义模板部件区域

在配置区块本身之前,我们先注册将容纳每个大型菜单的模板部件区域。你可以使用 default_wp_template_part_areas 钩子添加自定义区域。

/**
 * 为大型菜单添加自定义模板部件区域到模板部件区域列表。
 *
 * @param array $areas 现有的模板部件区域数组。
 * @return array 修改后的模板部件区域数组,包含新的“Menu”区域。
 */
function outermost_mega_menu_template_part_areas( array $areas ) {
	$areas[] = array(
		'area'        => 'menu',
		'area_tag'    => 'div',
		'description' => __( 'Menu templates are used to create sections of a mega menu.', 'mega-menu-block' ),
		'icon'        => '',
		'label'       => __( 'Menu', 'mega-menu-block' ),
	);

	return $areas;
}
add_filter( 'default_wp_template_part_areas', 'outermost_mega_menu_template_part_areas' );

将此 PHP 代码放在根目录 mega-menu-block 文件夹的主插件文件中。它应该是 mega-menu-block.php,除非你选择了不同的区块别名。注意 area 设置为 menu。我们将在本教程后面用到它。

在你的本地环境中,导航到站点编辑器。你应该看到在创建新模板部件时,“Menu”区域现在可选。

站点编辑器中的“创建模板部件”模态框,包含“Menu”模板部件区域。

截至 WordPress 6.5,无法为模板部件区域分配自定义图标。选项是 headerfootersidebar。将此字段留空或指定任何其他值将显示默认图标,如上图所示。

创建一个新的 Menu 模板部件并添加一些填充内容。我选择插入 Twenty Twenty-Four 主题中的一个模式。不必太在意它的外观。我们只需要一个用于测试的已保存模板。

一个示例大型菜单模板,包含 Twenty Twenty-Four 主题的一个模式。

向导航区块添加大型菜单

现在开始构建“Mega Menu Block”区块,首先要确保用户可以在 WordPress 中将其添加到导航区块。

在终端运行 npm start 启动构建过程。导航到本地环境中的一个新页面,确认该区块在编辑器中可用。它现在看起来应该还很简单,只是 create-block 包搭建的默认区块。

插入器中的 Mega Menu Block。

现在向页面添加一个导航区块,并尝试在菜单中添加 Mega Menu Block。你将无法添加。

默认情况下,导航区块只允许一组预定义的核心区块,这由区块的 allowedBlocks 设置中定义的区块名称数组控制。然而,在 WordPress 6.5 中,你现在可以使用 blocks.registerBlockType 过滤器来添加对自定义区块的支持。

过滤器本身并不新。它遍历每个区块类型,并允许你修改每个区块的设置,你可能在开发者博客的其他教程中见过它。过滤器的回调函数接受两个参数:区块设置对象 (blockSettings) 和区块名称 (blockName)。

要使用该过滤器,首先在 index.js 文件顶部导入 addFilter

import { addFilter } from '@wordpress/hooks';

然后,在文件底部添加以下代码并保存。构建过程应该仍在运行。如果没有,请在终端运行 npm start

/**
 * 使 Mega Menu Block 可用于导航区块。
 *
 * @param {Object} blockSettings 区块的原始设置。
 * @param {string} blockName     正在修改的区块名称。
 * @return {Object} 导航区块的修改后设置,或其他区块的原始设置。
 */
const addToNavigation = ( blockSettings, blockName ) => {
	if ( blockName === 'core/navigation' ) {
		return {
			...blockSettings,
			allowedBlocks: [
				...( blockSettings.allowedBlocks ?? [] ),
				'create-block/mega-menu-block',
			],
		};
	}
	return blockSettings;
};
addFilter(
	'blocks.registerBlockType',
	'add-mega-menu-block-to-navigation',
	addToNavigation
);

在修改任何设置之前,通过检查 blockName 来确保我们只针对导航区块,这一点很重要。然后,将 create-block/mega-menu-block 附加到 allowedBlocks 设置。这是区块的 name,在 block.json 中定义。

保存文件并刷新页面后,你现在应该能够将 Mega Menu Block 添加到菜单中了。

Mega Menu Block 现在可以添加到导航区块中。

更新 block.json、区块样式并添加自定义图标

为了本教程,我将把 Mega Menu Block 的编辑器功能保持在基础水平。你随时可以扩展它,我鼓励你这样做。

在编辑器中,该区块需要两个功能:一种在导航区块内设置菜单项标签的方法,以及一种为大型菜单选择菜单模板部件的机制。标签和选定的模板部件数据必须存储为区块属性

我们将从更新 block.json 文件开始,以包含这些属性并进行一些额外的清理。以下是待办事项:

  • 添加 attributes 属性
  • 添加类型为 stringlabel 属性
  • 添加类型为 stringmenuSlug 属性
  • 添加 parent 属性并将其设置为 core/navigation,以便用户不能在导航区块之外插入该区块
  • 添加排版支持以匹配导航区块中可用的其他链接区块
  • title 属性更新为“Mega Menu”
  • description 属性更新为“向导航添加大型菜单。”
  • category 属性更新为“design”
  • 移除 icon 属性(我们稍后将添加自定义图标)

更新后的 block.json 文件应如下所示。

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "create-block/mega-menu-block",
	"version": "0.1.0",
	"title": "Mega Menu",
	"category": "design",
	"description": "Add a mega menu to your navigation.",
	"parent": [ "core/navigation" ],
	"example": {},
	"attributes": {
		"label": {
			"type": "string"
		},
		"menuSlug": {
			"type": "string"
		}
	},
	"supports": {
		"html": false,
		"typography": {
			"fontSize": true,
			"lineHeight": true,
			"__experimentalFontFamily": true,
			"__experimentalFontWeight": true,
			"__experimentalFontStyle": true,
			"__experimentalTextTransform": true,
			"__experimentalTextDecoration": true,
			"__experimentalLetterSpacing": true,
			"__experimentalDefaultControls": {
				"fontSize": true
			}
		}
	},
	"textdomain": "mega-menu-block",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"render": "file:./render.php",
	"viewScript": "file:./view.js"
}

接下来,通过移除 style.scssedit.scss 文件中的所有内容,清除 create-block 设置的默认样式。我们将在本教程后面引入自定义样式。记得保存对这两个文件的更改。

这最后的清理步骤并非必需,但我总是喜欢添加自定义区块图标。block.json 中的 icon 属性允许你指定一个 Dashicon 别名,但你不能通过这种方式添加 SVG 图标。相反,让我们直接将图标添加到 index.js 文件中的 registerBlockType() 函数。

const megaMenuIcon = (
	<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg">
		<path d="M20,12 L4,12 L4,13.5 L20,13.5 L20,12 Z M10,6.5 L4,6.5 L4,8 L10,8 L10,6.5 Z M20,17.5 L4,17.5 L4,19 L20,19 L20,17.5 Z M20,5.62462724 L16.000015,9 L12,5.62462724 L12.9791165,4.5 L16.000015,7.04920972 L19.0208935,4.5 L20,5.62462724 Z"></path>
	</svg>
);

registerBlockType( metadata.name, {
	icon: megaMenuIcon,
	edit: Edit,
} );

保存文件并刷新页面。“Mega Menu”区块在编辑器中应该看起来像这样。注意由区块支持提供的排版面板。

清理过程后编辑器中的 Mega Menu 区块。

添加编辑器用户界面

初始设置完成后,现在是为区块添加控件的时候了,这些控件允许用户设置 labelmenuSlug 属性。

edit.js 文件中,让我们从更新 Edit 组件开始,以包含属性 attributessetAttributes。从 attributes 中提取 labelmenuSlug。我们稍后将使用 setAttributes 根据用户交互更新值。

最后,编辑器中的标记默认使用 <p> 标签。将其更新为 <div>。结果应如下所示。

export default function Edit( { attributes, setAttributes } ) {
	const { label, menuSlug } = attributes;

	return (
		<div { ...useBlockProps() }>
			{ __(
				'Mega Menu Block – hello from the editor!',
				'mega-menu-block'
			) }
		</div>
	);
}

导入组件、钩子和函数

接下来,我们需要导入一些用于构建区块界面的项目。为了简洁起见,让我们一次性全部添加。我们需要:

  • InspectorControls:一个在侧边栏渲染区块特定设置的组件。
  • PanelBody:在 InspectorControls 内部使用的组件,用于将相关的 UI 控件分组到可折叠容器中,以便更好地组织。
  • TextControl:一个表单输入组件,允许用户输入和编辑文本。
  • ComboboxControl:一个组合输入和下拉菜单组件,允许用户从预定义选项中选择。
  • RichText:一个提供富文本编辑界面的组件。
  • useEntityRecords:一个 React 钩子,根据指定的查询参数从 WordPress 数据库中检索实体列表(例如,文章、页面、模板部件)。

更新 edit.js 文件顶部的导入,包含以下内容。

import { __ } from '@wordpress/i18n';
import { InspectorControls, RichText, useBlockProps } from '@wordpress/block-editor';
import { ComboboxControl, PanelBody, TextControl } from '@wordpress/components';
import { useEntityRecords } from '@wordpress/core-data';

获取菜单模板部分

我们已经包含了必要的导入,块现在可以访问和属性。唯一缺少的信息是可用的“菜单”模板部分。labelmenuSlug

我们用这个钩子抓取所有类型的实体,然后解析所有模板部分的返回记录,区域为 ,就像本教程之前定义的那样。代码应该在组件的返回语句之前添加,应该看起来像这样。useEntityRecordswp_template_partmenuEdit

// Fetch all template parts.
const { hasResolved, records } = useEntityRecords(
	'postType',
	'wp_template_part',
	{ per_page: -1 }
);

let menuOptions = [];

// Filter the template parts for those in the 'menu' area.
if ( hasResolved ) {
	menuOptions = records
		.filter( ( item ) => item.area === 'menu' )
		.map( ( item ) => ( {
			label: item.title.rendered, // Title of the template part.
			value: item.slug,           // Template part slug.
		} ) );
}

注意,我们可以通过将 设为 来检索所有记录。per_page-1

该变量表示获取模板部分的请求是否已完成。一旦取物过程解析(是),代码会通过(取出的模板部分)筛选出属于该区域的模板部分。hasResolvedhasResolvedtruerecordsmenu

对于该区域中的每个模板部件,代码构建一个包含模板部件标题和字条的对象。这些对象被收集到数组中,然后用它来表示组件中的选项。menumenuOptionsComboboxControl

想了解更多关于获取实体记录的信息,可以参考文章 useEntityRecords:一种更简单的获取 WordPress 数据的方法

添加设置面板

我们拥有构建该模块设置面板所需的全部数据。为此,我们先在 return 语句中添加一个组件。然后添加一个属性设置为“设置”的组件。核心块通常默认会打开设置面板,所以将属性设置为 。InspectorControlsPanelBodytitleinitialOpentrue

组件更新后的返回语句应为:Edit


return (
	<>
		<InspectorControls>
			<PanelBody
				title={ __( 'Settings', 'mega-menu-block' ) }
				initialOpen={ true }
			>
				Testing
			</PanelBody>
		</InspectorControls>
		<div { ...useBlockProps() }>
			{ __(
				'Mega Menu Block – hello from the editor!',
				'mega-menu-block'
			) }
		</div>
	</>
);

在 React 中,组件只能返回单个元素,这也是为什么上面代码中所有内容都被封装在片段()中。<>...</>

保存文件,并在编辑器中预览Mega Menu块。当选中该块时,你应该会看到一个“设置”面板。edit.js

设置面板的初始状态。

接下来,我们用组件让用户修改属性,并用组件选择菜单模板并设置属性。TextControllabelComboboxControlmenuSlug

<PanelBody
	title={ __( 'Settings', 'mega-menu-block' ) }
	initialOpen={ true }
>
	<TextControl
		label={ __( 'Label', 'mega-menu-block' ) }
		type="text"
		value={ label }
		onChange={ ( value ) =>
			setAttributes( { label: value } )
		}
		autoComplete="off"
	/>
	<ComboboxControl
		label={ __( 'Menu Template', 'mega-menu-block' ) }
		value={ menuSlug }
		options={ menuOptions }
		onChange={ ( slugValue ) =>
			setAttributes( { menuSlug: slugValue } )
		}
	/>
</PanelBody>

注意,我们用 来根据用户互动更新 和 的数值。setAttributeslabelmenuSlug

保存文件后,控制项会在设置面板中出现。尝试修改标签并选择菜单模板。更新页面时确认数值已被保存。edit.js

虽然超出本教程范围,但如果你打算将该模块分发给用户,如果没有菜单模板部分,你需要添加某种通知。也许还可以提供一个链接,引导他们去网站编辑器创建新模板。

在画布中添加RichText字段

虽然该属性可以在设置侧边栏编辑,但这并不是一个很好的用户体验。如果你查看WordPress中导航链接块的代码,你会发现标签也可以通过编辑器画布中的组件编辑。labelRichText

编辑Mega Menu块应尽可能像原生WordPress,我们不想重新发明轮子。因此,复制相同的标记结构和 CSS 类,在导航链接块中实现该组件。这使得我们的模块能够继承核心样式,并提供一致的用户界面。RichText

<div { ...useBlockProps() }>
	<a className="wp-block-navigation-item__content">
		<RichText
			identifier="label"
			className="wp-block-navigation-item__label"
			value={ label }
			onChange={ ( labelValue ) =>
				setAttributes( {
					label: labelValue,
				} )
			}
			aria-label={ __(
				'Mega menu link text',
				'mega-menu-block'
			) }
			placeholder={ __( 'Add label…', 'mega-menu-block' ) }
			allowedFormats={ [
				'core/bold',
				'core/italic',
				'core/image',
				'core/strikethrough',
			] }
		/>
	</a>
</div>

Mega Menu 模块的编辑器组件提供了我们所需的基本功能,现在已经完成。让我们把注意力转向前端。查看完整edit.js文件

前端配置

按照上面步骤,Mega Menu 块的前端应该是这样的。

前端初始状态,菜单菜单块显示在导航块中。

该区块作为导航区块的一部分被正确显示,但默认输出依然存在。我们来解决这个问题。

更新块标记和基础样式

导航到文件,将和属性分配给变量。如果两者都不存在,则加一个返回的检定。我们不想展示没有标签的超级菜单,也不想显示没有超级菜单的标签。render.phplabelmenuSlugnull

最后,将默认信息替换为菜单标签。

<?php
$label     = esc_html( $attributes['label'] ?? '' );
$menu_slug = esc_attr( $attributes['menuSlug'] ?? '');

// Don't display the mega menu link if there is no label or no menu slug.
if ( ! $label || ! $menu_slug ) {
	return null;	
}
?>
<p <?php echo get_block_wrapper_attributes(); ?>>
	<?php echo $label; ?>
</p>

导航块是一个无序列表(),因此接下来更新标记,确保块以列表项()形式呈现。菜单标签也应包含在一个元素中,点击后切换到Mega菜单。<ul><li><button>

<li <?php echo get_block_wrapper_attributes(); ?>>
 	<button><?php echo $label; ?></button>
</li>

Browsers provide default styles to elements, which we don’t want. Let’s add a few reset styles in the file.<button>style.scss

// Reset button styles.
.wp-block-create-block-mega-menu-block {
	button {
		background-color: initial;
		border: none;
		color: currentColor;
		cursor: pointer;
		font-family: inherit;
		font-size: inherit;
		font-style: inherit;
		font-weight: inherit;
		line-height: inherit;
		padding: 0;
		text-transform: inherit;
	}
}

Note that the main class for the block will be . WordPress generates this automatically using the wp-block-create-block-mega-menu-blockget_block_wrapper_attributes() function. The block is converted to kebab case and prefixed with .namewp-block-

Save both the and files. When you refresh the page on the front end, it should look something like this.render.phpstyle.scss

此时,菜单项的可视化表示完成。

添加超级菜单

现在是时候用block_template_part()函数,该函数接受变量。为了简化后续步骤,将该函数与类包裹在 a。$menu_slug<div>wp-block-create-block-mega-menu-block__menu-container

<li <?php echo get_block_wrapper_attributes(); ?>>
 	<button><?php echo $label; ?></button>
	<div class="wp-block-create-block-mega-menu-block__menu-container">
		<?php echo block_template_part( $menu_slug ); ?>
	</div>
</li>

最后一步是在菜单容器内添加一个元素。用户可以点击按钮隐藏超级菜单。这个按钮可以是文字或图标。我决定使用WordPress组件库中的图标。<button>close

文件现在应该是这样的。render.php

<?php
$label       = esc_html( $attributes['label'] ?? '' );
$menu_slug   = esc_attr( $attributes['menuSlug'] ?? '');
$close_icon  = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false"><path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path></svg>';

// Don't display the mega menu link if there is no label or no menu slug.
if ( ! $label || ! $menu_slug ) {
	return null;	
}
?>
<li <?php echo get_block_wrapper_attributes(); ?>>
	<button><?php echo $label; ?></button>
	<div class="wp-block-create-block-mega-menu-block__menu-container">
		<?php echo block_template_part( $menu_slug ); ?>
		<button 
			aria-label="<?php echo __( 'Close menu', 'mega-menu' ); ?>" 
			class="menu-container__close-button" 
			type="button" 
		>
			<?php echo $close_icon; ?>
		</button>
	</div>
</li>

刷新页面,你会看到模板部分和关闭按钮在前端渲染。看起来不太好,但我们会通过交互性API和更多的样式来解决这个问题。

添加交互功能(交互API)

为了管理我们超级菜单的行为,我们将利用Interactivity API。这一部分不会涵盖API的所有细节,但应该能为你提供交互式模块结构和功能基础。

我在尝试基于方块的超级菜单时,从导航区块获得了灵感。交互性API驱动了许多部分,源代码为生产准备模块所需的交互水平提供了良好的模板。

在这个教程中,我们只讲基础知识。当用户点击菜单项时,应该会切换到超级菜单。如果我们想涵盖构建一个响应点击、悬停和焦点状态且完全响应的模块,就需要更长的指南。

我们需要做三件事:

  • 更新块和插件以支持交互性 API
  • 在前端标记中添加指令,以支持模块内的特定交互
  • 创建一个存储,包含所需交互性所需的逻辑(状态、动作或回调)

新增对交互性 API 的支持

我们的构建过程依赖于wp-scripts,在脚手架过程中引入。我们需要调整一些内容以适应交互性API。create-block

如果构建过程正在运行,请停止。接着,打开文件,在和脚本上添加标志。调整应该是这样的。package.json--experimental-modulesbuildstart

"scripts": {
	"build": "wp-scripts build --webpack-copy-php --experimental-modules",
	"format": "wp-scripts format",
	"lint:css": "wp-scripts lint-style",
	"lint:js": "wp-scripts lint-js",
	"lint:js:src": "wp-scripts lint-js ./src --fix",
	"packages-update": "wp-scripts packages-update",
	"plugin-zip": "wp-scripts plugin-zip",
	"start": "wp-scripts start --webpack-copy-php --experimental-modules",
	"env": "wp-env"
},

在文件中,将属性更改为block.jsonviewScriptviewScriptModule并且存钱。

	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"render": "file:./render.php",
	"viewScriptModule": "file:./view.js"
}

通过终端运行重新开始构建过程,确认所有更改都正确应用。前端,刷新页面并检查浏览器控制台。你应该看看这条信息:npm start

Hello World! (from create-block-mega-menu-block block)

在 WordPress 6.5 中,交互性 API 脚本需要新模块,因此任何依赖该 API 的 JavaScript 块必须通过以下方式排队viewScriptModule而非 。考虑到模块的要求,旗帜说明了如何正确构建。viewScript--experimental-moduleswp-scriptsview.js

虽然该标志技术上是实验性的,但它只影响方块插件的构建过程,且安全使用。一旦插件构建完成()并准备生产,它就不再依赖这个实验性标志。npm run build

最后,为了表明该块支持交互API,可以在块文件的部分添加内容。"interactivity": truesupportsblock.json

"supports": {
	"html": false,
	"interactivity": true,
	"typography": {
		...
	}
},

请参阅块编辑器手册,以了解“交互性”支持属性的更详细描述。

添加指令

指令是添加到块标记中的自定义属性,用于实现“交互”。交互式API指令使用前缀。data-

这是我们为Mega Menu块所需的指令列表。请点击链接查看代码示例及更多相关信息:

  • wp-interactive:实现了DOM元素及其子节点的交互性
  • wp-context:定义了供DOM元素及其子节点使用的本地状态
  • wp-bind:基于布尔值或字符串值为元素设置HTML属性
  • wp-on: 对调度的DOM事件(、、、等)运行代码。clickfocusoutkeydown

指令总是必不可少,并且接受命名空间。使用块名是个好习惯,除非你需要更高级的实现。将此指令添加到模块的主HTML元素中。wp-interactive

<li
	<?php echo $wrapper_attributes; ?>
	data-wp-interactive="create-block/mega-menu-block"
>
	...
</li>

接下来,也把指令添加到元素中。我们会用它来追踪超级菜单的状态。是开着的还是闭着的?wp-context<li>

对于该状态,我们使用一个变量,初始状态设为 。指令接受字符串化的JSON作为值,所以应该是这样的。isMenuOpenfalse

<li
	<?php echo $wrapper_attributes; ?>
	data-wp-interactive="create-block/mega-menu-block"
	data-wp-context='{ "isMenuOpen": false }'
>
	...
</li>

让我们来思考一下超级菜单应该如何运作。

如果用户第一次点击菜单标签元素,会显示超级菜单。该动作应设置为 。如果用户再次点击按钮,且是,隐藏菜单并设置为 。<button>isMenuOpentrueisMenuOpentrueisMenuOpenfalse

为了处理这种交互,我们为一个事件添加一个指令,看起来像 。指令接受回调,每次触发相关事件时都会执行回调。我们会在本教程后面创建这个。目前,将指令设为 。wp-onclickdata-wp-on–clickactions.toggleMenu

<li
	<?php echo $wrapper_attributes; ?>
	data-wp-interactive="create-block/mega-menu-block"
	data-wp-context='{ "isMenuOpen": false }'
>
	<button
		data-wp-on--click="actions.toggleMenu"
	>
		<?php echo $label; ?>
	</button>
	...
</li>

接下来,如果 是,我们使用 a 来设置属性。这将结合一些自定义样式来控制超级菜单的可见性,这些样式我们稍后会添加。wp-bindaria-expanded=trueisMenuOpentrue

<li
	<?php echo $wrapper_attributes; ?>
	data-wp-interactive="create-block/mega-menu-block"
	data-wp-context='{ "isMenuOpen": false }'
>
	<button
		data-wp-on--click="actions.toggleMenu"
		data-wp-bind--aria-expanded="context.isMenuOpen"
	>
		<?php echo $label; ?>
	</button>
	...
</li>

You could take other approaches besides using the attribute, such as adding a custom class using the aria-expandedwp-class directive.

The last directive to add is for the element within the mega menu that, when clicked, will close it. Again, let’s use the directive, but we’ll pass the callback instead of .<button>wp-onaction.closeMenuaction.toggleMenu

完整的区块标记,加上指令,应该是这样的。

<li 
	<?php echo get_block_wrapper_attributes(); ?>
	data-wp-interactive="create-block/mega-menu-block"
	data-wp-context='{ "isMenuOpen": false }'
>
	<button
		data-wp-on--click="actions.toggleMenu"
		data-wp-bind--aria-expanded="context.isMenuOpen"
	>
		<?php echo $label; ?>
	</button>
	<div class="wp-block-create-block-mega-menu-block__menu-container">
		<?php echo block_template_part( $menu_slug ); ?>
		<button 
			aria-label="<?php echo __( 'Close menu', 'mega-menu' ); ?>" 
			class="menu-container__close-button" 
			type="button" 
			data-wp-on--click="actions.closeMenu"
		>
			<?php echo $close_icon; ?>
		</button>
	</div>
</li>

添加商店

此时,指令已经没有任何作用了。如果你保存并查看前端,情况不会有任何变化。你必须创建一个存储,定义指令中指定的交互,具体和。render.phpaction.toggleMenuaction.openMenu

先打开文件,删除默认的控制台语句。然后,从包装中导入。我们也导入 。这将使我们能够获得块的上下文并确定当前值。view.jsstore@wordpress/interactivitygetContextisMenuOpen

import { store, getContext } from '@wordpress/interactivity';

这个教程里的内容相当基础。我们所需要做的就是创建能够切换和关闭超级菜单的操作,你可以通过将 的值设置为 或 来实现。store()isMenuOpentruefalse

接受使用指令定义的命名空间,以及包含动作、状态、回调等的对象。这就是我们将定义和的地方。store()wp-interactivityaction.toggleMenuaction.openMenu

完整的文件应该大致是这样的。view.js

/**
 * WordPress dependencies
 */
import { store, getContext } from '@wordpress/interactivity';

const { actions } = store( 'create-block/mega-menu-block', {
	actions: {
		toggleMenu() {
			const context = getContext();

			if ( context.isMenuOpen ) {
				actions.closeMenu();
			} else {
				context.isMenuOpen = true;
			}
		},
		closeMenu() {
            			const context = getContext();
			context.isMenuOpen = false;
		},
	}
} );

你可以在官方文档中了解更多关于店铺结构的知识。

添加样式

最后一步是添加自定义样式。他们必须默认隐藏超级菜单,在主元素时显示,设置菜单内容的位置和宽度,并样式关闭按钮。aria-expanded=true<button>

打开文件并更新到符合以下内容。style.scss

.wp-block-create-block-mega-menu-block {

	// Reset button styles.
	button {
		background-color: initial;
		border: none;
		color: currentColor;
		cursor: pointer;
		font-family: inherit;
		font-size: inherit;
		font-style: inherit;
		font-weight: inherit;
		line-height: inherit;
		padding: 0;
		text-transform: inherit;
	}

	.wp-block-create-block-mega-menu-block__menu-container {
		height: auto;
		right: 0;
		opacity: 0;
		overflow: hidden;
		position: absolute;
		top: 40px;
		transition: opacity .1s linear;
		visibility: hidden;
		width: var(--wp--style--global--wide-size);
		z-index: 2;

		.menu-container__close-button {
			align-items: center;
			-webkit-backdrop-filter: blur(16px) saturate(180%);
			backdrop-filter: blur(16px) saturate(180%);
			background-color: #ffffffba;
			border: none;
			border-radius: 999px;
			cursor: pointer;
			display: flex;
			justify-content: center;
			opacity: 0;
			padding: 4px;
			position: absolute;
			right: 12px;
			text-align: center;
			top: 12px;
			transition: opacity .2s ease;
			z-index: 100;
	
			// Show the close button when focused (for keyboard navigation)
			&:focus {
				opacity: 1;
			}
		}

		// Show the close button when the mega menu is hovered.
		&:hover {
			.menu-container__close-button {
				opacity: 1;
			}
		}
	}

// Show the mega menu when aria-expanded is true.
	button[aria-expanded=true] {
		&~.wp-block-create-block-mega-menu-block__menu-container {
			opacity: 1;
			overflow: visible;
			visibility: visible;
		}
	}
}

我不会一遍遍讲这些代码。设计超级菜单是构建这个区块最具挑战性的部分。

从菜单的位置、内容宽度到移动端菜单的外观,有许多因素需要考虑。此外,每个网站设计都不同,每个网站都有不同的需求。我鼓励你尝试,打造属于你自己的风格。

下一步

好吧,这里是个值得停下来的好点。虽然这个教程的成果远未达到生产准备阶段,但它提供了一个扎实的迭代框架。如果你有兴趣进一步推进这个区块,以下是一些需要修复的问题和改进,以改善该区块。

  • 超级菜单的位置应根据导航块的位置和浏览器窗口的大小进行调整
  • 允许用户配置每个超级菜单的宽度及其相对于导航块的位置
  • 新增对焦点状态、键盘导航及附加辅助功能的支持
  • 增加垂直定位导航块的支持
  • 增加移动端和平板电脑支持。
  • 在菜单项中添加标题和描述属性,以与其他链接块保持一致
  • 在菜单项上添加一个图标,表示它打开了超级菜单,类似于带有子菜单的链接
  • 提升编辑器的用户体验

想要更完整的例子,可以看看我实验性的Mega Menu模块,这个教程就是基于它。虽然还远未达到量产阶段,但已经涵盖了上述许多内容。

有很多方法可以构建Mega Menu方块。本教程的方法试图与导航块集成,尽可能提供原生的 WordPress 体验。此外,使用模板部分将大型菜单的设计与导航块分离,我觉得效果很好。

但如果你已经探索过其他方法,请在评论区分享。巨型菜单是WordPress中最受欢迎的功能之一,把它们做好是一项值得满足的挑战。