以下内容是 Scott Fennell 的客座文章,他是阿拉斯加安克雷奇的一位 WordPress 主题和插件开发人员,也是 本网站的常驻撰稿人。
假设您有一位客户,其业务规模足以拥有多个部门。 现在,假设这位客户希望每个部门都有自己的网站,并且每个网站都拥有独立的域名。 每个网站都应该具有相同的布局,但不同的配色方案。 这是一个使用 WordPress 定制器(也称为 主题定制 API)的绝佳案例,我想分享一个关于如何将其构建到主题中的基本示例。
在本文结束时,我们将拥有一个 WordPress 主题,该主题具有几个主题修改,并且我们的代码中有一个明显的模式,以便在将来添加更多修改。
获取示例
我们将引用示例主题,该主题位于 GitHub 中,名为 CSS-Tricks 主题修改演示。 您也可以克隆它并在您的 WordPress 安装中运行它——它开箱即用。
关于术语的简要说明
术语 **定制器**、**主题修改 API** 和 **主题定制 API** 一直以来在 WordPress 社区中被交替使用。 我认为这些术语之间出现了一些细微的差异,我会尽量尊重这些差异。
- **定制器** 指的是您 WordPress 安装中的实际 UI,位于
/wp-admin/customize.php
中。 - **主题修改 API** 指的是用于 CRUD 定制器管理的数据的函数族。
- **主题定制 API** 指的是开发人员可用来添加或删除定制器设置的函数族。
本文将介绍这三个概念,但我更喜欢使用 **主题修改** 这个词来命名事物,比如我的主题文件夹。 个人偏好而已,无关紧要。
创建测试网站
确保您的 WordPress 测试安装已备份并运行最新版本,在撰写本文时是 4.3.2。 事实上,我们只需要一个新的安装:没有插件,什么都没有。 只有上面提到的示例主题,安装并单独激活。

安装好示例主题后,查看 wp-admin
:您会注意到左侧的主题菜单下有一个名为“定制”的链接。 此菜单项默认存在; 我没有注册它或声明对其的支持。 即使我们的主题没有注册任何主题修改,该链接仍然很有用,因为 WordPress 核心会自动在此处添加一些设置,用于网站标题、网站标语等。 定制是我们想要做的事情,因此请点击该链接!
定制器面板
我们现在位于定制器中。 它被组织成一个包含三个层的层次结构:**面板** 包含部分,**部分** 包含设置,**设置** 是由定制器 UI 中的**控件** 管理的数据。
我们可以将其可视化为:
Panel
|__ Section
| |__ Setting and its Control
| |__ Setting and its Control
|__ Section
| |__ Setting and its Control
| |__ Setting and its Control
|
Panel
|__ Section
| |__ Setting and its Control
| |__ Setting and its Control
|__ Section
|__ Setting and its Control
|__ Setting and its Control
这种层次结构对于理解定制器非常有用。 如果您对它不清楚,请花点时间浏览有关 面板、部分、设置 及其 控件 的文档。 如果我早点学会了这一点,我的学习曲线会快很多。
另一个绊脚石:如果您正在学习如何理解定制器,您可能会首先尝试注册一个面板,然后检查您的 /wp-admin/customizer.php
以查看它是否已注册,然后再为它注册部分和设置。 不要这样做:**除非已经为面板注册了设置及其对应的控件,否则面板将不会显示。**
让我们详细了解一下这种层次结构。
面板
我们立即看到左侧有两个面板,名为 **网站标识** 和 **主体**。

网站标识面板随 WordPress 附带,而我们的主题添加了主体面板。
部分
如果我们点击主体,我们会看到它包含一些部分:颜色和布局选项。 再次重申,我在这个主题中包含了一些代码,这些代码添加了主体*面板*,而我还在代码中添加了一些代码,这些代码将颜色和布局选项*部分* 添加到了主体面板中。

设置
如果我们点击颜色,我们会看到我注册了几个设置,以及它们的控件,用于编辑我们主题的颜色。 在此过程中,我提供了橙色和黑色的默认值,这些值预先填充在左侧的控件中。

