超越方块样式,第三部分:构建自定义设计工具
我们共同进行的这段旅程即将抵达终点。我很期待你在阅读完本系列最后一篇文章后能创造出什么。
你错过了前两篇帖子吗?没问题。请查看这些链接,完成后再回来阅读这篇文章:
本教程是你前两课所有辛勤工作的结晶。你首先学习了如何在主题中使用 WordPress 脚本包及其原因。然后你为 Separator 区块设计了一些自定义图标样式。在这个过程中,你遇到了 Block Styles API 的一些限制。
现在,让我们更进一步,构建一个自定义编辑器控件,让你的用户可以为 Separator 区块分配图标。
你可以从 Beyond Block Styles GitHub 仓库的 part-3 分支获取本教程的最终代码。我建议你仅将其作为本教程的补充参考。
请记住,你的最终成品将如下所示:

准备工作
在第二部分结束时,我要求你删除 /resources/js/editor.js 文件中的所有代码,但保留你添加的所有其他代码。如果你当时没有这样做,请现在完成。
首先,在 /resources/js 文件夹下添加两个空的 JavaScript 文件,分别命名为 control-icon.js 和 util.js。请记住,将代码分解成更易于处理的块是一种好习惯。
添加上述文件后,你的 /resources 文件夹结构应如下所示:
/resources
-
/jsconst.jscontrol-icons.jseditor.jsutil.js
/scsseditor.scssscreen.scss
现在安装 WordPress 图标库。方便的是,它是一个可以通过单个命令安装的包,就像我们之前安装脚本包一样。为此,打开你喜欢的命令行编辑器,导航到你的主题文件夹。然后输入以下命令:
npm install @wordpress/icons --save-dev
现在你可以在脚本中使用 WordPress 图标库了。稍后添加工具栏图标时会需要它。
是时候进入真正激动人心的部分了:为 Separator 区块构建一个自定义图标选择器。
别忘了启用 watch 模式,以便 webpack 编译你的代码:
npm run start
使用 JavaScript 创建自定义控件
这是本教程系列中最大的部分。
这是你第一次为 WordPress 编辑器编写 JavaScript 吗?恭喜并欢迎你!
为每个图标注册渐变
在第二部分中,你在 /resources/js/const.js 文件中定义了一组自定义图标,该文件应如下所示:
export const ICONS = [
{ value: 'floral-heart', icon: '❦' },
{ value: 'blossom', icon: '🌼' },
// More icons...
];
图标选择器会根据所选图标自动为 Separator 区块设置渐变背景。这意味着每个图标都需要预先分配一个渐变预设。你可以使用 theme.json 中定义的预设,也可以使用默认 WordPress theme.json 中的预设。你只需要渐变的 slug 值。
现在,在你的 const.js 文件中为每个图标对象添加一个新的 gradient 属性,并赋予预设值:
export const ICONS = [
{
value: 'floral-heart',
icon: '❦'
},
{
value: 'blossom',
icon: '🌼',
gradient: 'luminous-vivid-amber-to-luminous-vivid-orange'
},
// ... 更多图标定义
];
上面的图标使用了 WordPress theme.json 文件中的渐变预设。如果你没有为图标定义渐变,它将回退到纯色。该颜色是你在第二部分编写的 CSS 中定义的。
编写一些实用函数
在构建实际控件之前,你需要编写几个自定义实用函数。你将把它们存储在本部分开头添加的新文件 /resources/js/utils.js 中。
首先,将依赖项添加到新的 utils.js 文件中。这些依赖项包括来自 const.js 文件的 ICONS 数组和 WordPress 的 TokenList 包:
// Internal dependencies.
import { ICONS } from './const';
// WordPress dependencies.
import TokenList from '@wordpress/token-list';
你需要的第一个实用函数将获取分配给 Separator 区块的图标值。
在主题中实现这一点的方式与在插件中实现的方式有很大不同。
这里你正在为主题构建一个功能,因此要避免添加自定义区块属性。而且,你已经有一个 className 属性——这正是 Block Styles API 所使用的。所以使用它,并且要知道你已经遵守了 WordPress.org 主题审查指南的一个子集。
现在,将一个名为 getIconFromClassName() 的函数添加到你的 utils.js 文件中:
export const getIconFromClassName = ( className ) => {
const list = new TokenList( className );
const style = ICONS.find( ( option ) =>
list.contains( `is-style-icon-${ option.value }` )
);
return undefined !== style ? style.value : '';
};
该函数通过将 className 变量传递给 TokenList 来创建一个 DOM 令牌类列表。然后,它搜索包含某个图标值的类。如果找到,则返回该图标的值。否则,返回一个空字符串。
你还需要一个用于更新区块 className 属性的函数,该函数不会干扰 Block Styles API,也不会擦除用户可能从 UI 输入的任何 CSS 类(提示:你可以在区块的高级选项卡下的附加 CSS 类字段中找到所有自定义类)。
现在将 updateIconClass() 函数添加到你的 utils.js 文件中:
export const updateIconClass = ( className, newIcon = '', oldIcon = '' ) => {
const list = new TokenList( className );
if ( oldIcon ) {
list.remove( `is-style-icon-${ oldIcon }` );
}
if ( newIcon ) {
list.add( `is-style-icon-${ newIcon }` );
}
return list.value;
};
同样,此函数使用 TokenList 函数创建一个类名列表。这使得定位与图标相关的类变得容易,移除旧的图标类并添加新的。
构建自定义控件
准备工作终于完成了。让我们开始构建那个控件吧!
现在打开你新创建的 /resources/js/control-icons.js 文件。像往常一样,你需要导入一些你创建的常量、函数以及一些来自 WordPress 的东西,主要是组件:
// Internal dependencies.
import { ICONS } from './const';
import { getIconFromClassName, updateIconClass } from './utils';
// WordPress dependencies.
import { __ } from '@wordpress/i18n';
import { starFilled } from '@wordpress/icons';
import {
BaseControl,
Button,
Dropdown,
ToolbarButton,
__experimentalGrid as Grid
} from '@wordpress/components';
除了导入之外,control-icons.js 文件将包含一个函数,该函数将是其默认导出。继续添加这个:
export default ( { attributes: { className }, setAttributes } ) => {
// Add your component code here.
};
此函数解构了 props 参数,为你提供 attributes(一个包含区块属性的对象)和 setAttributes(一个设置区块属性的函数)。attributes 参数被进一步解构,以便你直接访问 className 属性。
让我们从一段简单的代码片段开始。
你需要一个变量来存储 Separator 区块当前分配的图标。因此,将 className 属性传递给你自定义的 getIconFromClassName() 函数(你在 utils.js 中定义的那个):
// Get the current icon.
const currentIcon = getIconFromClassName( className );
现在你需要一个函数,用于在设置新图标时更新区块的属性。它将做两件事:
- 更新区块的类,这由你在
utils.js中定义的updateIconClass()函数处理。 - 更新区块的
gradient属性。
现在添加 onIconButtonClick() 函数的代码:
// Update the icon class and gradient.
const onIconButtonClick = ( icon ) => setAttributes( {
className: updateIconClass(
className,
currentIcon === icon.value ? '' : icon.value,
currentIcon
),
gradient: currentIcon === icon.value || ! icon?.gradient
? undefined
: icon?.gradient
} );
对于图标选择器,你需要按钮。这些按钮让你的主题用户可以选择他们想要的 Separator 区块图标。良好的开发实践原则之一是保持 DRY(不要重复自己)。与其为每个图标创建单独的按钮,不如使用 WordPress 的 <Button> 组件创建一个按钮函数。
将此代码添加到 control-icons.js 中的函数内部:
// Builds a menu item for an icon.
const iconButton = ( icon, index ) => (
<Button
key={ index }
isPressed={ currentIcon === icon.value }
className="theme-slug-sep-icons-picker__button"
onClick={ () => onIconButtonClick( icon ) }
>
{ icon.icon ?? icon.value }
</Button>
);
现在你将构建网格部分。你将使用 WordPress 的 <BaseControl> 组件作为包装元素并输出标签。
在基础控制包装器内部,你将使用一个 <div> 来输出描述,以帮助你的主题用户理解该控件的用途。你也可以省略这个。
你还将使用 <Grid> 组件来组织每个图标按钮。你将通过遍历 ICONS 数组(从 const.js 导入的那个)并将数据传递给 iconButton() 来实现。
将此代码添加到你的 control-icons.js 文件中的函数内部:
// Builds an icon picker in a 6-column grid.
const iconPicker = (
<BaseControl
className="theme-slug-sep-icons-picker"
label={ __( 'Icons', 'theme-slug' ) }
>
<div className="theme-slug-sep-icons-picker__description">
{ __( 'Pick an icon to super-charge your separator.', 'theme-slug' ) }
</div>
<Grid className="theme-slug-sep-icons-picker__grid" columns="6">
{ ICONS.map( ( icon, index ) =>
iconButton( icon, index )
) }
</Grid>
</BaseControl>
);
现在,创建控件本身的最后一部分。因为图标选择器控件位于区块工具栏中,所以使用 WordPress 的 <Dropdown> 组件是有意义的,将上述组件代码传递给它。
因此,将此代码添加到你的 control-icons.js 文件中的函数末尾:
// Returns the dropdown menu item.
return (
<Dropdown
className="theme-slug-sep-icons-dropdown"
contentClassName="theme-slug-sep-icons-popover"
popoverProps={ {
headerTitle: __( 'Separator Icons', 'theme-slug' ),
variant: 'toolbar'
} }
renderToggle={ ( { isOpen, onToggle } ) => (
<ToolbarButton
className="theme-slug-sep-icons-dropdown__button"
icon={ starFilled }
label={ __( 'Separator Icon', 'theme-slug' ) }
onClick={ onToggle }
aria-expanded={ isOpen }
isPressed={ !! currentIcon }
/>
) }
renderContent={ () => iconPicker }
/>
);
请注意两个 render*() 函数:
renderToggle()使用 WordPress 的<ToolbarButton>组件在工具栏中渲染按钮。renderContent()渲染你在前面步骤中制作的图标选择器。
在编辑器中显示你的组件
你可能已经构建了你的图标选择器,但你仍然需要让它显示出来。这意味着你需要告诉 WordPress 它在那里——你可以通过过滤器来实现。
还记得 /resources/js/editor.js 文件吗——如果你遵循了本教程系列第二部分的所有步骤,它应该是完全空的?是时候打开它并添加你的最终 JavaScript 代码了。
这里你还需要一些导入。
首先,你需要导入刚刚创建的组件。我们将其命名为 SeparatorIconControl。你还需要 WordPress 的 BlockControls 组件和 addFilter() 函数。
将它们添加到你的 editor.js 文件中:
// Internal dependencies.
import SeparatorIconControl from './control-icons';
// WordPress dependencies.
import { BlockControls } from '@wordpress/block-editor';
import { addFilter } from '@wordpress/hooks';
接下来,你需要为 editor.BlockEdit 过滤器钩子编写一个过滤器回调函数。这个函数将做几件事:
- 检查当前区块是否为 Separator 区块 (
core/separator)。 - 将图标控制组件添加到区块控件中。
将此代码添加到你的 editor.js 文件中:
const withSeparatorIcons = ( BlockEdit ) => ( props ) => {
return 'core/separator' === props.name ? (
<>
<BlockEdit { ...props } />
<BlockControls group="other">
<SeparatorIconControl
attributes={ props.attributes }
setAttributes={ props.setAttributes }
/>
</BlockControls>
</>
) : (
<BlockEdit { ...props } />
);
};
获得工作控件的最后一步是调用 addFilter()。传入钩子名称、自定义命名空间和 withSeparatorIcons 回调函数:
addFilter(
'editor.BlockEdit',
'theme-slug/separator-icons',
withSeparatorIcons
);
现在在 WordPress 编辑器中创建一篇新文章或页面,并向其中添加一个 Separator 区块。然后点击工具栏中的星星图标,确保一切正常。你应该会看到一个类似这样的下拉菜单:

