✏️最新內容請以github上的為準❗️
其他文章
- JavaScript記憶體那點事
- 常見的JavaScript記憶體洩露
- IE<8迴圈引用導致的記憶體洩露
- 記憶體洩露之jQuery.cache
- 記憶體洩露之Listeners
- requestAnimationFrame
- …………
在搭建 blog 過程中,使用 lighthouse 審查站點。在效能項上提示Serve images in next-gen formats
優化建議。
Image formats like JPEG 2000, JPEG XR, and webp often provide better compression than PNG or JPEG, which means faster downloads and less data consumption.Learn more
JPEG 2000, JPEG XR, 和 WebP 與傳統的 JPEG、PNG 相比具有高壓縮比、高質量的特點。這讓圖片載入更快,頻寬消耗更少。當前瀏覽器對 JPEG 2000, JPEG XR, 和 WebP 的支援情況:
結合瀏覽器的支援情況,最終選擇支援 WebP 來優化:
- 支援有損和無失真壓縮
- 支援動畫
- 開源
- 技術支援團隊是 Google
- 更多關於 WebP
如何支援 WebP
支援 WebP 有兩種方式:
-
客戶端處理,這種處理方式需要提前準備好 WebP 圖片。如何將圖片轉換為 WebP 格式
- 使用 js 檢測是否支援 WebP。
// check_webp_feature: // 'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'. // 'callback(feature, result)' will be passed back the detection result (in an asynchronous way!) function check_webp_feature(feature, callback) { var kTestImages = { lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA", lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==", alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==", animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA" }; var img = new Image(); img.onload = function() { var result = img.width > 0 && img.height > 0; callback(feature, result); }; img.onerror = function() { callback(feature, false); }; img.src = "data:image/webp;base64," + kTestImages[feature]; } 複製程式碼
- 使用 HTML5 和 CSS3 特性支援檢測庫: Modernizr 。Modernizr.webp,Modernizr.webp.lossless,Modernizr.webp.alpha 和 Modernizr.webp.animation。
- 使用
<picture>
元素
<picture> <source type="image/webp" srcset="demo.webp"> <source type="image/png" media="demo.png"> <img src="demo.png" alt="demo"> </picture> 複製程式碼
-
服務端處理。相比客戶端處理,在服務端處理更加靈活。因為它可以通過內容型別協商,能提前知道客戶端是否支援 WebP(請求頭中
Accept
欄位)。如果支援就優先響應 Web 格式圖片,否則就響應請求圖片。
對比兩種處理方式,通過服務端來支援 WebP 具有如下優勢:
- 提前知道客戶端是否支援 WebP。處理更靈活,更可靠。而客戶端還需要根據是否支援 WebP,對連結做額外的替換處理。
- 動態支援 WebP。如果支援 WebP,檢視本地是否有對應 WebP 圖片,如果沒有動態生成響應。
服務端動態支援 WebP
服務端要動態支援 WebP,可以由代理伺服器 Nginx,或 Backend 來完成。
singsong:圖片處理邏輯最好交給下游 Backend 來完成,NGINX 就負責轉發即可。當然也有自動處理圖片 nginx :ngx_pagespeed
Nginx 處理
- 確保
mime.types
中有 WebP。因為如果沒有 WebP 型別,WebP 圖片會作為application/octet-stream
輸出。
image/webp webp;
複製程式碼
- 獲取請求頭
Accept
欄位中的webp
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
複製程式碼
這裡使用 map(更多參考ngx_http_map_module)定義了一個$webp_suffix
變數,如果 WebP 存在,$webp_suffix
值為".webp"
,否則為空字串。
-
輸出圖片
- 檢視是否存在
.webp
的檔案,如果存在就直接輸出。 - 檢視是否存在請求檔案,如果存在就直接輸出。
- 如果上述檔案都不存在,就響應
404
。
- 檢視是否存在
try_files $uri$webp_suffix $uri =404;
複製程式碼
這裡還可以將響應操作反代理給 Backend:
if ($http_accept ~* "webp") { set $webp_accept "true"; }
location ~ ^/imgs.*\.(png|jpe?g)$ {
# Pass WebP support header to backend
proxy_set_header WebP $webp_accept;
proxy_pass http://127.0.0.1:8080;
}
複製程式碼
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#
# < regular Nginx configuration here >
#
# For a hands-on explanation of using Accept negotiation, see:
# http://www.igvita.com/2013/05/01/deploying-webp-via-accept-content-negotiation/
# For an explanation of how to use maps for that, see:
# http://www.lazutkin.com/blog/2014/02/23/serve-files-with-nginx-conditionally/
map $http_accept $webp_suffix {
"~*webp" ".webp";
}
map $msie $cache_control {
"1" "private";
}
map $msie $vary_header {
default "Accept";
"1" "";
}
# if proxying to another backend and using nginx as cache
proxy_cache_path /tmp/cache levels=1:2 keys_zone=my-cache:8m max_size=1000m inactive=600m;
proxy_temp_path /tmp/cache/tmp;
server {
listen 8081;
server_name localhost;
location ~ \.(png|jpe?g)$ {
# set response headers specially treating MSIE
add_header Vary $vary_header;
add_header Cache-Control $cache_control;
# now serve our images
try_files $uri$webp_suffix $uri =404;
}
# if proxying to another backend and using nginx as cache
if ($http_accept ~* "webp") { set $webp_accept "true"; }
proxy_cache_key $scheme$proxy_host$request_uri$webp_local$webp_accept;
location ~ ^/proxy.*\.(png|jpe?g)$ {
# Pass WebP support header to backend
proxy_set_header WebP $webp_accept;
proxy_pass http://127.0.0.1:8080;
proxy_cache my-cache;
}
}
}
複製程式碼
想了解更多可以參考如下文章:
- Ilya Grigorik: Deploying New Image Formats on the Web - webp
- Eugene Lazutkin: Serve files with nginx conditionally - webp
- Webp Detect - WebP with Accept negotiation
- my post on conditionally serving webp and jxr with nginx
Backend 處理
Backend 是基於 KOA 框架搭建的,要整合動態支援 WebP,需要完成如下兩個任務:
- 獲取請求頭中的
Accept
欄位,判斷是否支援 WebP。這一步也可由 Nginx 來做。
// 獲取請求頭:ctx.header.accept, ctx.headers.accept、ctx.req.headers.accept、ctx.request.headers.accept、ctx.request.header.accept
const isWebp = /webp/i.test(ctx.header.accept);
// 注意: 雖然 KOA 提供`ctx.accept('webp')`方法來判斷accept type。但是該方法對webp判斷存在bug,它會將`*/*`作為支援來處理。
複製程式碼
sharp 相比於 jimp、gm 綜合效能更好,對 WebP 支援更友好。因此這裡使用 sharp 來實現圖片格式轉換、縮放、水印等功能。npm 對比資料:gm vs jimp vs sharp 。
關鍵程式碼
const fs = require("fs-extra");
const path = require("path");
const send = require("koa-send");
const sharp = require("sharp");
const glob = require("glob");
const TextToSvg = require("text-to-svg");
// 配置sharp
sharp.concurrency(1);
sharp.cache(50);
module.exports = async ctx => {
// getSvgByText
const getSvgByText = (text, fontSize, color) => {
const textToSVG = TextToSvg.loadSync();
const svg = textToSVG.getSVG(text, {
fontSize,
anchor: "top",
attributes: {
fill: color
}
});
return Buffer.from(svg);
};
const originals = glob.sync(
path.join(__dirname, "public", "originals", "*.+(png|jpeg|svg|jpg)")
);
const nameMapOriginal = {};
originals.forEach(original => {
const metas = path.parse(original);
nameMapOriginal[metas.name] = original;
});
// getOriginals
const getOriginalsByName = name => nameMapOriginal[name];
const imgProcessor = async (
inputPath,
outputPath,
{ overlay, width, blur }
) => {
const image = sharp(inputPath);
const metadata = await image.clone().metadata(); // 獲取原圖片的後設資料
const rawWidth = width || metadata.width;
if (
overlay !== "off" &&
metadata.width > 200 &&
metadata.height > 100 &&
rawWidth > 200
) {
const tempFontSize = (rawWidth * 0.03) | 0; // eslint-disable-line
const fontSize = tempFontSize < 12 ? 12 : tempFontSize;
overlay = getSvgByText(
"zhansingsong.com",
fontSize,
"rgba(255, 255, 255, 0.3)"
); // eslint-disable-line
await image
.clone()
.overlayWith(overlay, { gravity: sharp.gravity.southeast })
.resize({ width: parseInt(width, 10) })
.toFile(outputPath)
.catch(err => ctx.app.emit("error", err));
} else if (!blur) {
await image
.clone()
.resize({ width: parseInt(width, 10) })
.toFile(outputPath)
.catch(err => ctx.app.emit("error", err));
} else {
await image
.clone()
.resize({ width: parseInt(width, 10) })
.blur(1.3)
.toFile(outputPath)
.catch(err => ctx.app.emit("error", err));
}
};
const { join, parse } = path;
const { existsSync, ensureDirSync } = fs;
// 編碼中文亂碼
const url = decodeURIComponent(ctx.path);
const metas = parse(url);
const isWebp = /webp/i.test(ctx.header.accept); // 判斷是否支援webp
const isThumbnail = /^\/public\/thumbnails\//.test(url);
const fileDir = isThumbnail
? join.apply(path, [
__dirname,
"public",
"thumbnails",
`${ctx.query.width || 20}`
])
: join.apply(path, [
__dirname,
"public",
"imgs",
...Object.values(ctx.query)
]);
const filePath = join(
fileDir,
`${metas.name}${isWebp ? ".webp" : metas.ext}`
);
const options = isThumbnail
? {
width: ctx.query.width || 20,
overlay: ctx.query.overlay || "off",
blur: true
}
: ctx.query;
ensureDirSync(fileDir);
if (!existsSync(filePath)) {
await imgProcessor(getOriginalsByName(metas.name), filePath, options); // eslint-disable-line
}
await send(ctx, filePath, { root: "/" });
};
複製程式碼
實現效果
通過 sharp 為 Backend 實現了一些簡單圖片處理介面:圖片壓縮、水印、格式轉換。這也為後面縮圖的使用提供了支援。處理效果如下圖所示:
從上圖可知:- Safari 和 Chrome 瀏覽器分別請求同一圖片,響應結果各不相同。瀏覽器支援 WebP 時,會直接響應 WebP 圖片。否則就響應請求圖片。
- 相同質量的圖片,WebP 格式大小約為 png 格式大小的 0.43。
總結
本文是自己在使用 WebP 的一些心得總結。主要對 WebP 的使用做個簡單介紹。至於為什麼要用 WebP,本文也做了相關介紹。但這並不代表 WebP 沒有缺點。如在編解碼效率上就存在不足。不過隨著硬體裝置的提升,這也在可接受範圍內。隨著移動網際網路的快速發展,PWA(Progressive Web App)必成為 Web App 的主流。而 WebP 是 PWA 一個組成部分,瞭解並支援 WebP 已成大趨勢。目前很多主流的站點已全站或部分支援 WebP。