关于数据的一句话:WordPress 通过 主题修改 函数族来保存和检索设置。 我们也可以在我们的主题代码中使用这些函数,尽管 get_theme_mod()
是我将在该主题中使用的唯一函数。
控件
我已经告诉您,面板、部分和设置之间存在层次关系。 控件也是这个家族的一部分:将控件视为每个设置的兄弟姐妹。
控件是在定制器中为给定设置绘制 UI 的部分。 为了对主题修改做任何有用的事情,我们必须为它注册控件和设置。
**注意:** 您可以非常疯狂地注册各种自定义控件*类型*。 比如复选框组、范围、多选。 Justin Tadlock 提供了 许多令人惊叹的示例,有时会使用 JavaScript 模板。 虽然我们必须为每个设置注册一个控件,但我们不需要为此教程注册任何自定义控件*类型*,我也不会深入研究它。 WordPress 附带了许多很棒的控件类型,包括上面示例中的颜色选择器。 您可以在 手册 中详细了解随 WordPress 附带的控件类型。
WordPress 默认值和如何移除它们
让我们回到定制器的顶层面板,然后点击网站标识面板。 WordPress 默认添加此面板,跳过部分层,直接进入网站标题和网站图标的几个设置。 网站图标是新的,但相信我,它是核心的一部分。

