本文档介绍与区块编辑器中影响用户内容的样式相关的主要概念,并指向相关的参考指南和教程,供读者深入了解文中所介绍的每个概念。本文档面向区块作者以及参与区块编辑器项目的人员。

HTML 和 CSS

用户在区块编辑器中创建文章时,实际上是在创建若干产物:一个 HTML 文档以及若干 CSS 样式表(这些样式表或嵌入在文档中,或为外部样式表)。

最终的 HTML 文档由以下几个方面共同决定:

  • 由主题提供的 WordPress 模板,通过 PHP(经典主题)或 HTML 模板(区块主题)实现(了解更多两者的差异)
  • 使用中的区块和模式,它们自带预定义的结构(HTML 标记)
  • 用户对内容的修改:添加内容、转换现有内容(将某个段落转换为标题)或修改内容(为区块附加 class 或内联样式)

在前端加载的样式表包括:

  • 区块。区块自带的样式表。在前端,你可以找到包含 WordPress 定义的所有区块样式的单个样式表(wp-block-library-*),也可以找到正在使用的每个区块各自的独立样式表(如 wp-block-group-*、wp-block-columns-* 等)。完整详情请参阅此说明。
  • 全局样式。这些样式是利用来自 theme.json 文件的数据动态生成的:参见说明、参考文档和操作指南。具体来说,它会合并来自 WordPress 的 theme.json、来自主题的 theme.json(如果有的话),以及用户通过站点编辑器中的全局样式侧边栏提供的用户数据。处理这些数据的结果是一个 id 为 global-styles-inline-css 的嵌入式样式表。
  • 主题。一直以来,主题都会引入自己的样式表,其 id 基于主题名称,如 twentytwentytwo-style-css。除了拥有自己的样式表之外,主题现在还可以声明一个 theme.json 文件,其中包含的样式将成为由全局样式生成的样式表的一部分。
  • 用户。用户在编辑器中执行的某些操作会生成样式内容。双色调、布局或链接颜色等功能就属于这种情况。
  • 其他。WordPress 和插件也可以引入样式表。

区块样式

自 WordPress 5.0 引入区块编辑器以来,就提供了供用户向特定区块“添加样式”的工具。通过使用这些工具,用户可以向区块附加新的 class 或内联样式,从而改变区块的视觉外观。

默认情况下,区块带有给定的 HTML 标记。以段落区块为例:

<p></p>

在最简单的形式下,任何针对 p 选择器的样式规则都会应用到这个区块,无论它来自区块、主题还是其他来源。

用户可以通过应用不同的样式来更改此区块的状态:文本对齐、颜色、字体大小、行高等。这些状态以 HTML 属性的形式反映在区块的 HTML 标记中,主要通过 class 或 style 属性,不过区块作者也可以视需要使用任何其他属性。

在用户对区块进行一些修改后,初始标记可能会变成如下所示:

<p class="has-color has-green-color has-font-size has-small-font-size my-custom-class"
    style="line-height: 1em"></p>

这就是我们所说的“用户提供的区块样式”,也称为“局部样式”或“序列化样式”。本质上,每个工具(字体大小、颜色等)最终都会向区块标记添加一些 class 和/或内联样式。这些 class 的 CSS 样式是区块、全局或主题样式表的一部分。

修改区块状态的能力,再加上区块可以存在于任何其他区块之中(比如 group 区块中的一个段落)这一事实,创造了大量潜在的状态和样式可能性。

从 UI 控件到 HTML 标记

如果你跟随区块教程学习,就可以更详细地了解此处介绍的区块 API 的各个部分,还可以构建你自己的区块。这里介绍的是区块如何让用户编辑其状态的一般概念。

