本文约 6,152 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:改一个版权文字就要动 FTP 的日子
做主题交付时最常被问到的两个问题:「底部这行版权怎么改?」「主色能不能换成我们的品牌蓝?」如果这些值写死在 footer.php 和 style.css 里,客户每次调整都要找你,或者自己进 FTP 改文件——改错了整站白屏,最后还是你的锅。
WordPress 自带的「自定义」(Customizer)就是为了解决这件事:左侧面板填值,右侧实时预览,点「发布」即刻生效,不需要代码。很多主题作者却直接上第三方框架(Kirki、Redux、Codestar),其实原生 Customizer API 一套 Panel / Section / Setting / Control 就够用,代码不到一百行,且不引入任何额外依赖。真正需要复杂控件(repeater、typography、sortable)时才考虑框架,那是另一篇文章要谈的事。
原理分析:四个对象各自负责什么
Customizer 的所有东西都由 WP_Customize_Manager 这个管理器对象持有,我们通过 customize_register 钩子拿到它:
| 对象 | 作用 | 常见坑 |
|---|---|---|
| Panel | 顶层分组容器,可折叠 | 没有 Panel 也能直接用 Section,不必强行套一层 |
| Section | 面板内的小分组 | id 冲突会覆盖,务必加主题前缀 |
| Setting | 真正存数据的地方 | 默认存 theme_mod,改 type 才存 option |
| Control | 数据对应的 UI 控件 | 没有 Control 的 Setting 存在但不可见 |
关键点有三个。第一,Setting 与 Control 是一一对应关系:Control 的 settings 指向 Setting 的 id,只写 Control 不写 Setting,界面上输入框能出来但值存不进去。第二,数据默认是 theme_mod,每个主题独立存放,切换主题后设置丢失;传 type => 'option' 则存进 wp_options 表,换主题还在,但不支持「主题切换后预览」的暂存机制。第三,sanitize_callback 是安全底线,Customizer 保存时会调用它,返回值才是最终入库的值——不写这个函数,等于把用户输入原样写进数据库。
刷新方式由 transport 决定:refresh(默认,整页 iframe 刷新)和 postMessage(JS 局部替换,无刷新)。视觉类设置(颜色、字号)适合 postMessage,结构性设置(是否显示侧边栏)用 refresh 更稳。
保姆级实操
第一步:挂载 customize_register,建 Panel 与 Section
add_action( 'customize_register', 'mimangfei_customize_register' );
function mimangfei_customize_register( $wp_customize ) {
$wp_customize->add_panel( 'mimangfei_options', array(
'title' => __( '主题设置', 'mimangfei' ),
'description' => __( 'Logo、配色与页脚设置', 'mimangfei' ),
'priority' => 160,
) );
$wp_customize->add_section( 'mimangfei_footer', array(
'title' => __( '页脚', 'mimangfei' ),
'panel' => 'mimangfei_options',
'priority' => 20,
) );
$wp_customize->add_section( 'mimangfei_colors', array(
'title' => __( '配色', 'mimangfei' ),
'panel' => 'mimangfei_options',
'priority' => 10,
) );
}
priority 决定排序,核心自带的「站点身份」是 20、「颜色」是 40,一般用 160 把自定义面板排到后面。
第二步:注册 Setting,写好默认值与 sanitize
$wp_customize->add_setting( 'mimangfei_footer_text', array(
'default' => __( '© 2026 迷忙飞工作室', 'mimangfei' ),
'transport' => 'postMessage',
'sanitize_callback' => 'sanitize_text_field',
) );
$wp_customize->add_setting( 'mimangfei_accent_color', array(
'default' => '#2563eb',
'transport' => 'postMessage',
'sanitize_callback' => 'sanitize_hex_color',
) );
$wp_customize->add_setting( 'mimangfei_show_search', array(
'default' => true,
'sanitize_callback' => 'mimangfei_sanitize_checkbox',
) );
function mimangfei_sanitize_checkbox( $value ) {
return (bool) $value;
}
内置 sanitize 函数可直接复用:sanitize_text_field、sanitize_hex_color、esc_url_raw、absint、wp_filter_nohtml_kses。自定义回调则写在同一个文件里。
第三步:加 Control,普通控件与特殊控件
$wp_customize->add_control( 'mimangfei_footer_text', array(
'label' => __( '页脚版权文字', 'mimangfei' ),
'section' => 'mimangfei_footer',
'type' => 'text',
) );
$wp_customize->add_control( new WP_Customize_Color_Control(
$wp_customize,
'mimangfei_accent_color',
array(
'label' => __( '主色调', 'mimangfei' ),
'section' => 'mimangfei_colors',
)
) );
$wp_customize->add_control( 'mimangfei_show_search', array(
'label' => __( '顶部显示搜索框', 'mimangfei' ),
'section' => 'mimangfei_footer',
'type' => 'checkbox',
) );
文本类控件可以直接用 add_control( $id, $args ) 简写(id 与 Setting 同名);颜色、图片这类需要实例化控件类:WP_Customize_Color_Control、WP_Customize_Image_Control、WP_Customize_Cropped_Image_Control(可指定裁切宽高)、WP_Customize_Upload_Control。下拉框用 type => 'select' 加 'choices' 数组,radio 同理。
第四步:在模板里输出
$footer_text = get_theme_mod( 'mimangfei_footer_text', __( '© 2026 迷忙飞工作室', 'mimangfei' ) );
echo '<p class="site-footer__text">' . esc_html( $footer_text ) . '</p>';
get_theme_mod( $name, $default ) 的第二个参数是兜底值,未保存过时用它。输出一律再过一次 esc_html() / esc_attr() / esc_url()——sanitize 管入库,escaping 管输出,两件事不能互相替代。
第五步:主色调注入内联 CSS
add_action( 'wp_head', 'mimangfei_customizer_css' );
function mimangfei_customizer_css() {
$accent = get_theme_mod( 'mimangfei_accent_color', '#2563eb' );
?>
<style>
:root { --mf-accent: <?php echo esc_attr( $accent ); ?>; }
a, .site-title a { color: var(--mf-accent); }
.button, button, input[type="submit"] { background: var(--mf-accent); }
</style>
<?php
}
第六步:postMessage 实时预览
wp.customize( 'mimangfei_footer_text', function( value ) {
value.bind( function( newval ) {
$( '.site-footer__text' ).text( newval );
} );
} );
wp.customize( 'mimangfei_accent_color', function( value ) {
value.bind( function( newval ) {
document.documentElement.style.setProperty( '--mf-accent', newval );
} );
} );
把上面代码存成 assets/js/customizer-preview.js,再注册:
add_action( 'customize_preview_init', 'mimangfei_customize_preview_js' );
function mimangfei_customize_preview_js() {
wp_enqueue_script(
'mimangfei-customizer',
get_template_directory_uri() . '/assets/js/customizer-preview.js',
array( 'jquery', 'customize-preview' ),
'1.0.0',
true
);
}
依赖里一定要带 customize-preview,否则 wp.customize 对象不可用。这一步做完,配色调整就是所见即所得,和 WordPress主题开发 里讲的主题结构规范是同一套工程思路。
常见踩坑
- 只加 Control 不加 Setting:界面正常显示,点发布后值丢失,因为根本没有存储目标。
- id 不加前缀:
footer_text这种通用名,父主题已有同名 Setting 时会静默冲突,必须写成mimangfei_footer_text。 transport设成 postMessage 但没写 JS:预览界面改了没反应,用户以为主题坏了。sanitize_callback写错函数名:保存时报 500,Customizer 返回「无效值」,排查时先看 PHP 错误日志。type => 'option'与 theme_mod 混淆:换option后取值要用get_option(),继续用get_theme_mod()永远拿不到。- 案例:某外贸站主题把「联系电话」做成了 Customizer 项,但
add_setting时漏了sanitize_callback,运营在后台填了一段带<script>的推广代码,保存后前台直接执行,站点被挂了跳转脚本。补上sanitize_text_field并清理数据库里的历史值才恢复。 - 在
init里调用get_theme_mod:太早,主题尚未完全加载,建议挂在wp_head、wp_footer或模板文件内。 - 忘了
customize_preview_init的依赖:脚本被加载但wp.customize is undefined,控制台报错。
总结
判断标准很简单:能用原生 Customizer 解决的(文字、颜色、开关、图片、下拉),就别引框架;需要 repeater、排版组合、字段联动时再上 Kirki 之类的方案。写完自查四件事——Setting 与 Control 成对、id 有前缀、sanitize 有回调、输出有 escaping。做完这套,客户改版权、换主色再也不会来找你,同时 主题选项面板实现方式 的选型也有了个清晰的起点。