你可能希望在这里也看到一个网站标语的设置,通常情况下是这样的,但我把它去掉了。事实上,我删除了很多 WordPress 默认添加的面板、部分和设置,只是为了提供一个如何取消注册它们的示例。我们的示例主题带有一个名为CSST_TMD_Customizer
的类,这是我在其中与自定义器交互以自定义这些主题修改的地方。为了删除默认节点,我钩入customize_register
操作并访问$wp_customize
对象。
<?php
class CSST_TMD_Customizer {
public function __construct() {
...
// Strip away unused customizer nodes.
add_action( 'customize_register', array( $this, 'deregister' ), 999 );
}
...
/**
* Remove stuff that WordPress adds to the customizer.
*
* param object $wp_customize An instance of WP_Customize_Manager.
*/
public function deregister( $wp_customize ) {
// Remove the setting for blog description, AKA Site Tagline.
$wp_customize -> remove_control( 'blogdescription' );
// Remove the section for designating a static front page.
$wp_customize -> remove_section( 'static_front_page' );
// Remove the panel for handling nav menus.
$wp_customize -> remove_panel( 'nav_menus' );
}
?>
我选择删除这些项目是因为它们提供了一个很好的例子,说明了我们一直在讨论的层次结构。通过查看自定义器中该区域的标记,我能够确定要传递给每个remove_
函数的键。它通常作为该控件的data-customize-setting-link
的数据属性找到(你可以在上面的截图中看到)。
请原谅我:删除nav_menus
似乎会在调试模式下触发一个 PHP 警告。我还没有找到避免这种情况的方法。如果这让你困扰,请务必保留它们,并享受你在自定义器中管理导航菜单的全新能力。
定义我们自己的面板、部分和设置
我不喜欢在写代码时重复自己,所以我将在一个巨大的数组中定义我们的面板、部分和设置,这样我们的主题就可以循环访问它们。这将为我们设置一些操作,例如将这些设置添加到自定义器,并在前端将其输出为 CSS。
示例主题带有一个名为CSST_TMD_Theme_Mods
的类,我在这里创建一个要添加到自定义器中的面板、部分和设置数组。**这个类确实是本文的重点**,我希望你花点时间阅读一下源代码。让我们遍历一个示例,其中目标是添加body
_面板_,将color
_部分_添加到该面板,然后将background_color
_设置_添加到该部分。换句话说
Panel: Body
|__ Section: Color
|__ Setting: Background Color, controlled with a Color Picker
首先,定义一个名为body
的面板
<?php
class CSST_TMD_Theme_Mods {
/**
* Define our panels, sections, and settings.
*
* @return array An array of panels, containing sections, containing settings.
*/
function get_panels() {
...
// Start an annoyingly huge array to define our panels, sections, and settings.
$out = array();
// Define the body panel.
$body = array(
// The title for this panel in the customizer UI.
'title' => esc_html__( 'Body', 'csst_tmd' ),
// The description for this panel in the customizer UI.
'description' => esc_html__( 'Theme Mods for the Page Body', 'csst_tmd' ),
// The order within the customizer to output this panel.
'priority' => 20,
// The body panel has a bunch of sections.
'sections' => array(),
);
$out['body'] = $body;
接下来,将一个名为colors
的部分添加到body
面板
// Define the colors section, which resides in the body panel.
$out['body']['sections']['colors'] = array(
// The title for this section in the customizer UI.
'title' => esc_html__( 'Colors', 'csst_tmd' ),
// The description for this section in the customizer UI.
'description' => esc_html__( 'Colors for the Page Body', 'csst_tmd' ),
// The order within this panel to output this section.
'priority' => 10,
// The colors section has a bunch of settings.
'settings' => array(),
);
?>
然后,将background_color
的设置添加到colors
部分
<?php
// The setting for body background color.
$out['body']['sections']['colors']['settings']['background_color'] = array(
// The type of control for this setting in the customizer.
'type' => 'color',
// The header text for the control.
'label' => esc_html__( 'Body Background Color', 'csst_tmd' ),
// The descriptive text for the control.
'description' => esc_html( 'The background color for the body element, on landscape screens smaller than 800px.', 'csst_tmd' ),
// The order within this section for outputting this control.
'priority' => 10,
// The default value for this setting.
'default' => '#000000',
// A callback function for sanitizing the input.
'sanitize_callback' => 'sanitize_hex_color',
'sanitize_js_callback' => 'sanitize_hex_color',
// Do we want to use css from this setting in TinyMCE?
'tinymce_css' => FALSE,
// Is this setting responsible for creating some css?
'css' => array(
// This array amounts to one css rule. We could do several more right here.
array(
// Here's the selector string.
'selector' => 'body',
// Here's the css property.
'property' => 'background-color',
// Here are some media queries for this css.
'queries' => array(
'max-width' => '800px',
'orientation' => 'landscape',
),
// End this css rule. We could start another one right here, perhaps to use this setting for a border-color on the body element, or whatever.
),
// End the list of css rules (yeah, there's just one right now).
),
// End this setting.
);
?>
这会导致很多嵌套,以及一个非常长的数组定义,我承认这可能很难理解。无论如何,我倾向于在深度数组中工作,而且这种技术对我很有效。在具有大量设置的实现中,我经常使用瞬态 来避免所有循环带来的任何性能问题。我还将数组分解成每个面板的多个函数,以便更容易阅读和调试。
好消息是,你不必这样做!这个类中没有任何东西以任何特定的格式钩入 WordPress。这只是我个人的习惯,以一种对我有效的方式定义我的设置。你可以用媒体查询来进行更细致的处理。你可能可以将规则限定在只有特定主体类存在时才应用。你可以抛弃我这个多维数组,一次声明一个设置。只要你想,你可以尽可能地富有创意和强大,或者尽可能地简单易读。这完全取决于你。
如果我所有的循环让你头晕目眩,请查看优秀的Twenty Sixteen 主题,它在没有循环的情况下注册其自定义器设置。我倾向于避开他们的技术,因为我发现它不太 DRY。例如,字符串color_scheme
在该文件中出现了 83 次。
我能想象另一种方法,灵感来自于Tom McFarline 的作品和写作。他有一系列关于使用面向对象编程 (OOP) 来管理 WordPress 设置的文章。如果这种架构对你来说更直观,那就试试吧。我认为你会发现他关于设置 API 的 OOP 工作有一些明显的相似之处,这些相似之处也适用于自定义器 API。
无论选择哪种技术,这些信息都必须传递到自定义器中,才能为这些设置获得一个 UI。我们还没有做到这一点,所以让我们来做吧!
将主题修改传递到自定义器
将我们的设置添加到自定义器非常类似于从自定义器中删除核心设置。同样,我们必须钩入customizer_register
,只是这次我们添加内容而不是删除内容。以下是执行此操作的函数 的一个片段
<?php
class CSST_TMD_Customizer {
public function __construct() {
// Register our custom settings and controls.
add_action( 'customize_register' , array( $this, 'register' ), 970 );
...
}
/**
* Add our panels, sections, and settings to the customizer.
*
* @param object $wp_customize An instance of the WP_Customize_Manager class.
*/
public function register( $wp_customize ) {
// Fire up our theme mods class.
$theme_mods_class = new CSST_TMD_Theme_Mods;
// Grab our panels, sections, and settings.
$panels = $theme_mods_class -> get_panels();
// For each panel...
foreach ( $panels as $panel_id => $panel ) {
// Add this panel to the UI.
$wp_customize -> add_panel(
$panel_id,
array(
'title' => $panel['title'],
'description' => $panel['description'],
'priority' => $panel['priority'],
)
);
// For each section in this panel, add it to the UI and add settings to it.
foreach( $panel['sections'] as $section_id => $section ) {
// Add this section to the UI.
$wp_customize -> add_section(
$panel_id . '-' . $section_id,
array(
'title' => $section['title'],
'description' => $section['description'],
'priority' => $section['priority'],
'panel' => $panel_id,
)
);
// For each setting in this section, add it to the UI.
foreach( $section['settings'] as $setting_id => $setting ) {
// Start building an array of args for adding the setting.
$setting_args = array(
'default' => $setting['default'],
'sanitize_callback' => $setting['sanitize_callback'],
'sanitize_js_callback' => $setting['sanitize_js_callback'],
);
// Register the setting.
$wp_customize -> add_setting(
$panel_id . '-' . $section_id . '-' . $setting_id,
$setting_args
);
// Start building an array of args for adding the control.
$control_args = array(
'label' => $setting['label'],
'section' => $panel_id . '-' . $section_id,
'type' => $setting['type'],
'description' => $setting['description'],
);
// Settings of the type 'color' get a special type of control.
if( $setting['type'] == 'color' ) {
$wp_customize -> add_control(
// This ships with WordPress. It's a color picker.
new WP_Customize_Color_Control(
$wp_customize,
$panel_id . '-' . $section_id . '-' . $setting_id,
$control_args
)
);
// Else, WordPress will use a default control.
} else {
$wp_customize -> add_control(
$panel_id . '-' . $section_id . '-' . $setting_id,
$control_args
);
}
// End this setting.
}
// End this section.
}
// End this panel.
}
}
?>
我们正在循环遍历自定义面板,添加每个面板。当我们在一个面板中时,我们循环遍历它并添加该面板的所有部分。当我们在一个部分中时,我们循环遍历该部分中的设置。每当我们添加一个设置时,我们也必须为该设置添加一个控件。循环的!
你可以通过浏览源文件 更轻松地看到这个的本质。
这种循环遍历数组的方法纯粹是我的个人技术。我喜欢这样,因为我可以通过将一个新设置添加到我的定义数组中来轻松地添加它。我无需接触或创建任何其他文件。有关我用来与$wp_customize
交互的函数的文档,请参见codex,例如add_setting()
。
让我们谈谈实时预览
预览窗格使选择和查看主题修改变得_非常快_

它更新如此之快的原因是因为“实时预览”已启用。但是,它不是我们示例主题的一部分,我个人也不在生产中使用它。有两种常见的方法可以实现实时预览,我对这两种方法都不满意。
#1:元素上的内联样式
最常用的文档方法 倾向于破坏 CSS 中的C。换句话说,JS 用于在选定的 HTML 元素上创建、注入和更新内联样式。类似这样,我主要从 codex 中提取而来
<script>
// Update default link color.
wp.customize( 'mytheme_options[link_color]', function( value ) {
value.bind( function( newval ) {
$( 'a' ).css( 'color', newval );
} );
} );
// Update sidebar link color.
wp.customize( 'mytheme_options[sidebar_link_color]', function( value ) {
value.bind( function( newval ) {
$( '.sidebar a' ).css( 'color', newval );
} );
} );
</script>
你看到问题了吗?通过更新主体链接的链接颜色,我会在预览期间无意中破坏侧边栏链接的更具体的样式。这是一个问题,我还没有找到解决方法。
这种技术的另一个问题是媒体查询。回想一下,我们已经将主题修改限定到特定的媒体查询。通过 JS 传递这些逻辑将是一项艰巨的工作。
#2:内联样式块
如果你仔细查看 Twenty Fifteen,你会发现他们并没有直接在元素上更新样式,而是更新了自定义器生成的内联样式块。我认为这要好得多。你可以在这里 找到这种技术的立足点。我试图追溯并将其重新编码成我自己真正理解和维护的东西,但我遇到了困难。我在这里找到了一些操作这里,但这张票实际上并没有让我对暂时退出实时预览感到太难过。我一直关注着自定义器 API,但我目前没有在生产中使用它,我也不会在这里深入探讨它。
这就是自定义器的特点:如果数百个客户网站运行相同的主题,那么当需要更新主题时,我们必须非常小心。我可能需要视觉回归测试 才能负责任。因此,我倾向于比使用单一客户主题时更远离自定义器中 WordPress 功能的尖端。如果核心发布了重大变更,我不想担心数百个网站。
底线是实时预览是一个不错的功能,但并非必不可少。我个人选择退出,直到 JS API 拥有更好的文档。
在前端进行样式设置
我们做了所有这些工作来获得一个 UI 用于设置,它已经工作了!我们可以将这些值用在任何我们想要的地方:在 `wp_head()` 的 CSS、body 类、TinyMCE 编辑器——任何你能想到的东西。我们甚至可以将这些值传递给我们的 JS。
让我们用它们来输出 CSS。我们已经有一些设置来控制 `body` 元素上的 `color` 和 `background-color`,所以我们可以从这里开始。
这个主题有一个名为 `CSST_TMD_Inline_Styles` 的类,它循环遍历我们所有的设置并输出相应的 CSS。这通过出色的 `add_inline_style()` 函数完成,这意味着自定义样式作为主题样式表的依赖项被排队。以下是它的实际操作情况
<?php
class CSST_TMD_Inline_Styles {
public function __construct( $output_for = 'front_end' ) {
// Add our styles to the front end of the blog.
add_action( 'wp_enqueue_scripts', array( $this, 'front_end_styles' ) );
...
}
/**
* Append our customizer styles to the <head> whenever our main stylesheet is called.
*/
public function front_end_styles() {
// Grab the styles that pertain to the front end, but don't wrap them in a style tag.
$styles = $this -> get_inline_styles( 'unwrapped', 'front_end' );
// Attach our customizer styles to our stylesheet. When it gets called, so do our customizer styles.
wp_add_inline_style( CSST_TMD, $styles );
}
...
?>
我们正在调用一个方法,`$this->get_inline_styles()`。该方法负责循环遍历每个自定义程序设置并输出一个巨大的 CSS 代码块
<?php
class CSST_TMD_Inline_Styles {
...
/**
* Loop through our theme mods and build a string of CSS rules.
*
* @param string $wrapped Whether or not to wrap the styles in a style tag. Expects 'wrapped' or 'unwrapped'.
* @param string $output_for The context for these styles. Expects 'front_end' or 'tinymce'.
* @return string CSS, either wrapped in a style tag, or not.
*/
public function get_inline_styles( $wrapped = 'wrapped', $output_for = 'front_end' ) {
// This will hold all of our customizer styles.
$out = '';
...
// For each setting...
foreach( $settings as $setting_id => $setting ) {
// Grab the css for this setting.
$css_rules = $setting['css'];
// Grab the current value for this setting.
$value = $setting['value'];
// For each css rule...
foreach( $css_rules as $css_rule ) {
// The css selector.
$selector = $css_rule['selector'];
// The css property.
$property = $css_rule['property'];
// Build this into a CSS rule.
$rule_string = "$selector { $property : $value ; }";
// Does this css rule have media queries?
if( isset( $css_rule['queries'] ) ) {
...
foreach( $queries as $query_key => $query_value ) {
...
// Add the media query key and value.
$query .= "( $query_key : $query_value )";
// If this isn't the last query, add the "and" operator.
if( $i < $query_count ) {
$query .= ' and ';
}
...
}
// Wrap the rule string in the media query.
$rule_string = " @media $query { $rule_string } ";
}
// Add the rule, which might be wrapped in a media query, to the output.
$out .= $rule_string;
}
}
...
return $out;
}
}
?>
一个内联样式块正在逐行构建,其中每个 CSS 选择器和 CSS 属性在设置数组中定义,而 CSS 值是通过自定义程序 UI 保存的。
自定义程序设置的其他用途
以上示例可能是自定义程序最重要的和最明显的用途,但它远非唯一用途。我们还有一个类,`CSST_TMD_Body_Classes`,我们在其中将每个设置作为 body 类添加,无论是在 `wp-admin` 还是前端。这对像 `color` 这样的设置来说并不重要,但如果我们有一个设置用于,比如,将侧边栏浮动到左侧或右侧,那可能会很有帮助。同样地,我们还有另一个类,`CSST_TMD_Tiny_MCE`,用于在 `wp-admin` 中为帖子编辑器设置样式,以便编辑器视图与用户在自定义程序中选择的配色方案保持一致。我将留给好奇的读者去深入研究这些;它们本身并不涵盖与自定义程序相关的新的内容。
资源和下一步
如果你发现这篇文章对你有帮助,我强烈建议你在 Make 博客上关注 自定义程序的讨论。最近的一篇文章 指出前端性能是自定义程序的一个关注点。我同意这一点,尽管我发现 Chrome 上的性能比 Firefox 上要好得多。他们还提到了为自定义程序提供修订的可能性,类似于帖子修订,这将是我这样的代理环境中受欢迎的功能。
这些年来,我在 GitHub 上看到过一些自定义控件的集合。 这个 看起来有一些有用且复杂的控件。 这是另一个。如果你有任何我应该关注的,请在评论中告诉我。
如果你觉得你能理解这篇文章的内容,以下是一些合理的下一步行动
- 在设置数组中添加更多设置。
- 使媒体查询语法更加健壮,以便你能够使用特定的 逻辑运算符 来注册媒体查询。
- 通过 Justin Tadlock 了解有关 JavaScript 模板 的知识,以便创建自定义控件类型。
如果你发现这篇文章难以理解,我很乐意通过评论回复你。你也可以回到过去,比如 Twenty Fourteen,并从一个更简单的时代剖析自定义程序代码,然后逐步向前了解每个年度主题。如有疑问,请使用 `var_dump()`!