Use SVG to generate favicon with logo

I made a Chrome plug-in before , which can generate different icons according to different addresses. This can easily distinguish different development environments. The effect is as follows

The main implementation process is actually not complicated. First, obtain the website favicon, then add a logo to the favicon, and redraw it to generate it.

Among them, the icons here are generated through SVG. Let’s take a look at the specific implementation.

1. How to obtain favicon

If you want to know how to get it, you can first understand how to set it up.

There are generally two ways to set up a website favicon.

The first one linkis set through tags (requires rel="icon"attributes)

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

The second one is to put one directly in the root directory of the websitefavicon.ico (it must be this name, the default of the browser), and there is no need to set anything in the html.

- Website directory
    index. html 
    favicon. ico

If none of the above are set, you will most likely see the following error

After understanding these, obtaining is simple. First linkobtain it, as long as it is relincluded .icon

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

If you can’t find it, you can request it /favicon.icoand add one directly here.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
    }
}

What is obtained in this way linkis guaranteed to exist, and then it is drawn

2. Draw using canvas

Since the image needs to be synthesized, the original image needs to be drawn first. When it comes to image drawing, you can think of canvas drawing, and only a little basic knowledge of canvas is enough. The specific implementation is as follows

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;

Since there is /favicon.icono setting, a default image needs to be given when the img fails to load.

img. onerror = () => {
     resolve ( "Default image base64" );
}

In this way, the image information of the favicon can be obtained.

3. Use SVG for image synthesis

On the basis of the above, you can actually continue to draw through canvas, and it is not difficult to draw another label. However, SVG can be used to draw here, which has the following advantages:

  1. Lower cost and easier to understand than canvas
  2. High flexibility, some style control can be done through CSS

First of all, we can freely draw such a layout in HTML like normal web development. I believe it is not difficult.

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

Since the width is limited, it is necessary to force the text to wrap and hide beyond it. The key styles are as follows

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 ;
}

Then, put this piece of html into foreignObjecta tag. Regarding the role of foreignObject , those who are interested can refer to this article by Zhang Xinxu. SVG introduction and screenshots and other applications . Here, you can simply understand that it is a tag that can contain HTML, and SVG itself is also a kind of picture, thus achieving the purpose of synthesizing images.

The specific implementation is as follows

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' )

Several points need to be noted here

  1. The img tag needs to be written in a closed form in svg <img></img>, otherwise it will be considered a structural error.
  2. img can only use inline images, such as base64, which is why the original favicon is drawn
  3. If you use inline svg, you need to escape the svg
  4. You also need to pay attention to the issue of single and double quotation marks in strings.

Then, set the generated SVG directly to the favicon

link.href = favicon;

This way you can render normally~

For complete implementation, please refer to the project: https://github.com/XboxYan/auto-dev-favicon-chrome-plugin

4. Some limitations

The first is compatibility.

Currently only Chrome and Firefox are supported. In order to be compatible with other browsers, you can use one .icoto check the details.

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

In addition, there is a limitation on Chrome (I don’t know if it is a limitation after Chrome is updated). When the favicon uses svg format images, the svg at this time will be in a secure-static-mode . In this mode, all The animation will not be executed and will be in the first frame, such as the following example

< 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 >

A very simple background color animation. On Firefox, it is used as a favicon and has animations.

However, it doesn’t work on Chrome, only the first frame is disabled

image-20220515191044404

So if you want to achieve the scrolling effect of logo text before, you can stop here😭

Similar to media query, I have seen such an implementation on the Internet before, which implements dark mode directly in 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 9 9.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 >

But it’s the same problem. It only works under Firefox, but Chrome is still.

In general, SVG provides endless possibilities in drawing, not just the cases in this article. Anyone who thinks canvas is too complicated can consider this solution.