1 問題
項目中需要動態(tài)改變頁面的 favicon廊移,icon 文件存儲在阿里云(OSS)上材蛛。改變 favicon 的方式是通過獲取 link 元素太伊,把 icon 的 url 賦值給其 href妻怎。在 chrome 上測試可以正確顯示刁赖,但是在 firefox 上卻沒有顯示圖標(biāo)搁痛。
2 分析
動態(tài)改變 favicon 以前我也沒有做過,秉承不放過一個可能的 debug 策略宇弛,首先懷疑 firefox 不支持動態(tài)修改 link[rel=icon]鸡典。于是打開 fiddler 查看在動態(tài)賦值圖標(biāo) url 后有沒有圖片請求發(fā)出(firefox自帶的開發(fā)者工具中沒有顯示 link[rel=icon] 的請求)。在 fiddler 中發(fā)現(xiàn) firefox 是發(fā)送了請求的枪芒,那么接下來就看下返回了彻况。此時發(fā)現(xiàn)阿里云返回的是403,估計是請求頭缺少了什么東西導(dǎo)致請求被阿里云屏蔽了舅踪。查看圖片的請求頭發(fā)現(xiàn)缺少 Referer纽甘,估計就是這個影響了。我重新打開 chrome 看了下圖片的請求是有 Referer 的抽碌,那么基本上可以確定這是 firefox 的一個 bug悍赢。最終我去到阿里云的管理界面看了下防盜鏈的設(shè)置界面,里面選擇的是 Referer 不能為空货徙,這下證實了我的猜測:由于 firefox 的加載 favicon 的時候左权,請求頭缺少了 Referer,被阿里云防盜鏈了痴颊。
3 解決方案
解決方案其實是在 google 的時候發(fā)現(xiàn)的赏迟,link[rel=icon] 的 href 不但可以寫 url,還可以寫圖片的 base64 編碼蠢棱,和 img 標(biāo)簽一樣锌杀。那么我是否可以用 img 標(biāo)簽加載這個圖標(biāo)然后轉(zhuǎn)成 base64 編碼再賦值給 link[rel=icon] 呢?說干就干裳扯,依稀記得轉(zhuǎn) base64 編碼可以使用 canvas.toDataURL 這個 API 來轉(zhuǎn)抛丽,查了下瀏覽器支持情況,幸好項目要求支持的瀏覽器都支持饰豺。代碼如下:
let ImageContentTypeMap:any = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
ico: 'image/x-icon',
gif: 'image/gif'
}
function parseSuffix( url:string ):string {
if ( url ) {
let lastDotIndex = url.lastIndexOf( '.' );
if ( lastDotIndex >= 0 ) {
return url.substr( lastDotIndex + 1 ).toLowerCase();
} else {
return '';
}
} else {
return '';
}
}
/*
請忽略為啥要用 promise亿鲜,只是 copy 出來懶得改了
*/
function imageToBase64( url, width, height ) {
return new Promise( function( resolve, reject ) {
let img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = function() {
var canvas = document.createElement( 'canvas' );
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext( '2d' );
ctx.drawImage( img, 0, 0 );
let imgSuffix = parseSuffix( url );
if ( imgSuffix ) {
let contentType = ImageContentTypeMap[imgSuffix];
if ( contentType ) {
resolve( canvas.toDataURL( contentType ) );
} else {
reject( new Error('Can not parse contentType of favicon') )
}
} else {
reject( new Error('Can not parse suffix of favicon file') )
}
}
// TODO: 當(dāng)圖片加載失敗的情況
img.src = url;
} )
}
let iconLink = document.getElementById('iconLink');
imageToBase64( '....../xxx.ico', 16, 16 ).then( imgStr=>{
iconLink.href = imgStr;
} ).catch( e=>{
console.error( 'Parse favicon: ', e.stack );
} )
測試結(jié)果是 icon 圖片是顯示出來了,但是只顯示了部分圖片(囧)冤吨。那...應(yīng)該可能是圖標(biāo)有問題蒿柳?遂找了另外一個 png 的圖標(biāo)試試了,測試通過漩蟆。難道是 firefox 中使用 img 加載 ico 文件有問題垒探?只能上 google 大法了,經(jīng)過了一番搜索和測試基本能確定下來怠李,firefox 顯示 ico 文件是沒有問題的圾叼,但是 canvas.toDataURL 這個 API 在不同的瀏覽器中支持的圖片格式有偏差蛤克,而且格式類型支持的都有限。所以我們最初用的 ico 文件通過 canvas.toDataURL encode 之后的編碼不是完全正確的夷蚊。此時我想到兩個解決方案:
- favicon 改換成 png 圖片构挤。
- 后臺直接給出的不是 icon 的 url,而是 base64 的編碼惕鼓。(用 ico 文件正確的 base64 編碼做過測試筋现,firefox 能夠正確顯示圖標(biāo))
至此 firefox 不支持動態(tài)修改 favicon 的問題算解決了。
延伸資料
Favicon 歷史 - https://en.wikipedia.org/wiki/Favicon
各瀏覽器支持顯示的圖片格式 - https://en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support