通常,整个域使用一个 favicon。 但是有时您想根据上下文使用不同的 favicon 来提升它。 网站可能会更改 favicon 以匹配正在查看的内容。 或者,网站可能会允许用户个性化他们的主题颜色,这些首选项会反映在 favicon 中。 也许您已经看到试图提醒用户某些事件的 favicon。
多个 favicon 在技术上可以通过手动方式进行管理——Chris 向我们展示了他如何 使用两种不同的 favicon 进行开发和生产。 但是,当您达到数十或数百种变体的规模时,就该动态生成它们了。
这是我在最近的一个关于大学和大学目录的 WordPress 项目中遇到的情况。 (我之前写过一篇关于 使用地理坐标在 WordPress 中查询附近位置 的文章,内容相同。) 当查看学校的个人资料时,我们希望 favicon 使用学校的颜色而不是我们的默认蓝色,以使其更加突出。
目录中有超过 200 所学校,而且还在不断增加,我们需要使用动态方式。 幸运的是,我们已经拥有存储每个学校视觉标识数据的自定义元字段。 这包括学校颜色,这些颜色正在样式表中使用。 我们只需要一种方法将这些自定义元颜色应用到动态 favicon 上。
在这篇文章中,我将向您介绍我们的方法以及需要注意的一些事项。 您可以 查看实际的效果,方法是查看不同的学校。

SVG 是关键
由于 对 SVG favicon 的浏览器支持得到改进,实现动态 favicon 比过去容易得多。 与 PNG(或过时的 ICO 格式)相比,SVG 依赖于标记来定义矢量形状。 这使得它们变得轻量级、可缩放,最重要的是,能够接受各种有趣的形式。
第一步是用 SVG 格式创建您的 favicon。 在之后将其通过 SVG 优化器 运行以消除冗余内容也不会有坏处。 这就是我们在学校目录中使用的
连接到 WordPress
接下来,我们希望在 HTML 头部添加 favicon 链接标记。 如何做到这一点完全取决于您。 在 WordPress 的情况下,可以将其添加到子主题的标题模板中,也可以通过 wp_head() 操作将其进行 echo
处理。
function ca_favicon() {
if ( is_singular( 'school' ) ) {
$post_id = get_the_ID();
$color = get_post_meta( $post_id, 'color', true );
if ( isset( $color ) ) {
$color = ltrim( $color, '#' ); // remove the hash
echo '<link rel="icon" href="' . plugins_url( 'images/favicon.php?color=' . $color, __FILE__ ) . '" type="image/svg+xml" sizes="any">';
}
}
}
add_filter( 'wp_head' , 'ca_favicon' );
在这里,我们检查帖子类型是否是 school
,并获取我们之前使用 get_post_meta() 存储的学校的 color
元数据。 如果我们有颜色,我们将其通过查询字符串传递到 favicon.php
中。
从 PHP 到 SVG
在 favicon.php
文件中,我们首先将内容类型设置为 SVG。 接下来,我们保存传入的颜色值,或者如果没有颜色值,则使用默认颜色。
然后我们使用 PHP 的 heredoc 语法 (对 模板化 有用) echo
大的、多行块的 SVG 标记。 在使用此语法时,$color
等变量会被扩展。
最后,我们对 SVG 标记进行了一些修改。 首先,将类分配给颜色变化的元素。 其次,在 SVG 元素内添加一个样式元素,声明相应的 CSS 规则并 echo
$color
。
除了 <style>
元素,我们还可以选择用 $color
替换默认颜色,前提是默认颜色没有在太多地方使用。
<?php
header( 'Content-Type: image/svg+xml' );
$color = $_GET[ 'color' ] ?? '065281';
$color = sanitize_hex_color_no_hash( $color );
echo <<<EOL
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
<style type="text/css">
<![CDATA[
.primary {
fill: #$color;
}
.shield {
stroke: #$color;
}
]]>
</style>
<defs>
<path id="a" d="M0 34L318 0l316 34v417.196a97 97 0 01-14.433 50.909C483.553 722.702 382.697 833 317 833S150.447 722.702 14.433 502.105A97 97 0 010 451.196V34z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<g transform="translate(183 65)">
<mask id="b" fill="#fff">
<use xlink:href="#a"/>
</mask>
<use fill="#FFF" xlink:href="#a"/>
<path class="primary" mask="url(#b)" d="M317-37h317v871H317z"/>
<path class="primary" mask="url(#b)" d="M0 480l317 30 317-30v157l-317-90L0 517z"/>
<path fill="#FFF" mask="url(#b)" d="M317 510l317-30v37l-317 30z"/>
</g>
<g fill-rule="nonzero">
<path class="primary" d="M358.2 455.2c11.9 0 22.633-.992 32.2-2.975 9.567-1.983 18.375-4.9 26.425-8.75 8.05-3.85 15.458-8.458 22.225-13.825 6.767-5.367 13.3-11.433 19.6-18.2l-34.3-34.65c-9.567 8.867-19.192 15.867-28.875 21-9.683 5.133-21.525 7.7-35.525 7.7-10.5 0-20.125-2.042-28.875-6.125s-16.217-9.625-22.4-16.625-11.025-15.167-14.525-24.5-5.25-19.25-5.25-29.75v-.7c0-10.5 1.75-20.358 5.25-29.575 3.5-9.217 8.4-17.325 14.7-24.325 6.3-7 13.825-12.483 22.575-16.45 8.75-3.967 18.258-5.95 28.525-5.95 12.367 0 23.508 2.45 33.425 7.35 9.917 4.9 19.658 11.667 29.225 20.3l34.3-39.55a144.285 144.285 0 00-18.2-15.4c-6.533-4.667-13.65-8.633-21.35-11.9-7.7-3.267-16.275-5.833-25.725-7.7-9.45-1.867-19.892-2.8-31.325-2.8-18.9 0-36.167 3.325-51.8 9.975-15.633 6.65-29.05 15.75-40.25 27.3s-19.95 24.967-26.25 40.25c-6.3 15.283-9.45 31.675-9.45 49.175v.7c0 17.5 3.15 33.95 9.45 49.35 6.3 15.4 15.05 28.758 26.25 40.075 11.2 11.317 24.5 20.242 39.9 26.775 15.4 6.533 32.083 9.8 50.05 9.8z"/>
<path fill="#FFF" d="M582.35 451l22.4-54.95h103.6l22.4 54.95h56.35l-105-246.75h-49.7L527.4 451h54.95zM689.1 348.45H624L656.55 269l32.55 79.45z"/>
</g>
<path class="shield" stroke-width="30" d="M183 99l318-34 316 34v417.196a97 97 0 01-14.433 50.909C666.553 787.702 565.697 898 500 898S333.447 787.702 197.433 567.105A97 97 0 01183 516.196V99h0z"/>
</g>
</svg>
EOL;
?>
这样,您就拥有了在网站上运行的动态 favicon。
安全注意事项
当然,盲目地 echo
URL 参数会使您容易受到攻击。 为减轻这些风险,我们应该对所有输入进行清理。
在本例中,我们只对与 3 位或 6 位十六进制颜色格式匹配的值感兴趣。 我们可以包含一个函数,例如 WordPress 自带的 sanitize_hex_color_no_hash(),以确保只传入颜色。
function sanitize_hex_color( $color ) {
if ( '' === $color ) {
return '';
}
// 3 or 6 hex digits, or the empty string.
if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
return $color;
}
}
function sanitize_hex_color_no_hash( $color ) {
$color = ltrim( $color, '#' );
if ( '' === $color ) {
return '';
}
return sanitize_hex_color( '#' . $color ) ? $color : null;
}
您需要根据要传入的值添加自己的检查。
缓存以提高性能
浏览器会缓存 SVG,但默认情况下,PHP 文件不会缓存。 这意味着每次加载 favicon 时,您的服务器都会被访问。
为了减少服务器负载并提高性能,您必须显式地缓存此文件。 您可以配置您的服务器,通过您的 CDN 设置页面规则,或者在 favicon.php
的最顶部添加缓存控制头,如下所示
header( 'Cache-Control: public, max-age=604800' ); // 604,800 seconds or 1 week
在我们的测试中,如果没有缓存,我们的 1.5 KB SVG 文件在第一次加载时需要大约 300 毫秒才能处理,在后续加载时需要大约 100 毫秒。 这很糟糕。 但是通过缓存,我们将此时间缩短到了 CDN 第一次加载时的 25 毫秒,以及浏览器缓存后续加载时的 1 毫秒——与普通的 SVG 一样好。
浏览器支持
如果我们到此为止,这就不算是 Web 开发了。 我们还需要谈谈浏览器支持。
如前所述,现代浏览器对 SVG favicon 的支持很稳固,并在 Chrome、Firefox 和 Edge 的当前版本中得到完全支持。

