本文约 4,593 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:为了几个小图标,加载一整包字体
很多主题为了显示搜索、菜单、日期、评论这些小图标,会引入一整套图标字体(dashicons、Font Awesome、theme icons)。代价是:一个几十 KB 的字体文件加一份 CSS,只为用到其中十几个字形。更难受的是字体加载完成前,页面上显示的是空白或方框——这就是 FOUT/FOIT。字体文件属于阻塞渲染资源,Lighthouse 会把它列进「排除阻塞渲染的资源」。
SVG 精灵(SVG sprite)是更合适的方案:把所有图标合并成一个 SVG 文件,页面里用 <use href="#icon-name"> 引用。它的优势是:可以用 currentColor 跟随文字颜色、支持多色图标、不依赖外部字体文件、不会因为字体加载失败而显示豆腐块、屏幕阅读器可通过 <title> 获得可读文本。本文给出从构建到主题集成的完整做法。若你只关心 dashicons 这一类 WordPress 自带的图标,可先看 主题加载的 dashicons 前台无用时的处理。
原理分析:图标字体到底差在哪
| 维度 | 图标字体 | SVG 精灵 |
|---|---|---|
| 加载方式 | 外部字体文件 + CSS,阻塞渲染 | 内联或单个文件,可缓存复用 |
| 渲染稳定性 | 字体未到时显示空白/方框 | 随 HTML 一起到达,无闪烁 |
| 颜色控制 | 只能单色,靠 color 继承 |
currentColor 单色,也支持多色 |
| 无障碍 | 靠 ::before 伪元素,语义缺失 |
可用 <title> + aria-hidden |
| 定位精度 | 受行高、基线影响,易错位 | 与普通元素一样参与盒模型 |
图标字体还有个隐性成本:伪元素依赖。字体图标通常写在 CSS 的 content: "\f002" 里,这类内容对屏幕阅读器不可见,对 SEO 无意义,也无法被用户选中。改成内联 SVG 后,可以用 aria-hidden="true" 明确标记为装饰性,或加 role="img" + <title> 表达语义。
精灵的实现方式有两种:内联到页面(把 <svg><symbol> 直接输出到 HTML,通常放在 wp_footer 或 wp_body_open)和外部文件引用(<use href="/assets/sprite.svg#icon-name">)。前者零额外请求、可跨域无碍,但无法被浏览器缓存复用;后者能被缓存,但旧版 Safari 对外部 use 引用支持不佳。综合看,内容站的图标数量通常不多,内联是更省事的选择。
保姆级实操
- 整理图标:确定站点真正用到的图标清单,别一股脑塞整套。
- 生成精灵文件:用构建工具(svgo + svg-sprite)或手工合并成
assets/svg/sprite.svg,每个图标包一层<symbol id="icon-xxx" viewBox="0 0 24 24">。 - 去冗余属性:用 SVGO 去掉
fill硬编码,改成fill="currentColor",才能跟随文字颜色。 - 注入页面:在
wp_body_open输出内联<svg>,外层hidden或display:none,但不能用display:none影响<use>——实际上隐藏容器用style="position:absolute;width:0;height:0;overflow:hidden"最安全。 - 写辅助函数:提供
mimangfei_icon( $name, $args ),统一输出<svg><use>。 - 替换模板:全局搜索主题的图标字体 class,逐个换成辅助函数。
- 清理旧资源:确认无残留后,注销图标字体的样式表。
# 安装构建依赖并生成精灵
npm i -D svgo svg-sprite
npx svgo -f src/icons --config=.svgo.config.js
npx svg-sprite -C --symbol --symbol-dest assets/svg --symbol-sprite sprite.svg "src/icons/*.svg"
<?php
// 子主题 functions.php:把精灵内联进页面
add_action( 'wp_body_open', 'mimangfei_output_svg_sprite', 1 );
function mimangfei_output_svg_sprite() {
$path = get_stylesheet_directory() . '/assets/svg/sprite.svg';
if ( ! file_exists( $path ) ) {
return;
}
$svg = file_get_contents( $path );
// 去掉 XML 声明与注释,避免重复输出头部
$svg = preg_replace( '/<\?xml[^>]*\?>/i', '', $svg );
$svg = preg_replace( '/<!--.*?-->/s', '', $svg );
echo '<div class="svg-sprite" aria-hidden="true" '
. 'style="position:absolute;width:0;height:0;overflow:hidden">' . $svg . '</div>';
}
<?php
// 统一图标输出函数:可选 title 提升无障碍
function mimangfei_icon( $name, $args = array() ) {
$defaults = array(
'class' => 'icon',
'title' => '',
'size' => 24,
);
$args = wp_parse_args( $args, $defaults );
$id = 'icon-' . sanitize_html_class( $name );
$class = esc_attr( $args['class'] . ' icon--' . sanitize_html_class( $name ) );
$size = absint( $args['size'] );
$html = sprintf(
'<svg class="%1$s" width="%2$d" height="%2$d" aria-hidden="%3$s" focusable="false">',
$class,
$size,
$args['title'] ? 'false' : 'true'
);
if ( $args['title'] ) {
$html .= '<title>' . esc_html( $args['title'] ) . '</title>';
}
$html .= '<use href="#' . esc_attr( $id ) . '"></use></svg>';
return $html;
}
/* 图标基础样式:跟随文字颜色与行高 */
.icon {
display: inline-block;
vertical-align: -0.125em;
fill: currentColor;
flex: none;
}
.icon--search { color: var( --muted, #656d76 ); }
.svg-sprite symbol {
/* 统一用 currentColor,删除源文件里的硬编码 fill */
fill: currentColor;
}
常见踩坑
display: none隐藏精灵容器:部分浏览器下<use>引用display:none的 symbol 会渲染失败。用绝对定位 + 零尺寸的方式隐藏。- SVG 里残留硬编码
fill="#000":导致currentColor无效,深色模式下图标变成黑块。必须用 SVGO 统一移除或改成currentColor。 - 案例:某站替换完图标后,Chrome 正常、Safari 全部空白。原因是用了外部文件引用
<use href="/assets/sprite.svg#icon-search">,跨域与部分旧 Safari 版本对外部引用支持不稳定。改成内联精灵后所有浏览器正常。教训:图标数量在几十个以内时,优先内联,别为了省几 KB 引入兼容性风险。 viewBox缺失:从设计稿导出的 SVG 常没有viewBox,导致无法按尺寸缩放。生成精灵时必须保留原始viewBox。- 图标 ID 冲突:主题和插件各输出一份精灵,同名 symbol 会互相覆盖。给自己的 symbol 加统一前缀,如
mmf-icon-。 - 忘记清理旧字体:图标换完了,字体 CSS 还在加载,纯属浪费。确认无残留引用后再
wp_dequeue_style()。 - 缓存与版本:内联精灵会随每个页面输出,改动后页面缓存不刷新会看到旧图标。精灵内容变化时要记得清全站缓存。
- 无障碍过度标记:装饰性图标应
aria-hidden="true";只有承担语义的图标才加<title>。全加 title 会让屏幕阅读器读出大量噪音。
总结
是否值得从图标字体迁移,看两个条件:站点用到的图标是否少于 50 个(精灵体积可控),以及是否存在深色模式、多色图标、无障碍需求(图标字体难以满足)。行动清单:清点图标 → SVGO 清理并生成精灵 → wp_body_open 内联 → 写统一输出函数 → 逐个替换模板 → grep 确认无残留后注销字体样式 → 清缓存验证。迁移完成后,还可以顺手处理 主题深色模式(Dark Mode)实现方式,因为 SVG 图标用 currentColor 后会自动跟随深色配色,这是字体图标做不到的。
