藉助 SVG 生成帶標識的 favicon

之前做了一個 Chrome 插件,可以用於區分不同的開發環境,效果如下

主要實現過程其實不復雜,首先獲取網站 favicon,然後給 favicon 添加標識,重新繪製生成就行了

其中,這裏的圖標就是通過 SVG 生成的,下面看看具體實現吧。

一、favicon 的獲取方式

想知道獲取方式,可以先了解設置方式。

一般有兩種方式可以設置網站的 favicon

第一種,通過 link 標籤設置(需要rel="icon"屬性)

<link rel="icon" href="xxx.png">

第二種,直接在網站根目錄放一張favicon.ico(必須是這個名稱,瀏覽器默認的),html 中什麼也不用設置

- 網站目錄
 index.html
 favicon.ico

如果以上都沒有設置,那麼大概率會看到以下錯誤

瞭解這些,獲取就簡單了,先通過link獲取,只要rel包含icon就行了

const link = document.querySelector('link[rel~="icon"]');

如果找不到,可以請求/favicon.ico,這裏直接添加一個link

function getLink(){
    const link = document.querySelector('link[rel~="icon"]');
    if (link) {
        return link
    } else {
        const link = document.createElement('link');
        link.rel = "icon";
        link.href = "/favicon.ico"
        document.head.append(link);
        return link
    }
}

這樣獲取的link就保證存在了,然後就是繪製

二、利用 canvas 進行繪製

由於需要合成圖像,所以需要先繪製原有圖像。提到圖像繪製,可以想到 canvas 繪製,只需要一點點 canvas 基礎知識就足夠了。具體實現如下

const canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () ={
    canvas.height = img.height;
    canvas.width = img.width;
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    let dataURL = canvas.toDataURL("image/png");
    resolve(dataURL);
    canvas = null;
};
img.src = url;

由於存在/favicon.ico沒有設置的情況,所以需要在 img 加載失敗的時候給一張默認圖

img.onerror = () ={
 resolve("默認圖base64");
}

這樣就能獲取到 favicon 的圖像信息了

三、利用 SVG 進行圖片合成

在上面的基礎上,其實可以繼續通過 canvas 進行繪製,再繪製一個標籤也不是難事。不過這裏可以採用 SVG 的方式來進行繪製,有以下一些優點

  1. 成本更低,比 canvas 更易理解

  2. 靈活性高,可以通過 CSS 進行一些樣式控制

首先,我們可以在 HTML 中自由的、像正常網頁開發一樣,繪製這樣一個佈局,相信沒有什麼難度

<body>
  <strong>local</strong>
  <img src='xxx.png'>
</body>

由於寬度有限,所以需要強制文本換行,超出隱藏,關鍵樣式如下

strong{
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  text-transform: uppercase;
  background-color: orange;
  color: #fff;
  padding: 0px 2px;
  border-radius: 2px;
  font-size: 12px;
  box-sizing: border-box;
  max-width: 100%;
  width: max-content;
  height: 16px;
  line-height: 16px;
  word-break: break-all;
  overflow: hidden;
}

接着,將這一段 html 放入 foreignObject標籤中,關於  foreignObject[1] 的作用,有興趣的可以參考張鑫旭老師的這篇文章 SVG 簡介與截圖等應用 [2],在這裏,你可以簡單理解爲是可以包含 HTML 的標籤,而 SVG 本身也是一種圖片,這樣就達到了合成圖像的目的

具體實現如下

