在搭建 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。