个使用 React 和 Cloudflare 图像的自动响应图像
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--odc3TJDq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1。 medium.com/max/1024/1%2ABaaub6dK7QBFlJSIMzQVjw.jpeg)
在当今以移动设备为主导的世界中,根据客户端设备的屏幕尺寸提供尺寸合适的图像是一种谨慎的做法。在过去,这需要大量的 Javascript 代码。
值得庆幸的是,大多数现代浏览器都接受标签中的 srcset 属性。此属性允许您根据客户端的屏幕尺寸指定多个图像源。
这是一个简单的例子:
<img srcset="/images/image-768 768w,
/images/image-1024.jpg 1024w,
/images/image-1600.jpg 1600w,
/images/image-1920.jpg 1920w"
src="/images/image.jpg"
/>
进入全屏模式 退出全屏模式
这样做的问题是,您必须为您打算提供的每个屏幕尺寸预渲染这些图像。这不仅乏味,而且还需要您的 Web 服务器上的大量存储空间。
有几种图像大小调整 CDN,例如 imagekit.io 和 kraken.com,但它们的成本很高。 Cloudflare 提供即用即付的图像大小调整服务,允许您调整来自任何来源的图像大小。 Cloudflare 还缓存调整大小的图像,以便快速交付。
在本教程中,我将使用 AWS Cloudfront 分发作为我们 Cloudflare 图像 CDN 的原始源,但您可以使用任何可公开访问的源来实现此目的。有关如何设置的更多信息,请参阅我的文章如何使用 AWS CloudFront 创建您自己的免费 CDN和如何为您的免费 CloudFront CDN获取自定义域。
第 1 步:设置 Cloudflare 映像
假设您已经有一个注册的 Cloudflare 帐户并且您的 DNS 托管在那里。创建一个像 cdn.example.com 这样的子域并打开代理。
接下来,在侧边栏中点击 Speed > Optimization 并打开 Image Resizing,并确保 Resize images from any origin 是 unchecked。如果您选中它,任何人都可以使用您的 CDN url 来调整来自任何来源的图像大小!
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--oe8r8bVs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1。 medium.com/max/1024/1%2AfIGdA03a-AlUPFpTOlcP6A.jpeg)
您需要使用 CloudFlare 设置计费帐户才能使用图像调整器 API。可计费的使用非常便宜,除非您的网站流量非常大,否则您可能会被收取很少的费用。
第 2 步:创建 Service Worker API 端点
您可以使用 cdn_cgi url 方法来调整图像大小,但这会暴露您的源源,您必须启用 Resize images from any origin 才能工作,所以我们不打算这样做那。相反,我们将创建一个 CloudFlare 工作者来处理我们的图像调整请求。
在 CloudFlare 仪表板的侧边栏中,单击 Workers,然后单击 Create Service。
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--1mzgE7mT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1。 medium.com/max/1024/1%2ACP5aDTOWW5frreK-Gcfb_A.jpeg)
输入一个服务名称。我选择了“resizer”,但你可以随意命名,然后点击Create Service。
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--Ad6DnEb5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1. medium.com/max/1024/1%2AoiRHlyygZiw3JUugmyp4-w.jpeg)
在工作人员设置屏幕上,单击 Quick Edit 按钮并删除左侧窗格中的默认代码。输入以下代码并根据您的环境对其进行自定义:
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
/** GLOBALS */
// URL prefix for origin requests
const cdnUrl = "[https://cdn.example.com](https://cdn.example.com)";
const defaultImage = cdnUrl + "/images/image-placeholder.webp";
/**
* Fetch and log a request
* [@param](http://twitter.com/param) {Request} request
*/
async function handleRequest(request) {
// Parse request URL to get access to query string
let url = new URL(request.url)
// validate URL pathname
if (!url.pathname || !url.pathname.length) {
return new ErrorDefault("Missing image path!");
}
imageURL = cdnUrl + url.pathname.replace("/resize", ""); // prefix image path with CDN url
// Validation options
const whRange = [50, 3000]; // width and height range limit
const formats = ['webp', 'avif', 'json'];
const fitVals = ['scale-down', 'contain', 'cover', 'crop', 'pad'];
const gravityVals = ['left', 'right', 'top', 'bottom', 'center', 'auto']; // also can be object int {x, y}
const qualityRange = [60, 90]; // actually 0-100 but 60-90 is usable range
const rotateVals = [90, 180, 270]; // Only multiples of 90 allowed
const sharpenRange = [0, 10]; // float: 1.0 recommended for down-scaled images
const blurRange = [0, 250]
// Cloudflare-specific options are in the cf object.
let options = { cf: { image: {} } }
// Copy parameters from query string to request options.
if (url.searchParams.has("fit")) options.cf.image.fit = url.searchParams.get("fit")
// fit accepted value: object: {top, right, bottom, left}
if (url.searchParams.has("w")) options.cf.image.width = url.searchParams.get("w")
// width accepted value 50-3000
if (url.searchParams.has("h")) options.cf.image.height = url.searchParams.get("h")
// height accepted value: 50-3000
if (url.searchParams.has("q")) options.cf.image.quality = url.searchParams.get("q")
if (url.searchParams.has("r")) options.cf.image.rotate = url.searchParams.get("r")
if (url.searchParams.has("sharpen")) options.cf.image.sharpen = url.searchParams.get("sharpen")
if (url.searchParams.has("blur")) options.cf.image.blur = url.searchParams.get("blur")
if (url.searchParams.has("t")) options.cf.image.trim = url.searchParams.get("t")
if (url.searchParams.has("g")) options.cf.image.gravity = url.searchParams.get("g")
// Validate parameters
if (options.cf.image.fit && !fitVals.includes(options.cf.image.fit)) {
return new ErrorDefault("Invalid value for fit!");
}
if (options.cf.image.width && !inRange(options.cf.image.width, whRange)) {
return new ErrorDefault(`Invalid width range [${whRange.join("-")}]`);
}
if (options.cf.image.height && !inRange(options.cf.image.height, whRange)) {
return new ErrorDefault(`Invalid height range [${whRange.join("-")}]`);
}
if (options.cf.image.quality && !inRange(options.cf.image.quality, qualityRange)) {
return new ErrorDefault(`Invalid quality range [${qualityRange.join("-")}]`);
}
if (options.cf.image.rotate && !rotateVals.includes(options.cf.image.rotate)) {
return new ErrorDefault(`Invalid rotate value [${rotateVals.join("|")}]`);
}
if (options.cf.image.sharpen && !inRange(options.cf.image.sharpen, sharpenRange)) {
return new ErrorDefault(`Invalid sharpen range [${sharpenRange.join("-")}]`);
}
if (options.cf.image.blur && !inRange(options.cf.image.blur, blurRange)) {
return new ErrorDefault(`Invalid blur range [${blurRange.join("-")}]`);
}
// Your Worker is responsible for automatic format negotiation. Check the Accept header.
// Try webp first, then avif
const accept = request.headers.get("Accept");
if (/image\/webp/.test(accept)) {
options.cf.image.format = 'webp';
} else if (/image\/avif/.test(accept)) {
options.cf.image.format = 'avif';
}
try {
const { hostname, pathname } = new URL(imageURL)
// only allow URLs with JPEG, PNG, GIF, or WebP file extensions
// [@see](http://twitter.com/see) [https://developers.cloudflare.com/images/url-format#supported-formats-and-limitations](https://developers.cloudflare.com/images/url-format#supported-formats-and-limitations)
if (!/\.(jpe?g|png|gif|webp)$/i.test(pathname)) {
return new Response('Disallowed file extension', { status: 400 })
}
} catch (err) {
return new Response('Invalid "image" value', { status: 400 })
}
// Build a request that passes through request headers
const imageRequest = new Request(imageURL, {
headers: request.headers
})
console.log(options);
// Returning fetch() with resizing options will pass through response with the resized image.
return fetch(imageRequest, options)
}
class ErrorDefault extends Response {
constructor(message, props) {
super(props);
console.log("Image resize error: " + message);
return fetch(defaultImage, { status: 200 });
}
}
// Helper functions
const inRange = (v, [x, y]) => {
return (v >= x && v <= y);
}
进入全屏模式 退出全屏模式
将 cdnUrl 常量更改为您的 CDN 基本 URL。进行您想要的任何其他修改,然后单击 Save and Deploy。
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--c3m-Krx_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images- 1.medium.com/max/1024/1%2AYrcyugqswGGJ6LBZCoNdIA.jpeg)
返回您的服务屏幕并单击 Triggers 并单击 Add Route。
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--wSAczPhL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1。 medium.com/max/1024/1%2AEx2a-dLKonQaBbLQwJpG6Q.jpeg)
在 Route 下,您将输入类似https://cdn.example.com/resizer\*的内容。它的作用是拦截任何带有此前缀的 URL 并通过 service worker 运行它。提取此前缀之后的任何路径名,并将您的 cdnUrl 常量值添加到它前面。任何调整大小的选项都是通过查询参数配置的。
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--Fw9zUUVV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1。 medium.com/max/1024/1%2A23SxKWPC0f2ryRTUKvI05A.jpeg)
好的!让我们试试看。尝试向您的新服务人员请求图像,如下所示:https://cdn.example.com/resizer/images/some-image.jpg?wu003d760,其中 /images/some-image.jpg 是CDN url 的相对路径。
我已经在这个脚本中编写了几个可用的调整大小选项,但还有更多可供选择。有关使用 CloudFlare 的图像大小调整 API 的更多信息,请点击此处。
现在,你可以在这里停下来,随心所欲地使用这个服务,但如果你是 React/NextJS 开发人员,请继续阅读!
第 3 步:创建 React 自定义组件
本文的标题表明我们将自动化此过程,而这正是我们现在要做的。
考虑以下代码:
const resizeUrl = "https://cdn.example.com/resizer%SRC%?w=%WIDTH%&q=%QUALITY%";
const imageResizerConfig = {
resizeUrl: resizeUrl,
cfSrcSetUrl: resizeUrl + " %WIDTH%w",
srcSetWidths: [640, 768, 1024, 1366, 1600, 1920],
srcSetDefaultWidth: 1024,
placeholderImage: "/images/pixel-black.png",
defaultQuality: 90,
blogThumbQuality: 60
};
export default function CfImage({
defaultWidth = imageResizerConfig.srcSetDefaultWidth,
maxWidth = null,
src,
className,
alt,
onClick,
quality = imageResizerConfig.defaultQuality
}) {
const srcSet = [];
imageResizerConfig.srcSetWidths.forEach((w) => {
if (!maxWidth || w <= maxWidth) {
srcSet.push(imageResizerConfig.cfSrcSetUrl.replace(/%WIDTH%/g, w).replace("%SRC%", src).replace("%QUALITY%", quality));
}
});
if (defaultWidth > maxWidth) {
defaultWidth = maxImageWidth(maxWidth);
}
const srcMain = imageResizerConfig.resizeUrl.replace(/%WIDTH%/g, defaultWidth).replace("%SRC%", src).replace("%QUALITY%", quality);
return (
<img className={className} src={srcMain} srcset={srcSet.join(',')} alt={alt} onClick={onClick} />
);
}
function maxImageWidth(maxWidth) {
const useWidths = [];
imageResizerConfig.srcSetWidths.forEach((w) => {
if (!maxWidth || w <= maxWidth) {
useWidths.push(w);
}
});
useWidths.sort(function (a, b) {
return a - b;
});
return useWidths.pop();
}
进入全屏模式 退出全屏模式
这个组件的作用是渲染一个带有预设 srcset URL 的标签,大小为 [640, 768, 1024, 1366, 1600, 1920]。这些是我根据常见的移动和桌面屏幕尺寸选择的任意尺寸。对于高清桌面屏幕,您可能需要的最大尺寸是 1920,因此您的原始源图像不需要比这更高的分辨率。
然后,您可以将此组件导入您的 React 页面并像这样调用它:
此代码应呈现如下内容:
<CfImage
{...this.props}
className="pop-image"
src="/images/path/to/image.jpg"
onClick={handleClick}
alt="My image description"
maxWidth={this.state.winWidth}
quality=60
/>
进入全屏模式 退出全屏模式
而已。现在你有了一个全自动的图像缩放器。您所需要做的就是以 JPG 或 PNG 格式以 1920w 发布图像,CloudFlare 将根据 HTTP Accept 标头优化图像,无论是 webp 还是 avif。
随意使用此代码并对其进行自定义以满足您的需求。
谢谢阅读!如需更多重要信息,请访问我们的博客。
最初于 2022 年 5 月 5 日发表于__https://design.biz。_
更多推荐

所有评论(0)