const link = getLink();
const icon = await img2Base64(link.href);
const favicon = `data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><foreignObject x="0" y="0" width="100%" height="100%"><body xmlns="http://www.w3.org/1999/xhtml">
  <style>
    html,body{
      height: 100%;
      margin: 0;
      text-align: center;
    }
    img{
      display: block;
      width: 100%;
      height: 100%;
      object-fit: contain;
    }
    strong{
      position: absolute;
      bottom: 0;
      left: 50%;
      transform: translateX(-50%);
      text-transform: uppercase;
      background-color: ${color};
      color: #fff;
      padding: 0px 2px;
      border-radius: 2px;
      font-size: 12px;
      box-sizing: border-box;
      max-width: 100%;
      width: max-content;
      height: 16px;
      line-height: 16px;
      word-break: break-all;
      overflow: hidden;
    }
  </style>
  <strong>local</strong>
  <img src='${icon}'></img>
  </body></foreignObject></svg>`.replace(/\n/g, '').replace(/\t/g, '').replace(/#/g, '%23')

這裏需要注意幾點

  1. img 標籤在 svg 中需要寫成<img></img>閉合標籤形態,不然會被認爲結構錯誤

  2. img 只能使用內聯圖片,比如 base64,不然就是僅僅是一個字符串,這也是爲何需要繪製原始 favicon 的原因

  3. 如果使用內聯 svg,需要對 svg 進行轉義

  4. 字符串中的單雙引號問題也需要注意一下

然後,將生成的 SVG 直接設置爲 favicon,如下

link.href= favicon;

這樣就能正常地渲染了~

完整實現可以參考項目:https://github.com/XboxYan/auto-dev-favicon-chrome-plugin[3]

四、目前一些侷限性

首先是兼容性。

目前只有 Chrome 和 Firefox 是支持的(不過本文是運用在 Chrome 插件,所以無需關注這個問題),但是爲了兼容其他瀏覽器,可以用一個 .ico來兜底

<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">

另外,在 Chrome 上還有一個限制(不知道是不是 Chrome 更新後的安全限制),當 favicon 使用 svg 格式圖片時,此時的 svg 會處於一種 secure-static-mode[4],在這種模式下,所有動畫都不會執行,會處於第一幀,比如下面這個例子

<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
  <foreignObject width="100%" height="100%">
      <body xmlns="http://www.w3.org/1999/xhtml">
        <style>
        html,body{
            margin: 0;
            height: 100%
        }
        div{
            height: 100%;
            background: pink;
            animation: hue 3s infinite;
        }
        @keyframes hue {
            to {
                filter: hue-rotate(360deg)
            }
        }
        </style>
        <div></div>
      </body>
    </foreignObject>
</svg>

很簡單的一個背景顏色動畫。在 Firefox 上是用作 favicon 是有動畫的

Firefox 下是動畫狀態

但是,Chrome 上卻不行,只有禁止的第一幀

Chrome 下是靜止狀態

所以之前想實現 favicon 文本滾動的效果可以就此打住了😞

比較類似的還有媒體查詢,之前在網上看到有這樣的實現,直接在 SVG 中實現黑暗模式

比較類似的還有媒體查詢,之前在網上看到有這樣的實現,直接在 SVG 中實現黑暗模式

<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
    <style>
        path {
            fill: #fff;
        }
        rect {
            fill: #B09AFE;
        }
        @media (prefers-color-scheme: dark) {
            path {
                fill: #B09AFE;
            }
            rect {
                fill: #fff;
            }
        }
    </style>
    <rect width="128" height="128" rx="64" fill="#B09AFE"/>
    <path d="M32.375 37.5714H22C22 58.004 38.2596 74.5714 58.3125 74.5714V98.3571C58.3125 99.8107 59.4797 101 60.9062 101H66.0937C67.5203 101 68.6875 99.8107 68.6875 98.3571V74.5714C68.6875 54.1388 52.4279 37.5714 32.375 37.5714ZM94.625 27C80.9754 27 69.109 34.6808 62.9002 46.0286C67.3906 51.017 70.7139 57.079 72.4646 63.8018C90.7344 61.8692 105 46.1442 105 27H94.625Z" fill="white"/>
</svg>

但是也是同樣的問題,只有 Firefox 下可行,Chrome 是靜止不動的

Firefox 下是支持媒體查詢的

什麼時候 Chrome 可以解除這種限制呢?

總的來說,SVG 在繪製方面提供了無限可能,不僅僅是本文中的案例,覺得 canvas 過於複雜的都可以考慮這一方案

前端偵探 致力於有趣的前端探索~

參考資料

[1] foreignObject: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject

[2] SVG 簡介與截圖等應用: https://www.zhangxinxu.com/wordpress/2017/08/svg-foreignobject/

[3] https://github.com/XboxYan/auto-dev-favicon-chrome-plugin: https://github.com/XboxYan/auto-dev-favicon-chrome-plugin

[4] secure-static-mode: https://svgwg.org/svg2-draft/conform.html#secure-static-mode

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/g_IpbiyJxB3S8-bvBtrxXg