这还不错,但还需要一点设计上的润色。让我们现在就开始吧。
美化图标选择器设计
这个过程的最后一步是对设计进行最后的润色。添加一些样式规则会大有帮助。
我选择 SCSS 而不是普通的 CSS 来完成本教程系列的原因之一是,它允许你导入变量、混合和函数(不过,也有一些构建工具可以让你在 CSS 中完成所有这些)。
因为你的组件在 WordPress 之外,你需要一种方法来访问诸如内边距和边距值之类的东西,以便为用户保持一致的体验。你可以通过导入 WordPress 的 _variables.scss 文件来获得这些。
现在打开你的 /resources/scss/editor.scss 文件并添加:
// WordPress imports.
@use '~@wordpress/base-styles/variables';
该文件有许多变量,你可以在各种项目中使用。但对于这个项目,你只需要 $grid-unit-20 来进行一些内边距和边距调整。
增加 emoji 图标本身的大小也无妨。我为此选择了 20px,但你可以随意调整这个数字。
你只剩下最后一点代码了。所以将此 SCSS 添加到 editor.scss:
.theme-slug-sep-icons-popover .components-popover__content {
padding: variables.$grid-unit-20;
}
.theme-slug-sep-icons-picker {
&__description {
margin: 0 0 variables.$grid-unit-20;
}
&__button {
font-size: 20px;
}
}
你的图标选择器现在应该看起来像这样:

