[](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.iokraken.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 originunchecked。如果您选中它,任何人都可以使用您的 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。_


Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