要构建上文所述的体验,区块作者需要以下几个要素:

  1. UI 控件。它向用户呈现一些选项,例如让用户能够更改区块的字体大小。控件负责从区块读取数据(这个区块是否已经分配了字体大小?)以及它需要的其他数据(用户可以在这个区块中使用哪些字体大小?)。请查看可用的组件库。
  2. 区块属性。区块需要保存数据,以了解对其应用了哪些修改:例如是否已被指定了字体大小。请参阅区块如何定义属性。
  3. 访问样式数据。控件可能需要有关给定区块可用样式的外部信息:例如颜色列表或字体大小列表。这些被称为“样式预设”,因为它们是通常由主题定义的一组预先选定的样式,不过 WordPress 也提供了一些默认值。请查看主题可以提供给编辑器的数据列表,以及区块作者如何通过 useSettings 访问这些数据。
  4. 将用户样式序列化为 HTML 标记。当用户执行操作时,区块的 HTML 标记需要相应地更新(应用适当的 class 或内联样式)。此过程称为序列化,是 edit、save 和 render_callback 函数的职责:这些函数接收区块数据并将其转换为 HTML。

本质上,这些就是区块作者需要关心的核心机制,以便其区块能够被用户设置样式。虽然这一切完全可以手动完成,但有一个 API 可以为常见的样式需求自动完成此过程:区块支持。

区块支持 API

区块支持是一个 API,它允许区块声明自己支持哪些功能。通过在区块的 block.json 文件中添加一些信息,区块就会告诉系统用户可以对它执行哪些操作。

例如:

{
    "name": "core/paragraph",
    "...": "...",
    "supports": {
        "typography": {
            "fontSize": true
        }
    }
}

段落区块在其 block.json 中声明了对字体大小的支持。这意味着区块将显示一个 UI 控件,供用户调整其字体大小,除非主题禁用了该控件(在 theme.json 参考文档中详细了解主题如何禁用 UI 控件)。系统还会负责设置 UI 控件的数据(如果区块已分配了字体大小则显示该值、要显示的可用字体大小列表),并在用户进行更改时将区块数据序列化为 HTML 标记(适当地附加 class 和内联样式)。

通过 block.json 使用区块支持机制,区块作者只需编写几行代码即可创建与之前相同的体验。请查阅区块支持 API,了解如何为静态或动态区块添加区块支持。

除了只需更少的工作量即可获得相同结果这一好处之外,还有一些其他优点:

  • 区块的样式信息将可在原生移动应用和服务器端使用
  • 区块将使用其他区块用于相同样式的 UI 控件,从而带来更加一致的用户体验
  • 区块所使用的 UI 控件会随着其自身的改进而自动更新,而区块作者无需做任何事情

区块支持 API 的当前限制

虽然区块支持 API 带来了价值,但它也存在一些区块作者需要了解的限制。为了更直观地理解这些限制,我们来看下面这个表格区块的例子:

<table>
    <thead>
        <tr>
            <th>Header</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th>First</th>
        </tr>
        <tr>
            <th>Second</th>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <th>Footer</th>
        </tr>
    </tfoot>
</table>
  1. 每个区块只能有一种样式类型。

其中一个限制是,在所有可用样式中,每种样式在区块中只能有一个实例。沿用上面的例子,表格区块只能有一个字号。如果区块作者想要使用三个不同的字号(表头、正文和页脚),当前的区块支持 API 无法做到。更多详细信息和后续方向,请参见此 issue。

  1. 样式会被序列化到区块最外层的 HTML 节点,即包装器(wrapper)。

区块支持 API 只将字号值序列化到包装器上,从而产生如下 HTML:<table class="has-small-font-size">。当前的区块支持 API 不会将该值序列化到其他节点,例如 <tbody>。

这是一个正在积极开发中的领域,你可以在跟踪 issue中关注进展。链接中的提案正在探索一种不同的用户更改序列化方式:不再由每个区块支持分别序列化自己的数据(例如 has-small-font-size、has-green-color 这样的类),而是让区块获得一个单独的类(例如 wp-style-UUID),该类的 CSS 样式将由 WordPress 在服务器端生成。

虽然该提案的相关工作仍在继续,但目前有一个逃生通道,即区块作者可以使用的一个实验性选项。任何区块支持都可以通过 __experimentalSkipSerialization 来跳过向 HTML 标记的序列化。例如:

{
    "name": "core/paragraph",
    "...": "...",
    "supports": {
        "typography": {
            "fontSize": true,
            "__experimentalSkipSerialization": true
        }
    }
}

这意味着排版(typography)区块支持将完成其余所有工作(创建 UI 控件、将区块属性绑定到该控件等),只是不会将用户值序列化到 HTML 标记中。类和内联样式不会被自动应用到包装器上,区块作者需要自行在 edit、save 和 render_callback 函数中实现这一点。关于 WordPress 提供的一些区块是如何实现的示例,请参见此 issue。

请注意,如果为某个组(typography、color、spacing)启用了 __experimentalSkipSerialization,它会影响该组内的所有区块支持。在上面的例子中,typography 组内的所有属性都会受到影响(例如 fontSize、lineHeight、fontFamily 等)。

若只想对单个属性启用,可以使用数组来声明要跳过序列化的属性。在下面的例子中,只有 fontSize 会跳过序列化,而 typography 组内的其他项目(例如 lineHeight、fontFamily 等)不受影响。

{
    "name": "core/paragraph",
    "...": "...",
    "supports": {
        "typography": {
            "fontSize": true,
            "lineHeight": true,
            "__experimentalSkipSerialization": [ "fontSize" ]
        }
    }
}

对该功能的支持是在这个 PR中添加的。

全局样式

全局样式(Global Styles)指的是一种生成全站范围样式的机制。与上一节所描述的区块样式不同,这些样式不会被序列化到文章内容中,也不会附加到区块的 HTML 上。相反,该系统输出的是一个 id 为 global-styles-inline-css 的新样式表。

该机制是在 WordPress 5.8 中引入的。当时,它只从 WordPress 和活动主题获取数据。WordPress 5.9 扩展了该系统,使其还能从用户获取样式数据。

以下是整体的数据流:

全局样式的数据流

生成样式表的过程本质上包含三个步骤:

  1. 收集数据:WordPress 内置的 theme.json 文件、活动主题的 theme.json 文件(如果存在),以及用户通过站点编辑器中的全局样式 UI 提供的样式。
  2. 整合数据:来自不同来源(WordPress 默认值、主题和用户)的结构化信息会被规范化并合并为一个单一结构。
  3. 将数据转换为样式表:将内部表示转换为 CSS 样式规则,并将其作为样式表入队。

收集数据

数据可以来自三个不同的来源:WordPress 默认值、活动主题或用户。这三者都使用相同的theme.json 格式。

来自 WordPress 和活动主题的数据从相应的 theme.json 文件中获取。来自用户的数据则从数据库中读取——用户通过站点编辑器中的全局样式侧边栏保存更改后,这些数据便存储在数据库中。

整合数据

此阶段的目标是构建一个整合后的结构。

此阶段有两个重要的过程。首先,系统需要对所有输入的数据进行规范化,因为不同的来源可能使用不同版本的 theme.json 格式。例如,某个主题可能使用的是 v1,而 WordPress 基础使用的是最新版本。其次,系统需要决定如何将这些输入合并为一个单一结构。这将是下面几节的重点。

样式

输入的 theme.json 结构的不同部分会以不同的方式处理。styles 部分中的数据按照以下逻辑混合在一起:用户数据覆盖主题数据,主题数据覆盖 WordPress 数据。

例如,假设我们有以下三个分别来自 WordPress、主题和用户的 theme.json 结构:

{
    "styles": {
        "color": {
            "background": "<WordPress value>"
        },
        "typography": {
            "fontSize": "<WordPress value>"
        }
    }
}
{
    "styles": {
        "typography": {
            "fontSize": "<theme value>",
            "lineHeight": "<theme value>"
        }
    }
}
{
    "styles": {
        "typography": {
            "lineHeight": "<user value>"
        }
    }
}

整合后的结果将是:

{
    "styles": {
        "color": {
            "background": "<WordPress value>"
        },
        "typography": {
            "fontSize": "<theme value>",
            "lineHeight": "<user value>"
        }
    }
}

设置

settings 部分的工作方式与样式不同。大多数设置仅用于配置编辑器,对全局样式没有影响。只有少数几个设置会成为最终样式表的一部分:预设(presets)。

预设是在 UI 的不同部分展示给用户的预定义样式,例如调色板或字号。它们包含以下设置:color.duotone、color.gradients、color.palette、typography.fontFamilies、typography.fontSizes。与 styles 不同,来自某个来源的预设不会覆盖其他来源的值,而是所有来源的预设都会被存储在整合后的结构中。

例如,假设我们有以下分别来自 WordPress、主题和用户的 theme.json 结构:

{
    "settings": {
        "color": {
            "palette": [ "<WordPress values>" ],
            "gradients": [ "<WordPress values>" ]
        }
    }
}
{
    "settings": {
        "color": {
            "palette": [ "<theme values>" ]
        },
        "typography": {
            "fontFamilies": [ "<theme values>" ]
        }
    }
}
{
    "settings": {
        "color": {
            "palette": [ "<user values>" ]
        }
    }
}

合并后的结果将是:

{
    "settings": {
        "color": {
            "palette": {
                "default": [ "<WordPress values>" ],
                "theme": [ "<theme values>" ],
                "user": [ "<user values>" ]
            },
            "gradients": {
                "default": [ "<WordPress values>" ]
            }
        },
        "typography": {
            "fontFamilies": {
                "theme": [ "<theme values>" ]
            }
        }
    }
}

从数据到样式

生成样式表的最后一个阶段是将合并后的数据转换为 CSS 样式规则。

从样式到 CSS 规则

styles 部分可以看作是 CSS 规则的结构化表示,每个片段代表一条 CSS 规则:

  • theme.json 中的一个键/值对映射到一条 CSS 声明(property: value)。
  • 给定片段的 CSS 选择器是根据其语义生成的:
    • 顶层部分使用 body 选择器。
    • 顶层元素使用与其所代表的 HTML 元素相匹配的 ID 选择器(例如 h1 或 a)。
    • 区块使用其生成的默认类名(core/group 变为 .wp-block-group),除非它们通过自己的 block.json 显式设置了不同的类名(core/paragraph 变为 p)。更多信息请参阅“当前限制”部分。
    • 区块内的元素使用区块选择器和元素选择器的拼接。

例如,以下 theme.json 结构:

{
    "styles": {
        "typography": {
            "fontSize": "<top-level value>"
        },
        "elements": {
            "h1": {
                "typography": {
                    "fontSize": "<h1 value>"
                }
            }
        },
        "blocks": {
            "core/paragraph": {
                "color": {
                    "text": "<paragraph value>"
                }
            },
            "core/group": {
                "color": {
                    "text": "<group value>"
                },
                "elements": {
                    "h1": {
                        "color": {
                            "text": "<h1 within group value>"
                        }
                    }
                }
            }
        }
    }
}

会被转换为以下 CSS:

body {
    font-size: <top-level value>;
}
h1 {
    font-size: <h1 value>;
}
p {
    color: <paragraph value>;
}
.wp-block-group {
    color: <group value>;
}
.wp-block-group h1 {
    color: <h1 within group value>;
}

从设置到 CSS 规则

在 settings 部分中,任何给定预设值的所有值都将被转换为遵循以下命名结构的 CSS 自定义属性:--wp--preset--<category>-<slug>。选择器遵循上文样式部分所述的相同规则。

例如,以下 theme.json

{
    "settings": {
        "color": {
            "palette": {
                "default": [
                    {
                        "slug": "vivid-red",
                        "value": "#cf2e2e",
                        "name": "Vivid Red"
                    }
                ],
                "theme": [
                    {
                        "slug": "foreground",
                        "value": "#000",
                        "name": "Foreground"
                    }
                ]
            }
        },
        "blocks": {
            "core/site-title": {
                "color": {
                    "palette": {
                        "theme": [
                            {
                                "slug": "foreground",
                                "value": "#1a4548",
                                "name": "Foreground"
                            }
                        ]
                    }
                }
            }
        }
    }
}