一个需要注意的是,Firefox 需要在 favicon 声明中使用 type="image/svg+xml"
属性才能正常工作。 其他浏览器更宽容,但这只是一个好习惯。 您应该在添加它时也包含 sizes="any"
。
<link rel="icon" href="path/to/favicon.svg" type="image/svg+xml" sizes="any">
Safari
截至目前,Safari 还不支持 SVG favicon,除非使用 用于固定标签的掩码图标功能。 在我的实验中,这很繁琐且不一致。 它不能很好地处理复杂的形状或颜色,并且在整个域中缓存相同的图标。 最终,我们决定放弃,只是使用带有默认蓝色填充的回退,直到 Safari 改善支持。
回退
SVG favicon 支持虽然很稳固,但还不是 100%。 所以请务必添加回退。 我们可以使用 rel="alternative icon"
属性设置一个额外的 favicon,用于不支持 SVG 图标的情况
<link rel="icon" href="path/to/favicon.svg" type="image/svg+xml" sizes="any">
<link rel="alternate icon" href="path/to/favicon.png" type="image/png">
为了使网站更加健壮,您也可以将永恒的 favicon.ico
放到根目录中。
更进一步
我们使用了一个相对简单的 favicon,并用另一种颜色替换了它。 但是,采用这种动态方法可以打开更多可能性:修改多种颜色、position
等其他属性、完全不同的形状,以及 动画。
例如,这里有一个我称为 Favicoin 的演示。 它在 favicon 中绘制了加密货币价格的 sparkline。

实现这样的动态 favicon 不仅限于 WordPress 或 PHP;无论您的技术栈是什么,相同的原理都适用。 heck,您甚至可以使用数据 URL 和 JavaScript 在客户端实现这一点……我并不推荐将其用于生产环境。
但有一点是肯定的:我们将在未来看到 SVG favicon 的创造性应用。 您是否见过或创建过您自己的动态 favicon? 您是否有任何技巧或窍门要分享?
做得很好!(从一切都很清楚的角度来看,但对新手来说什么都不清楚 :)。对于一个 wp 插件来说,这将非常棒。此外,实际上,你已经创建了它。可以期待一个完整的产品吗?