在搭建 blog 過程當中,使用 lighthouse 審查站點。在性能項上提示Serve images in next-gen formats
優化建議。html
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 morenode
JPEG 2000, JPEG XR, 和 WebP 與傳統的 JPEG、PNG 相比具備高壓縮比、高質量的特色。這讓圖片加載更快,帶寬消耗更少。當前瀏覽器對 JPEG 2000, JPEG XR, 和 WebP 的支持狀況:nginx
結合瀏覽器的支持狀況,最終選擇支持 WebP 來優化:git
支持 WebP 有兩種方式:github
客戶端處理,這種處理方式須要提早準備好 WebP 圖片。如何將圖片轉換爲 WebP 格式web
// 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];
}
複製代碼
<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 格式圖片,不然就響應請求圖片。apache
對比兩種處理方式,經過服務端來支持 WebP 具備以下優點:npm
服務端要動態支持 WebP,能夠由代理服務器 Nginx,或 Backend 來完成。瀏覽器
singsong:圖片處理邏輯最好交給下游 Backend 來完成,NGINX 就負責轉發便可。固然也有自動處理圖片 nginx :ngx_pagespeedbash
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;
}
}
}
複製代碼
想了解更多能夠參考以下文章:
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 實現了一些簡單圖片處理接口:圖片壓縮、水印、格式轉換。這也爲後面縮略圖的使用提供了支持。處理效果以下圖所示:
從上圖可知:本文是本身在使用 WebP 的一些心得總結。主要對 WebP 的使用作個簡單介紹。至於爲何要用 WebP,本文也作了相關介紹。但這並不表明 WebP 沒有缺點。如在編解碼效率上就存在不足。不過隨着硬件設備的提高,這也在可接受範圍內。隨着移動互聯網的快速發展,PWA(Progressive Web App)必成爲 Web App 的主流。而 WebP 是 PWA 一個組成部分,瞭解並支持 WebP 已成大趨勢。目前不少主流的站點已全站或部分支持 WebP。