将被转换为以下 CSS 样式规则:

body {
  --wp--preset--color--vivid-red: #cf2e2e;
  --wp--preset--color--foreground: #000;
}

.wp-block-site-title {
  --wp--preset--color--foreground: #1a4548;
}

除了 CSS 自定义属性之外,所有预设值(duotone 除外)还会为每个值生成 CSS 类。上面的示例还将生成以下 CSS 类:

/* vivid-red */
.has-vivid-red-color { color: var(--wp--preset--color--vivid-red) !important; }
.has-vivid-red-background-color { background-color: var(--wp--preset--color--vivid-red) !important; }
.has-vivid-red-border-color { border-color: var(--wp--preset--color--vivid-red) !important; }

/* foreground */
.has-foreground-color { color: var(--wp--preset--color--foreground) !important; }
.has-foreground-background-color { background-color: var(--wp--preset--color--foreground) !important; }
.has-foreground-border-color { border-color: var(--wp--preset--color--foreground) !important; }

/* foreground within site title*/
.wp-block-site-title .has-foreground-color { color: var(--wp--preset--color--foreground) !important; }
.wp-block-site-title .has-foreground-background-color { background-color: var(--wp--preset--color--foreground) !important; }
.wp-block-site-title .has-foreground-border-color { border-color: var(--wp--preset--color--foreground) !important; }

全局样式 API 的当前限制

1. 为区块设置不同的 CSS 选择器需要服务器端注册

默认情况下,分配给区块的选择器是 .wp-block-<block-name>。不过,区块在需要时可以更改它。区块可以通过其 block.json 中的 __experimentalSelector 属性提供 CSS 选择器。

如果区块这样做,就需要使用 block.json 在服务器端注册,否则全局样式代码将无法访问该信息,并将为该区块使用默认的 CSS 选择器。

2. 无法为不同样式指定不同的 HTML 节点

每个样式片段只能使用一个选择器。

如果区块使用 __experimentalSkipSerialization 将不同的样式属性序列化到包裹器以外的不同节点,这一点尤为重要。更多信息请参阅“区块支持的当前限制”。

3. 每个区块只能有单一属性

与区块支持类似,区块所使用的任何样式只能有一个实例。例如,区块只能有一个字号。请参阅相关的“区块支持的当前限制”。

4. 只有使用区块支持的区块才会显示在全局样式 UI 中

站点编辑器中的全局样式 UI 有一个用于各区块样式的界面。区块列表是使用各区块 block.json 中的区块支持动态生成的。如果某个区块希望被列在其中,就需要使用区块支持机制。

布局样式

除了单个区块级别的样式和全局样式之外,还有布局样式的概念,这些样式会同时为基于区块的主题和经典主题输出。

布局区块支持会输出由用于创建布局的区块所共享的通用布局样式。布局样式对于为任何作为其他区块容器的区块提供通用样式非常有用。依赖这些布局样式的区块示例包括组(Group)、行(Row)、列(Columns)、按钮(Buttons)和社交图标(Social Icons)。在核心区块中,该功能通过区块 block.json 文件中 supports 下的 layout 设置启用。

布局样式主要在以下两个地方输出:

基础布局样式

基础布局样式是所有启用了特定布局类型的区块所共有的样式。常见基础布局样式的示例包括:为使用 Flex 布局类型的区块(如按钮和社交图标)设置 display: flex,以及为受限布局提供默认最大宽度。

基础布局样式由处理全局样式的主 PHP 类输出,并构成全局样式样式表的一部分。为了在经典主题中为核心区块提供支持,无论主题是否提供自己的 theme.json 文件,这些样式都会始终被输出。

通用布局定义存储在核心布局区块支持文件中。

单独的布局样式

