3.4 图片资源与性能优化

> 图片路径写成常量,首屏少传 300KB,按收益排序逐条砍。

图片放在哪:两类目录两种命运

论坛里的图分两种来路,放的地方完全不同:

类型 目录 引用方式 归属
界面基础图片 后台风格页配置的界面基础图片目录(默认 static/image/common) {IMGDIR} / $_G['style']['imgdir'] 跟着套系走
扩展图片 扩展图片目录(你的套系内) {STYLEIMGDIR} / $_G['style']['styleimgdir'] 跟着风格走
用户上传附件 data/attachment/ 由附件表记录生成链接 用户内容
头像 后台设置的远程调用或本地目录 模板头像变量输出 用户内容

两个目录的配置位置在后台 → 界面 → 模板风格里,和配色一起改。换套系时只有界面基础图片目录会整体替换,扩展图片目录是你自己的东西,所以自家 logo、品牌图一律放扩展图片目录。

不要硬编码路径

硬编码是模板开发里最常见也最难查的坑:

<!-- 错误:换套系、换域名、移动端都直接崩 -->
<img src="/template/我的标识/style/t1/images/logo.png" />

<!-- 正确 -->
<img src="{STYLEIMGDIR}/images/logo.png" alt="{$_G['site']}" />

第二条为什么必须这么写:

  1. 换套系不用改代码。 目录名写在配置里,不是写死在模板里。
  2. 换域名不用改代码。 Discuz! 会输出绝对地址,站点搬家或改域名不会丢图。
  3. CDN 友好。 后台可以把静态资源单独挂到 CDN 或独立域名,模板不用知道这件事。

在 .php 业务文件里同理,用 $_G['style']['styleimgdir']。唯一该硬编码的是静态目录常量,比如 DISCUZ_ROOT_STATIC(存放 md/txt/js/css/json/xml/图片等静态文件的目录)——它的存在意义就是对外提供静态资源路径,属于合理使用。

图片优化:从大到小的四刀

第一刀是尺寸。 上传 2000px 宽的图然后靠 CSS 缩到 200px 显示,是最常见的浪费。Discuz! 论坛附件本身就支持缩略图分类(后台 → 界面 → 上传设置里配置),发帖时先生成缩略图,模板里用缩略图地址,别引用原图。

第二刀是格式。 站点装饰用的图标、箭头、渐变这类纯图形,用 PNG 或 GIF 就够,不要用 JPEG——JPEG 在纯色块和文字周围会产生明显的振铃噪点。照片才用 JPEG。

第三刀是 CSS sprite。 一堆小图标合成一张图,用背景定位取:

.dt-ico-reply,
.dt-ico-share,
.dt-ico-fav {
    background-image: url('{STYLEIMGDIR}/images/icons.png');
    background-repeat: no-repeat;
    display: inline-block;
    width: 16px;
    height: 16px;
}
.dt-ico-reply { background-position: 0 0; }
.dt-ico-share { background-position: -16px 0; }
.dt-ico-fav { background-position: -32px 0; }

代价是这张图会被第一个用到它的页面缓存住,后续页面直接命中缓存不再请求。十几个以内的小图标合成一张,收益明显;上百个就别合了,CSS 本身会变大,而且精灵图很难维护。

第四刀是懒加载。 首屏之外的图加 loading="lazy",一行代码的事:

<img src="{STYLEIMGDIR}/images/banner.jpg" alt="" loading="lazy" />

注意别对首屏图片加,首屏图一慢就是白屏,体验反而更差。

静态资源的版本号

浏览器缓存是双刃剑:CDN 和本地缓存让二次访问快得飞起,但资源更新后用户拿到旧版。解法是给引用加版本号,改了内容就换号:

<link rel="stylesheet" href="{STYLEIMGDIR}/style.css?v={STYLEVER}" />
<script src="{STYLEIMGDIR}/js/main.js?v={STYLEVER}"></script>

{STYLEVER} 是风格常量之一,取值来自后台风格页。这样同一次发版里,所有页面引用的版本号一致,缓存自然失效;没改的文件版本号不变,继续走缓存。比自己拼时间戳好——时间戳每次都变,等于把所有缓存全废了。

模板里的资源引入位置

CSS 和 JS 放在哪里,直接决定渲染过程会不会被卡住:

资源 位置 原因
关键 CSS <head> 里 晚加载会导致首屏闪白
非关键 CSS 可延后 不影响首屏视觉的可以晚点来
统计类 JS 页面底部 加载慢也不影响阅读
需要立即执行的 JS <head> 且不设 defer 否则执行时机不对
其余 JS 加 defer 下载和解析不阻塞文档解析

defer 对大多数第三方脚本是安全的默认值,但表单校验、编辑器初始化这类必须在 DOM 就绪后同步执行的脚本不要加。加上之后表现不对,八成就是这个原因。

优先级表格

按收益从高到低排,动手时从上往下做,一轮下来通常就够了:

优先级 动作 预期收益 成本
1 列表页、门户页改用附件缩略图,不引原图 极大(首屏可能从数 MB 降到百 KB 级) 低,改模板输出
2 给首屏外的图片加 loading="lazy" 大 极低,一行属性
3 换图片格式(装饰图转 PNG/GIF) 中 低,替换文件
4 小图标做 sprite 中 中,要算坐标
5 非关键 JS 加 defer 中 极低
6 静态资源加版本号 {STYLEVER} 中(提升回访体验) 极低
7 把装饰图移到扩展图片目录的子目录,按需启用 小 中
8 合并自定义 CSS/JS 减少请求数 小(HTTP/2 后更小) 中

第 8 项放最后是有道理的:HTTP/2 之后多个小文件的请求开销已经很低,合并的收益远不如把原图换成缩略图。花两小时合并 CSS,不如花十分钟把帖子列表的附件换成缩略图。

验证手段

改完必须实测,别凭感觉。浏览器开发者工具的 Network 面板按 Size 排序,看首屏总共传了多少;再勾上 Slow 3G 限速刷新,观察首屏时间。前端资源之外,页面本身的模板编译缓存也要确认已经更新,否则你测的是缓存里的旧页面。

论坛上有个特殊场景值得单独留意:论坛首页的板块图标、版块描述图往往是站长自己上传的大图,很多站没做缩略图就直接贴原图。一张 300KB 的板块图乘以十几个板块,首屏就废了——这是最划算的一处优化点。