天天看點

社群精選 |使用 SVG 生成帶辨別的 favicon

作者:SegmentFault思否

今天小編為大家帶來的是社群作者XboxYan的文章,讓我們一起來學習使用 SVG 生成帶辨別的 favicon。

之前做了一個 Chrome 插件,可以根據位址的不同生成不同的圖示,這樣可以很友善的區分不同的開發環境,效果如下:

社群精選 |使用 SVG 生成帶辨別的 favicon

主要實作過程其實不複雜,首先擷取網站 favicon,然後給 favicon 添加辨別,重新繪制生成就行了。

社群精選 |使用 SVG 生成帶辨別的 favicon

其中,這裡的圖示就是通過 SVG 生成的,下面看看具體實作吧。

一、favicon 的擷取方式

想知道擷取方式,可以先了解設定方式。

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

第一種,通過 link 标簽設定(需要rel="icon"屬性)

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

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

- 網站目錄
    index.html
    favicon.ico
           

如果以上都沒有設定,那麼大機率會看到以下錯誤:

社群精選 |使用 SVG 生成帶辨別的 favicon

了解這些,擷取就簡單了,先通過link擷取,隻要rel包含icon就行了

const link = document.querySelector('link[rel~="icon"]');           
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 中自由的、像正常網頁開發一樣,繪制這樣一個布局,相信沒有什麼難度

社群精選 |使用 SVG 生成帶辨別的 favicon
<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 的作用,有興趣的可以參考張鑫旭老師的這篇文章 SVG 簡介與截圖等應用,在這裡,你可以簡單了解為是可以包含 HTML 的标簽,而 SVG 本身也是一種圖檔,這樣就達到了合成圖像的目的。

社群精選 |使用 SVG 生成帶辨別的 favicon

具體實作如下:

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: con}

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>

這裡需要注意幾點:

  1. img 标簽在 svg 中需要寫成<img></img>閉合形态,不然會被認為結構錯誤
  2. img 隻能使用内聯圖檔,比如 base64,這也是為何繪制原始 favicon 的原因
  3. 如果使用内聯 svg,需要對 svg 進行轉義
  4. 字元串中的單雙引号問題也需要注意一下

然後,将生成的 SVG 直接設定為 favicon

link.href= favicon;           
社群精選 |使用 SVG 生成帶辨別的 favicon

這樣就能正常的渲染了~

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

四、一些局限性

首先是相容性。

目前隻有 Chrome 和 Firefox 是支援的,為了相容其他浏覽器,可以用一個 .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,在這種模式下,所有動畫都不會執行,會處于第一幀,比如下面這個例子:

<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 是有動畫的

社群精選 |使用 SVG 生成帶辨別的 favicon

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

社群精選 |使用 SVG 生成帶辨別的 favicon

是以之前想實作辨別文本滾動的效果可以就此打住了

比較類似的還有媒體查詢,之前在網上看到有這樣的實作,直接在 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是靜止不動的

社群精選 |使用 SVG 生成帶辨別的 favicon

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

最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤

繼續閱讀