懒加载兜底:WordPress 静态 PHP 页面图片不显示的 MU 插件修法

34次阅读
没有评论

WordPress 自定义静态 PHP 页面图片全部不显示,排查发现是 Smush 懒加载 JS 没注入——因为模板里没有 wp_head() 和 wp_footer()。与其逐个改模板,不如一个 MU 插件在服务端做兜底回写,配合浏览器原生 loading="lazy",彻底甩掉 JS 依赖。

lifetruth.top

最近给网站做了几个自定义静态页面:/about-me/、/contact/、/series/,放在主题的 pages/ 目录下,是独立的 PHP 模板文件。页面内容正常,但图片全部不显示。

查看源码,<img> 标签长这样:

<img class="lazyload" src="data:image/svg+xml;..." data-src="https://example.com/a.jpg">

真实地址被放进了 data-src,src 是一个 SVG 占位图。正常情况下,Smush 插件的懒加载 JS 会把 data-src 回写到 src,图片就出来了。但这些静态页面里,JS 根本没有生效。

症状很明确:图片就在那里,只是没被”激活”。

一、为什么 Smush 的懒加载 JS 没加载

Smush 的懒加载依赖它自己的 smush-lazy-load.min.js,这个脚本通过 WordPress 的 wp_head() 或 wp_footer() 钩子注入。标准主题的 header.php 和 footer.php 里都会调用这两个函数,所以文章页、默认页面都没问题。

但我的静态页面是独立 PHP 模板,文件里只有完整的 HTML 结构,没有调用 wp_head() 和 wp_footer()。

结果就是:

  • Smush 的懒加载 JS 没有被加载
  • 图片的 data-src 永远不会被回写
  • 浏览器只看到占位图,真实图片不显示

简单说,静态模板”绕开”了 WordPress 的资源队列,导致插件失效。

不是 Smush 坏了,是模板没给 Smush 上场的机会。

二、思路:为什么不用 wp_head() 方案

最直觉的修法是给每个模板加上 wp_head() 和 wp_footer()。这确实干净,但有个问题:它会引入全站所有插件的 CSS 和 JS——不仅仅是 Smush 的那一个脚本,而是所有通过钩子注册的资源。

我的静态页面追求的是轻量、快加载,塞一整坨插件资源进去,和初衷相悖。

所以我选了另一个思路:用一个 MU 插件(Must-Use Plugin,WordPress 启动时自动加载的插件,无需手动启用)在服务端做一次性处理:

  1. 只针对使用 pages/ 目录模板的页面
  2. 在输出前把 data-src 回写成 src,data-srcset 回写成 srcset
  3. 移除 lazyload 类,补上 lazyloaded
  4. 加上原生 loading="lazy" 和 decoding="async"
  5. 不注入任何额外的 JS,彻底摆脱对 Smush 懒加载脚本的依赖

同时,在 Smush 设置里关闭 Lazy Load,让它只负责图片压缩。

服务端回写 + 原生懒加载 = 零 JS 依赖的最优组合。

三、为什么这个方案最快

四个字:服务端、原生。

服务端回写:图片直接以真实 src 输出,浏览器解析到就开始加载,没有 JS 延迟。不需要等脚本下载、解析、执行,图片请求和 HTML 解析同步进行。

原生懒加载:loading="lazy" 是浏览器内置能力,零额外请求,性能最好。Chrome、Edge、Firefox 全支持,Safari 从 15.4 起也支持了。

无额外 JS:不再加载 Smush 的懒加载脚本,减少一个网络请求和主线程任务。

只处理静态页:不影响文章页和其他页面,范围可控。

但这里有一个前提:你的图片在 HTML 源码里确实被 Smush 替换成了 data-src 形式。如果 Smush 的懒加载本身就关了,或者图片原本就是正常 src,那就不需要这个插件。

四、MU 插件核心代码

插件放在 /wp-content/mu-plugins/ 下,自动生效,无需手动启用。核心逻辑如下:

add_action('template_redirect', function () {
    if (!is_page()) return;
    $tpl = get_page_template_slug();
    if ($tpl === '' || strpos($tpl, 'pages/') === false) return;

    ob_start(function ($html) {
        if (stripos($html, 'data-src') === false) return $html;

        return preg_replace_callback(
            '/<img\b[^>]*>/i',
            function ($m) {
                $tag = $m[0];
                if (stripos($tag, 'lazyload') === false) return $tag;
                if (!preg_match('/\bdata-src\s*=\s*(["\'])(.*?)\1/i', $tag, $dm))
                    return $tag;

                $real_src = $dm[2];
                // 回写 src
                $tag = preg_replace('/\bsrc\s*=\s*(["\'])(.*?)\1/i',
                    'src="' . $real_src . '"', $tag, 1);
                // 回写 srcset
                if (preg_match('/\bdata-srcset\s*=\s*(["\'])(.*?)\1/i', $tag, $sm)) {
                    $tag = preg_replace('/\bsrcset\s*=\s*(["\'])(.*?)\1/i',
                        'srcset="' . $sm[2] . '"', $tag, 1);
                }
                // 移除 data-src / data-srcset
                $tag = preg_replace('/\sdata-src\s*=\s*(["\'])(.*?)\1/i', '', $tag);
                $tag = preg_replace('/\sdata-srcset\s*=\s*(["\'])(.*?)\1/i', '', $tag);
                // 替换类名
                $tag = str_replace('lazyload', 'lazyloaded', $tag);
                // 补原生懒加载
                if (!preg_match('/\bloading\s*=/i', $tag))
                    $tag = preg_replace('/<img\b/i', '<img loading="lazy"', $tag, 1);
                if (!preg_match('/\bdecoding\s*=/i', $tag))
                    $tag = preg_replace('/<img\b/i', '<img decoding="async"', $tag, 1);
                return $tag;
            }, $html);
    });
}, 0);

这段在做什么:拦截页面输出,找到所有带 lazyload 类和 data-src 的 <img> 标签,把真实地址回写到 src,同时补上浏览器原生的懒加载属性。

完整版还处理了 src 不存在时补 src、srcset 不存在时补 srcset 等边缘情况,但核心逻辑就是上面这些。

▸ 小贴士:MU 插件(Must-Use Plugin)和普通插件的区别是:它放在 wp-content/mu-plugins/ 目录下,WordPress 启动时自动加载,不需要在后台手动启用,也无法被管理员误关。非常适合做这种”基础设施级”的兜底逻辑。

五、效果对比

上传 MU 插件后,刷新静态页面,图片立刻恢复。源码变化一目了然:

修复前:

<img class="lazyload" src="data:image/svg+xml;..." data-src="https://example.com/a.jpg">

修复后:

<img loading="lazy" decoding="async" class="lazyloaded" src="https://example.com/a.jpg">

图片直接加载,不再依赖任何 JS。文章页不受影响,已有真实 src 的图片也不会被二次处理。

一个文件搞定所有静态页,不用改 10 个模板。

六、完整配置清单

从运行速度看,这是目前最优的组合:

  1. Smush 只做图片压缩,关闭它的懒加载
  2. 静态页图片直接输出真实 src
  3. 非首屏图片用 loading="lazy",首屏大图用 loading="eager" + fetchpriority="high"
  4. MU 插件只做一次服务端字符串替换,不引入额外 JS

当然,如果你的站点全部都是标准模板页面,都正常调用了 wp_head() 和 wp_footer(),那这个问题压根不会出现。这个方案是为”有自定义静态模板”这个特定场景准备的。

写在最后

这个问题的本质是静态模板绕过了 WordPress 的资源队列,导致插件的懒加载 JS 无法注入。解决思路不是硬把 wp_head() 塞进每个模板,而是用一个轻量的 MU 插件在服务端做兜底回写,同时把懒加载交给浏览器原生能力。

如果你也正在面对自定义页面图片不显示的问题,不妨检查一下源码里图片是不是被替换成了 data-src 形式。如果是,下一步建议:把上面的 MU 插件代码保存为 mu-plugins/fix-static-lazyload.php 上传,同时关闭 Smush 的 Lazy Load 开关——效果立竿见影。

你站上还有哪些插件”偷偷”依赖 wp_head() 的场景?欢迎留言交流。

正文完
 0
评论(没有评论)