搜索全站
性能优化 16 分钟阅读

WordPress 全站通知条 / 公告栏:插件方案、关闭逻辑与缓存兼容处理

活动过期三天了公告还挂在首页?本文讲清 WordPress 顶部通知条的实现方式:Hello Bar 类插件、主题选项、纯代码三种路径,重点解决「关闭后仍然弹出」「缓存导致内容不更新」两个高频故障。

U
UFO_PF 2026-08-06 · 阅读 16 分钟
Wo 性能优化
阅读提示
本文约 6,114 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:用户点过「×」,刷新一次又弹出一次

全站通知条(Notification Bar / Announcement Bar)看着是个小功能,真上线却经常翻车。常见投诉有两类:一是用户点了关闭,点进下一篇文章公告又回来了——因为他点的只是视觉隐藏,浏览器根本没记住状态;二是运营在后台改了文案,前台三天没变,一问才知道是页面缓存把旧公告缓存住了,而且缓存的粒度是全站共用的那一份。

这两类问题的根源不在插件选得对不对,而在于理解状态存在哪(localStorage / cookie / user meta)和内容什么时候被渲染(PHP 服务端输出 vs JS 异步拉取)。插件帮你做的事是把这两件事打包,但一旦和页面缓存组合,默认实现往往会失效。本文从机制讲起,给出三条可行路径和一套兼容全站缓存的方案。

原理分析:一块横幅背后的两个独立问题

问题 存放 典型实现 缓存兼容性
「已关闭」状态 浏览器端 cookie 或 localStorage wp_footer 注入 JS 读写 好(完全客户端)
登录用户的关闭状态 user_meta update_user_meta() 差(会让页面变成用户私有)
公告正文 wp_options 或主题 mod 服务端 echo 差(被页面缓存固化)
是否对当前页显示 PHP 条件判断 is_front_page() 等 中

关键结论有三条:

第一,关闭状态必须存在浏览器端。 用 localStorage 存一个版本号最稳妥:localStorage.setItem('announce_v', '2026-10-01')。每次运营改文案就在后台 bump 一次版本号,版本号变了则所有用户的关闭状态自动失效、公告重新展示。这解决了”改了文案但用户之前关闭过,永远看不到新活动”的死局。

第二,服务端渲染公告内容=给页面缓存挖坑。 只要公告 HTML 进入 <body>,Nginx FastCGI Cache、WP Rocket、Cloudflare APO 都会把它当作页面的一部分缓存。解决方案有两条:让公告 HTML 走 JS 异步拉取(牺牲一点首屏),或者干脆接受缓存规则——更新公告后自动执行一次清缓存。

第三,布局抖动(CLS)是隐形杀手。 很多主题把公告条用 JS 动态插到 <header> 之前,页面已经渲染完才出现,正文整体下移几十像素,Google 的 CLS 指标直接爆红。正确做法是预留占位高度,或者让公告条 position: fixed 不占文档流。

布局指标相关:WordPress 核心网页指标优化

保姆级实操

步骤 1:先选实现路径

三条路,按站点情况选:

  • 纯静态 / 不需要频繁改:主题自带公告选项(Kadence、Astra Pro、OceanWP 都有),零插件,改起来最快。
  • 需要定时上下线、需要做 A/B 测试:用专用通知条插件。免费方案里 WP Notification Bars、Holler Box(免费版可用) 是常见选项;Hello Bar 是 SaaS,需要外嵌外部脚本,国内加载偏慢,一般不建议。付费里 Holler Box Pro 支持关闭状态与转化统计。
  • 追求零依赖 / 要和缓存共存:自己写,下面给完整代码。

步骤 2:注册后台配置项

把公告文案、链接、版本放进 Customizer,运营可以直接改而不用碰代码:

add_action( 'customize_register', function( $wp_customize ) {
    $wp_customize->add_section( 'mf_announce', array(
        'title'    => '全站公告',
        'priority' => 20,
    ) );
    $wp_customize->add_setting( 'mf_announce_text', array(
        'default'           => '',
        'sanitize_callback' => 'wp_kses_post',
        'transport'         => 'refresh',
    ) );
    $wp_customize->add_setting( 'mf_announce_ver', array(
        'default'           => '1',
        'sanitize_callback' => 'sanitize_text_field',
    ) );
    $wp_customize->add_control( 'mf_announce_text', array(
        'label'   => '公告内容(支持 <a> 标签)',
        'section' => 'mf_announce',
        'type'    => 'textarea',
    ) );
    $wp_customize->add_control( 'mf_announce_ver', array(
        'label'       => '版本号(改文案时必须 +1,用于重置用户关闭状态)',
        'section'     => 'mf_announce',
        'type'        => 'text',
    ) );
} );

步骤 3:服务端输出(避免 CLS 的写法)

关键点是用一个固定高度的容器占位,内容由服务端输出,关闭由 JS 处理:

add_action( 'wp_body_open', function () {
    $text = get_theme_mod( 'mf_announce_text', '' );
    $ver  = get_theme_mod( 'mf_announce_ver', '1' );
    if ( ! $text ) {
        return;
    }
    printf(
        '<div id="mf-announce" class="mf-announce is-hidden" data-ver="%1$s" role="region" aria-label="站点公告">
            <div class="mf-announce-inner">%2$s</div>
            <button type="button" class="mf-announce-close" aria-label="关闭公告">&times;</button>
        </div>',
        esc_attr( $ver ),
        wp_kses_post( $text )
    );
} );