当启用了布局支持的区块被渲染时,会通过 layout.php 处理两项内容并将其添加到输出中:

  • 会在区块标记中添加语义类名,以指示当前正在使用哪些布局设置。例如,is-layout-flow 用于使用默认/流式布局的区块(如 Group 区块),而当用户将区块设置为右对齐时,则会添加 is-content-justification-right。
  • 对于正在渲染的单个区块上设置的非默认布局值,会生成相应的独立样式。这些样式通过容器类名附加到区块上,该类名采用 wp-container-$id 的形式,其中 $id 是一个唯一编号。

可用的布局类型

目前共有四种正在使用的布局类型:

  • 默认/流式(Flow):项目垂直堆叠。不指定父容器区块的 display 值,以便其可以使用该 HTML 元素的默认值。对于大多数元素来说,该值通常为 block。子元素之间的间距通过垂直外边距来处理。
  • 受限(Constrained):项目垂直堆叠,采用与流式布局相同的间距逻辑。为子内容提供受限宽度,输出标准内容尺寸(content size)和宽尺寸(wide size)的宽度。默认使用在 theme.json 中 settings.layout 里设置的全局 contentSize 和 wideSize 值。
  • Flex:项目使用 Flexbox 布局显示。默认为水平方向。子元素之间的间距通过 gap CSS 属性处理。
  • Grid:项目使用 Grid 布局显示。列生成默认采用 auto-fill 方式,但也可以设置为固定的列数。子元素之间的间距通过 gap CSS 属性处理。

要控制区块之间的间距并启用区块间距控制,请参阅:什么是 blockGap 以及如何使用它?。

从主题中定位布局或容器区块

布局区块支持旨在让用户能够在区块编辑器和站点编辑器中控制布局功能。在可能的情况下,请尽量利用区块自身的功能来确定特定的布局需求,而不是依赖额外的样式表。

对于希望定位容器区块以添加或调整特定样式的主题来说,区块的类名通常是最佳选择。诸如 wp-block-group 或 wp-block-columns 之类的类名通常是定位特定区块的可靠类名。除了区块类名和布局类名之外,还有一个由区块和布局组合而成的类名:例如,对于采用受限布局的 Group 区块,其类名将是 wp-block-group-is-layout-constrained。

要定位使用特定布局类型的区块,请避免以 wp-container- 作为定位目标,因为容器类并不一定总是存在于渲染后的标记中。

语义类名

目前正在进行相关工作,以在布局区块支持输出中扩展稳定的语义类名。该任务正在此 issue中进行讨论。

布局区块支持当前可以输出的语义类名包括:

  • is-layout-flow:使用默认/流式(Default/Flow)布局类型的区块。
  • is-layout-constrained:使用受限(Constrained)布局类型的区块。
  • is-layout-flex:使用 Flex 布局类型的区块。
  • is-layout-grid:使用 Grid 布局类型的区块。
  • wp-container-$id:其中 $id 是一个半随机数。这是一个仅在区块包含非默认布局值时才会存在的容器类。不应直接将此类用于任何 CSS 定位,因为它可能存在,也可能不存在。
  • is-horizontal:当区块将 orientation 显式设置为 horizontal 时。
  • is-vertical:当区块将 orientation 显式设置为 vertical 时。
  • is-content-justification-left:当区块将 justifyContent 显式设置为 left 时。
  • is-content-justification-center:当区块将 justifyContent 显式设置为 center 时。
  • is-content-justification-right:当区块将 justifyContent 显式设置为 right 时。
  • is-content-justification-space-between:当区块将 justifyContent 显式设置为 space-between 时。
  • is-nowrap:当区块将 flexWrap 显式设置为 nowrap 时。

选择退出生成的布局样式

布局样式输出默认处于开启状态,因为核心结构区块需要这些样式。不过,主题可以通过 disable-layout-styles 区块支持来选择退出生成的区块布局样式,同时保留语义类名输出。此类主题需要自行负责提供其所有布局样式。请参阅Theme Support 下的相应条目。