上下文相关控制、区块与面板
WordPress 4.0与4.1还新增了功能,可根据用户在自定义器预览窗口中查看的网站部分,决定显示或隐藏自定义器界面中的某些元素。一个简单的上下文相关控制示例是:主题在首页仅显示标题图片和站点标语。这正是自定义器管理器中get_方法的理想应用场景,因为我们可以直接修改这些设置的默认控制项,使其与首页的上下文相匹配:
// 当当前页面未使用某些核心区块/控制项时将其隐藏。
$wp_customize->get_section( 'header_image' )->active_callback = 'is_front_page';
$wp_customize->get_control( 'blogdescription' )->active_callback = 'is_front_page';
对于面板、区块和控制项而言,active_callback参数接受一个回调函数名称,可以是核心自带的函数,也可以是用户自定义的函数。在注册自定义对象时也可以设置该参数。以下是Twenty Fourteen主题的示例:
$wp_customize->add_section( 'featured_content', array(
'title' => __( '特色内容', 'twentyfourteen' ),
'description' => //...
'priority' => 130,
'active_callback' => 'is_front_page',
) );在上面的示例中,直接使用了is_front_page函数。但对于更复杂的逻辑,比如判断当前视图是否为某个页面(甚至是特定ID的页面),则可以使用自定义函数(详情可参见#30251)。如果无需支持PHP 5.2,也可以将相关逻辑写在代码内部:
'active_callback' => function () { return is_page(); }若需要支持PHP 5.2,只需创建一个命名函数,然后通过active_callback参数引用该函数即可:
//...
'active_callback' => 'prefix_return_is_page';
//...
function prefix_return_is_page() {
return is_page();
}在自定义控制项、区块和面板中,还可以直接在自定义的自定义器对象类内部覆盖active_callback函数:
class WP_Customize_Greeting_Control extends WP_Customize_Control {
// ...
function active_callback() {
return is_front_page();
}
}最后,还有一个过滤器可用于覆盖所有其他active_callback相关的行为:
// 在预览单个文章时,隐藏没有描述信息的所有控制项。
function title_tagline_control_filter( $active, $control ) {
if ( '' === $control->description ) {
$active = is_singular();
}
return $active;
}
add_filter( 'customize_control_active', 'title_tagline_control_filter', 10, 2 );需要注意的是,active_callback API对于所有类型的自定义器对象(控制项、区块以及面板)都适用相同的机制。此外,如果某个区块内的所有控制项都被设置为根据上下文隐藏,那么该区块也会自动被隐藏,面板也是如此。
选择性刷新:快速、精准的更新
选择性刷新功能于WordPress 4.5引入,它只在自定义器“预览”界面中更新那些设置值发生变动的区域。由于仅更新实际发生变化的元素,因此相比完整的iframe刷新方式,它的速度更快,对正常使用的干扰也更小。正如《自定义器中的选择性刷新》一文所述,它的其他优势还包括:
- 遵循DRY原则,减少代码重复
- 实现精准的预览更新
- 将预览的不同部分与对应的设置和控制项建立关联,同时从WordPress 4.7开始还支持显示编辑快捷方式
对于纯JavaScript实现的postMessage更新方式来说,存在代码重复的问题。自定义器中的JavaScript代码必须与生成标记的PHP代码保持一致,或者采用简化方式来近似实现相同功能。而选择性刷新则不存在这种重复问题,因为JavaScript和PHP代码都没有重复。它通过Ajax请求来获取预览所需的新标记内容。
由于采用了Ajax调用,因此这种刷新方式是精准的。它会利用各种过滤器来调整标记内容,从而显示出与前端完全一致的结果。
此外,选择性刷新功能还能在预览的不同区域与对应的设置之间建立关联。自定义器会利用这种关联关系,提供可见的编辑快捷方式,帮助用户快速找到与网站特定部分相关的控制项。未来,这部分API还有可能进一步扩展,以便在预览界面内直接编辑设置,同时提供结构化的JS API来辅助使用这些部分进行设置预览。
基于以上原因,强烈建议所有主题都采用选择性刷新机制来提升用户体验,同时也可以选择额外的基于JavaScript的传输方式,进一步优化设置预览的效果。
注册自定义部件
若要使用选择性刷新功能对主题侧边栏中的部件进行部分刷新,就需要先注册相应的自定义部件。以下示例主要取自Twenty Sixteen主题,它是通过为blogdescription设置添加一个同名自定义部件来实现选择性刷新的。
function foo_theme_customize_register( WP_Customize_Manager $wp_customize ) {
$wp_customize->selective_refresh->add_partial( 'blogdescription', array(
'selector' => '.site-description',
'container_inclusive' => false,
'render_callback' => function() {
bloginfo( 'description' );
},
) );
}
add_action( 'customize_register', 'foo_theme_customize_register' );如果未指定settings参数,它将会默认与自定义部件的ID相同,就像控制项的设置默认使用控制项的ID一样。以下是自定义部件的一些关键参数:
| 变量名 | 类型 | 描述 |
settings | 数组 | 与该自定义部件关联的设置ID。 |
selector | 字符串 | 用于指定页面标记中需要刷新的元素。 |
container_inclusive | 布尔值 | 如果设置为true,刷新操作将会替换整个容器;否则仅替换容器内的子元素。默认值为false。 |
render_callback | 函数 | 用于指定刷新时需要生成的标记内容。 |
fallback_refresh | 布尔值 | 如果文档中找不到该自定义部件,则是否需要执行整个页面的刷新操作。 |
选择性刷新的JavaScript事件
以下事件会在wp.customize.selectiveRefresh触发:
partial-content-rendered
当对应元素被渲染时触发。如前所述,基于JavaScript的插件可以在此事件触发时重新生成内容。render-partials-response
在请求部分内容渲染之后,数据返回时触发。服务器会通过‘customize_render_partials_response’过滤器来处理这些数据。partial-content-moved
当侧边栏中的某个插件位置发生变化时触发。同样,基于JavaScript的插件可以在此事件触发时刷新自身。widget-updated
当WidgetPartial通过其renderContent方法被刷新时触发。sidebar-updated
当侧边栏中的某个插件被刷新或更新,或者侧边栏中的插件顺序被调整时触发,这一操作是通过reflowWidgets()函数实现的。
插件:选择是否启用选择性刷新
无论是主题还是插件,都必须主动选择才能使用选择性刷新功能。所有的核心插件和主题都已经启用了这一功能。
侧边栏中的主题支持
若要让主题侧边栏中的插件支持部分刷新功能,需要执行以下操作:
add_theme_support( 'customize-selective-refresh-widgets' );重要提示:要为插件启用选择性刷新,主题必须为每个包含该插件的元素添加before_widget>/after_widget包装元素,这些元素中需要包含该插件的ID。当你使用register_sidebar()函数时,这些包装元素就是默认存在的。例如:
function example_widgets_init() {
register_sidebar(
array(
'name' => esc_html__( '侧边栏', 'example' ),
'id' => 'sidebar-1',
'description' => esc_html__( '在此处添加插件。', 'example' ),
'before_widget' => '<section id="%1$s" class="widget %2$s">', // <= 用于选择性刷新的关键元素。
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
)
);
}
add_action( 'widgets_init', 'example_widgets_init' );插件的支持情况
即便某个主题支持选择性刷新功能,插件也必须主动启用该功能。所有的核心插件都已经启用了这一功能。以下是一个为插件添加选择性刷新支持的示例:
class Foo_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
‘foo’,
__( '示例插件', 'bar-plugin' ),
array(
'description' => __( ‘这是一个示例插件’, ‘bar-plugin’ ),
'customize_selective_refresh' => true,
)
);
if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
}
...第9行代码用于启用选择性刷新功能:
'customize_selective_refresh' => true,第13行代码则确保插件的样式表会在自定义器预览界面中始终被加载。添加该插件时无需通过完整页面刷新来获取样式表:
if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}更多相关内容可参阅《为插件实现选择性刷新支持》这篇文章。
基于JavaScript的插件支持
那些依赖JavaScript来生成标记的插件需要额外处理,相关说明可见《为插件实现选择性刷新支持》一文:
- 需要根据is_customize_preview()的结果,像处理样式表那样加载相应的JavaScript文件。
- 需要为partial-content-rendered事件添加处理函数,以便在需要时刷新插件内容:
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
// 用于刷新的逻辑
} );- 如果插件内部使用了iframe,还需要为partial-content-moved事件添加处理函数,以便在相应元素位置变化时刷新插件内容:
wp.customize.selectiveRefresh.bind( 'partial-content-moved', function( placement ) {
// 用于刷新的逻辑,可以是条件判断式的刷新逻辑
}使用postMessage提升设置预览效果
自定义器默认就能实现所有设置的预览功能。它是通过悄悄重新加载整个预览窗口来实现的,而设置值的过滤工作则由该Ajax请求中的PHP代码来完成。虽然这种方式也能正常使用,但由于每次设置值发生变化时都需要重新加载整个前端界面,因此速度较慢。选择性刷新功能通过仅刷新实际发生变化的元素来提升体验,不过由于仍需通过Ajax调用,因此在预览界面中看到更改仍会存在一定的延迟。
为了进一步提升用户体验,自定义器还提供了一个API,允许在JavaScript层面直接管理设置变更,从而实现近乎实时的预览效果。下图展示了使用基于postMessage技术的自定义CSS设置选项与默认刷新选项的对比效果:


若要使用postMessage功能,首先在添加设置时将传输方式设置为postMessage即可。许多主题还会通过修改相关设置的transport属性,来启用postMessage功能,从而对标题和站点标语等设置进行优化:
$wp_customize->get_setting( 'blogname' )->transport = 'postMessage';
$wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage';一旦某个设置的传输方式被设置为postMessage,那么当该设置的值发生变化时,就不会再触发预览界面的刷新。若要在预览界面中实时更新该设置的内容,首先需要创建并加载一个JavaScript文件:
function my_preview_js() {
wp_enqueue_script( 'custom_css_preview', 'path/to/file.js', array( 'customize-preview', 'jquery' ) );
}
add_action( 'customize_preview_init', 'my_preview_js' );你的JavaScript文件大致应如下所示:
( function( $ ) {
wp.customize( 'setting_id', function( value ) {
value.bind( function( to ) {
$( '#custom-theme-css' ).html( to );
} );
} );
wp.customize( 'custom_plugin_css', function( value ) {
value.bind( function( to ) {
$( '#custom-plugin-css' ).html( to );
} );
} );
} )( jQuery );需要注意的是,使用postMessage功能并不要求使用者必须精通JavaScript——大部分代码都是标准模板代码。那些最能从postMessage传输方式中受益的设置,只需通过简单的JavaScript操作即可实现,比如使用jQuery的.html()或.text()方法,或者通过修改
元素或其他元素的类名来触发不同的CSS规则。通过这种方式,或者通过选择性刷新功能实现更精准的实时预览,都可以提升用户体验,而无需在JavaScript中重复实现所有的PHP逻辑。通知功能

通知功能主要用于向用户提供反馈,这些反馈通常是基于某个控制项的设置值而产生的。当某个设置的验证流程返回一个WP_Error实例时,该设置对应的notifications集合中就会添加一条错误通知。在PHP中,每个WP_Error实例对应的错误信息,在JavaScript中则表现为一个wp.customize.Notification对象:
- WP_Error对象的code属性,在JavaScript中对应为notification.code。
- WP_Error对象的message属性,在JavaScript中对应为notification.message。需要注意的是,如果在PHP中为同一个错误代码添加了多条错误信息,那么在JavaScript中这些信息会合并成一条消息显示。
- WP_Error对象的data属性,在JavaScript中对应为notification.data。这一属性可用于将服务器端的额外错误上下文传递给客户端。
每当服务器端的验证流程返回一个WP_Error实例时,就会对应生成一个type属性值为“error”的wp.customize.Notification对象。
虽然目前PHP还不支持设置非错误类型的通知(参见#37281),但也可以通过JavaScript来添加非错误类型的通知,具体实现方式如下:
wp.customize( 'blogname', function( setting ) {
setting.bind( function( value ) {
var code = 'long_title';
if ( value.length > 20 ) {
setting.notifications.add( code, new wp.customize.Notification(
code,
{
type: 'warning',
message: '该主题建议标题长度不超过20个字符。'
}
) );
} else {
setting.notifications.remove( code );
}
} );
} );你还可以将“info”作为通知的type值。默认的type值为“error”。当然也可以自定义其他类型,而且这些通知可以通过CSS选择器进行样式设置,样式类名格式为notice.notice-类型名,其中“类型名”就是用户自定义的类型值。某个控制项还可以通过覆盖wp.customize.Control.renderNotifications方法,来自定义通知的显示方式。