is-hidden 类默认隐藏,由 JS 判断是否有历史关闭记录再决定是否显示——这样即使全站缓存了 HTML,最终是否展示仍由浏览器端决定。

步骤 4:关闭逻辑 + 版本号(核心代码)

(function () {
    var KEY = 'mf_announce_dismissed_ver';
    var bar = document.getElementById('mf-announce');
    if (!bar) { return; }

    var ver = bar.getAttribute('data-ver');
    var seen = localStorage.getItem(KEY);

    if (seen === ver) {
        // 已经关过当前版本,保持隐藏
        bar.parentNode.removeChild(bar);
        return;
    }
    bar.classList.remove('is-hidden');

    bar.querySelector('.mf-announce-close').addEventListener('click', function () {
        bar.classList.add('is-hidden');
        try { localStorage.setItem(KEY, ver); } catch (e) { /* Safari 隐私模式会抛错 */ }
    });
})();

注意那个 try/catch:Safari 无痕模式下 localStorage.setItem 会抛异常,不包一层会让整个 JS 崩掉。

步骤 5:CSS(不吃 CLS)

.mf-announce {
    position: relative;
    background: #1f2937;
    color: #f9fafb;
    font-size: 14px;
    line-height: 1.6;
    text-align: center;
}
.mf-announce.is-hidden { display: none; }
.mf-announce-inner { padding: 10px 56px 10px 16px; max-width: 1200px; margin: 0 auto; }
.mf-announce-inner a { color: #fbbf24; text-decoration: underline; }
.mf-announce-close {
    position: absolute; top: 6px; right: 12px;
    background: none; border: 0; color: #f9fafb;
    font-size: 22px; line-height: 1; cursor: pointer;
}
@media (max-width: 600px) {
    .mf-announce-inner { padding: 10px 44px 10px 12px; font-size: 13px; }
}

步骤 6:更新公告后同步清理缓存

服务端输出公告的场景,必须在保存配置时触发一次清缓存:

add_action( 'customize_save_after', function() {
    // WP Rocket
    if ( function_exists( 'rocket_clean_domain' ) ) { rocket_clean_domain(); }
    // LiteSpeed Cache
    if ( function_exists( 'do_action' ) ) { do_action( 'litespeed_purge_all' ); }
    // Nginx FastCGI 或自建 redis:用 WP-CLI 在 deploy hook 里清,见下方 bash
} );
# Nginx FastCGI Cache 目录清理(路径按服务器实际配置调整)
rm -rf /var/run/nginx-cache/*
echo "FastCGI cache purged"

常见踩坑

  1. 关闭后换页又弹出来:典型原因是把状态存在了 PHP session 而不是浏览器,或者插件用的是 .hide() 纯 CSS 隐藏,没写任何持久化。用上面的 localStorage + 版本号方案一次性解决。
  2. 公告被 wp_body_open 之外的位置卡住:很多老主题没有实现 wp_body_open() hook(WordPress 5.2 引入)。用 wp_head 输出会被 <body> 标签拦截解析错误,正确做法是先在 header.php 里补 <?php wp_body_open(); ?>。
  3. 案例:某外贸客户站在 9 月 30 日上线了”国庆 8 折”的公告栏,设定 10 月 8 日下线。10 月 9 日客户投诉:优惠码还在被使用,因为公告虽然到期被移除了,CDN 边缘节点缓存的旧页面还在分发,且有一部分用户是在移动端 App 内置浏览器里看到的离线副本。复盘后改成:公告内容带明确到期时间戳,前端再次 Date.now() 校验,过期则不渲染——服务端/缓存层的过期控制永远要有一层客户端兜底。
  4. CDN(Cloudflare)把整页 HTML 缓存:尤其开了 APO 的情况。必须在 Cloudflare 后台配 purge 规则,或者干脆让公告走异步接口。
  5. user_meta 存关闭状态导致缓存分裂:登录用户可以精确记忆,但只要日志缓存按 user key 分片,缓存命中率会暴跌,TTFB 反升。非必要不要用。
  6. 无障碍与 SEO:公告条会被算进正文相关性。如果是长期挂在首页的通用文案(”本店支持七天无理由退货”),建议给容器加 role="region" 且不要放进 <h1> 附近,避免稀释标题语义。
  7. CLS 超标:动态 JS 插入公告必须用固定高度占位或 fixed 定位,否则每次加载都会抖一下,Core Web Vitals 里 CLS 直接爆表。

总结

判断标准一句话:是否展示由浏览器决定,什么时候更新由版本号决定。 具体落地三条:①用「版本号 + localStorage」而不是单纯布尔标记管理关闭状态;②服务端输出的公告必须在配置变更钩子(如 customize_save_after)里触发一次全站清缓存;③前端再加一层时间戳兜底,防止任何一层缓存失效导致过期活动继续展示。插件适合快速上线和需要转化统计的场景,自己写方案适合缓存层完全可控、不希望额外依赖的站点。

缓存清理相关:WordPress 缓存插件配置与清理

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。