好多了,对吧?
旅程的终点
随着对设计的最后调整,本教程系列已经结束。恭喜你!
你现在可以冒险进入超越 Block Styles API 的广阔而奇妙的世界了。你已经拥有了成功所需的所有工具。
附录
本系列中有很多我没有涵盖的内容:比如更多关于我自己的开发和决策过程的见解。或者更多的技巧和窍门。
但我也想尊重你的时间。在超过 7000 字,或者说相当于一篇 28 页大学论文的长度之后,你可能已经读得够多了。是时候去构建一些很酷的东西了!
类命名
在整个系列中,我使用了 .is-style-{value} 命名约定,这是 Block Styles API 使用的。这使得将代码从第二部分迁移到第三部分变得更容易。如果你愿意,也可以轻松切换回 Block Styles API。
在我的代码中,我会使用 .has-icon-{value} 类名。这是我通常命名事物的方式。
你可以随心所欲——你不受任何命名规则束缚。
为什么要用className属性?
你可能会好奇为什么我选择用CSS类而不是自定义属性。虽然主题评审指南并未明确禁止在块中添加自定义属性,但我认为使用现有属性更符合功能与功能指南的精神,该指南定义了属于主题的功能,而非插件。className
而且,如果你的用户更改了主题,而你的设计消失了,分隔器块上的图标类并没有消失。网站所有者仍然可以用CSS来做样式。
确实,你确实需要写几个辅助函数。但代码非常简约,可能不会比添加自定义属性所需的代码重多少。
其他可建造的东西
以下是我一直在做的一些事情——如果你需要一些灵感的话:
- 一个用于RichText块的文本阴影选择器。
- 用于图片和后期特色图片块的渐变环/轮廓。
- 一个列表块下拉菜单,用于选择项目符号样式(它用的代码基本和这个教程里的一样)。
- 一个颜色变化选择器,可以为群组和其他容器型方块设置所有颜色。
我还可以列出十几个我还没机会探索的想法。而且,我也不想因为说出我想做的功能而限制你的想象力。我认为,这样对你来说更好,你应该开始自己的旅程,去发现新的事物。我唯一请求的是:和别人分享你的经历。
资源
本教程系列第三部分中使用的资源:
- 超越块样式仓库:第3部分分支
- 方块控制:方块工具栏和设置侧边栏
- 块属性
- 组件参考
- 钩子引用
- 包参考
- WordPress 故事书文档
- 使用区块检查器侧边栏组
- 如何通过SlotFill系统扩展WordPress。
- 主题评审指南