React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React处理图片样式

React处理复杂图片样式的方法详解

作者:慕仲卿

这篇文章主要为大家详细介绍了React处理复杂图片样式的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

正如文章题目所示,本文的目的是为了记录工作中遇到的,在页面中处理复杂图片样式的解决方案。使用的技术栈有:

之所以称之为“兜底”方案,是因为下文中提到的方法能够以像素级别操作图片。而,如果只是这样,也没有什么值得记录的。但是,本文为了防止这种像素级别的操作对页面渲染性能造成大的冲击,结合 web worker 异步处理,解决了这个问题。

从本文的思路出发,至少可以收获两点技术:

下面让我们开始吧~

本文分为三个小节,第一小节简单的介绍上面提到的技术栈;第二小节在非工程化 Demo 中演示这个解决方案的流程;第三小节将此方法分装成一个 React 组件,以便后面复用和维护。

1. 技术栈介绍

这些知识不能说不常见吧,反正是有点高级的。所以这个解决方案还是有点东西的,你可以用来在面试中吹牛

2. 兜底方案流程图

本文介绍的解决方案可以用下面的流程图表示:

在一个空白的文件夹下面创建以下文件

使用到的代码如下:

<!DOCTYPE html>
<html>

<head>
  <title>Web Worker Image Processing</title>
</head>

<body>
  <img id="originalImage" src="https://images.pexels.com/photos/12196392/pexels-photo-12196392.jpeg?auto=compress&cs=tinysrgb&w=600&lazy=load" alt="Original Image" />
  <img id="processedImage" alt="Processed Image" />

  <script>
    // 创建一个Web Worker实例  
    const worker = new Worker('worker.js');

    // 监听Web Worker的消息  
    worker.onmessage = function (e) {
      const processedImageUrl = e.data;
      
      const _ = document.getElementById('processedImage');
      _.onload = () => {
        worker.terminate();
      }
      _.src = processedImageUrl;
    };

    // 图片地址,你可以根据需要修改  
    const imageUrl = 'https://images.pexels.com/photos/12196392/pexels-photo-12196392.jpeg?auto=compress&cs=tinysrgb&w=600&lazy=load';

    // 当原始图片加载完成后,向Web Worker发送消息  
    document.getElementById('originalImage').onload = function () {
      worker.postMessage(imageUrl);
    };  
  </script>
</body>

</html>
// worker.js
// 当此Web Worker接收到消息时,会调用此函数  
self.onmessage = async function (e) {  
  
  // 从接收到的消息中提取出图片的URL  
  const imageUrl = e.data;  
  
  try {  
    // 使用fetch API从给定的URL异步获取图片资源  
    const response = await fetch(imageUrl);  
  
    // 检查HTTP响应状态,如果不是200-299之间,则抛出错误  
    if (!response.ok) {    
      throw new Error(`HTTP error! status: ${response.status}`);    
    }  
  
    // 将响应体转换为Blob对象,这通常用于处理二进制数据  
    const blob = await response.blob();  
  
    // 使用Blob对象创建一个ImageBitmap,这是一个可以高效绘制到Canvas上的位图图像  
    const imgBitmap = createImageBitmap(blob);  
  
    // ImageBitmap对象创建是异步的,所以使用.then()来处理创建成功后的操作  
    imgBitmap.then(function (bitmap) {  
      // 创建一个离屏Canvas,其尺寸与位图相同  
      const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);  
  
      // 获取Canvas的2D渲染上下文  
      const ctx = canvas.getContext('2d');  
  
      // 在Canvas上绘制位图  
      ctx.drawImage(bitmap, 0, 0);  
  
      // 从Canvas上获取图像数据  
      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);  
  
      // 获取图像数据的像素数组  
      const data = imageData.data;  
  
      // 遍历每个像素,将其转换为灰度(通过计算RGB通道的平均值,并将其设置为每个通道的值)  
      for (let i = 0; i < data.length; i += 4) {  
        const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;  
        data[i] = avg; // R  
        data[i + 1] = avg; // G  
        data[i + 2] = avg; // B  
      }  
  
      // 将处理后的图像数据放回Canvas  
      ctx.putImageData(imageData, 0, 0);  
  
      // 将Canvas转换为Blob对象  
      canvas.convertToBlob().then(blob => {  
        // 创建一个表示该Blob对象的URL  
        const newUrl = URL.createObjectURL(blob);  
  
        // 将新的图像URL发送回主线程  
        self.postMessage(newUrl);  
      });  
  
    }).catch(e => {  
      // 如果在处理过程中发生错误,则抛出一个新的错误(这里可以添加更详细的错误处理)  
      throw new Error();  
    });  
  
  } catch (e) {  
    // 如果在尝试获取或处理图像时发生错误,则将原始图像URL发送回主线程  
    self.postMessage(imageUrl);  
  }   
};

理解上面代码的逻辑可以结合代码注释和流程图,在此就不过多赘述了。

完成之后使用 live server 启动 index.html 可以看到如下效果:

左边是原图,而右边是经过 web worker 处理之后的图像。

在这个示例中,你可以简单的将 web worker 理解成为 PS。它接受原始图片的地址,返回处理之后的图片的地址。

需要注意的是,图片是二进制数据,所以我们用到了 Blob 将数据转成 URL。

3. 封装成 React 组件

在 React 中使用 web worker 需要一点技巧,请参考我之前的文章: 在react项目中使用web worker的方法 - 掘金 (juejin.cn)

在你的前端工程目录中创建 src/component/ImageProcessor 目录,然后创建下面两个文件:

文件中的代码如下所示:

// index.js
// 引入React及其相关hooks
import React, { useEffect, useRef, useState } from 'react';
// 引入Web Worker的脚本
import workerScript from './worker';
// ImageProcessor组件,它接受原始图片的URL、宽度和高度作为属性
const ImageProcessor = ({ originSrc, width, height }) => {
  // 使用useState hook来存储处理后的图片URL,初始值为原始图片的URL
  const [src, setSrc] = useState(originSrc);
  // 使用useRef hook来存储Web Worker的实例
  const workerInstance = useRef(new Worker(workerScript));
  // 使用useEffect hook来处理图片的URL变化
  useEffect(() => {
    // 当原始图片URL与处理后的图片URL不相同时,才进行处理
    if (originSrc === src) {
      // 设置Web Worker的onmessage事件处理函数
      workerInstance.current.onmessage = function (e) {
        // 接收处理后的图片URL
        const processedImageUrl = e.data;
        // 更新处理后的图片URL状态
        setSrc(processedImageUrl);
      };
      // 向Web Worker发送原始图片的完整URL,以便进行处理
      workerInstance.current.postMessage(window.location.origin + originSrc);
    }
    // 清除函数,在组件卸载或状态变化时终止Web Worker
    return () => {
      workerInstance.current.terminate();
    }
    // 当原始图片URL或处理后的图片URL发生变化时,触发此hook
  }, [src, originSrc])

  // 图片加载完成后的事件处理函数
  const imageLoaded = (event) => {
    // 终止当前的Web Worker
    workerInstance.current.terminate();
  };

  // 返回JSX,表示组件的UI
  return (
    <div>
      {/* 当原始图片的URL与处理后的图片URL不相同时,显示处理后的图片 */}
      {originSrc !== src && <img
        style={{
          position: 'absolute',
          left: 0,
          top: 0,
          zIndex: 0,
          width: width ?? '100%',// 使用nullish coalescing操作符来提供默认值
          height: height ?? '100%',
          objectFit: 'cover',// 图片填充方式
        }}
        src={src}// 设置图片的URL
        alt="Original Image"// 图片的替代文本
        onLoad={imageLoaded}// 图片加载完成后的事件处理函数
      />}
    </div>
  );
};

// 导出ImageProcessor组件
export default ImageProcessor;
// worker.js
// 定义一个workerCode函数,这个函数将作为Web Worker的代码  
const workerCode = () => {  
  
  // 使用_self变量来引用全局的self对象,以便在Web Worker内部使用  
  // 是用来告诉eslint忽略下一行的代码检查,因为这里我们对self进行了重新赋值
  // eslint-disable-next-line   
  const _self = self;  
  
  // 当Web Worker接收到消息时,会调用此函数  
  _self.onmessage = async function (e) {  
    // 从接收到的消息中提取出图片的URL  
    const imageUrl = e.data;  
  
    try {  
      // 使用fetch API异步地从给定的URL获取图片资源  
      const response = await fetch(imageUrl);  
  
      // 检查HTTP响应状态,如果不是200-299之间,则抛出错误  
      if (!response.ok) {  
        throw new Error(`HTTP error! status: ${response.status}`);  
      }  
  
      // 将HTTP响应的内容转换为Blob对象,这通常用于处理二进制数据  
      const blob = await response.blob();  
  
      // 使用Blob对象创建一个ImageBitmap,这是一个可以高效绘制到Canvas上的位图图像  
      // 注意:createImageBitmap是异步的  
      const imgBitmap = createImageBitmap(blob);  
  
      // ImageBitmap对象创建成功后,会执行以下操作  
      imgBitmap.then(function (bitmap) {  
        // 创建一个与位图尺寸相同的离屏Canvas  
        const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);  
  
        // 获取这个离屏Canvas的2D渲染上下文  
        const ctx = canvas.getContext('2d');  
  
        // 在Canvas上绘制之前创建的位图  
        ctx.drawImage(bitmap, 0, 0);  
  
        // 从Canvas上获取整个图像的图像数据  
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);  
  
        // 获取图像数据的像素数组  
        const data = imageData.data;  
  
        // 遍历每个像素,将彩色图像转换为灰度图像  
        // 灰度是通过计算RGB三个通道的平均值,并将其设置为每个通道的值来得到的  
        for (let i = 0; i < data.length; i += 4) {  
          const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;  
          data[i] = avg;     // R  
          data[i + 1] = avg; // G  
          data[i + 2] = avg; // B  
        }  
  
        // 将处理后的灰度图像数据重新放回Canvas  
        ctx.putImageData(imageData, 0, 0);  
  
        // 将Canvas内容转换为Blob对象  
        canvas.convertToBlob().then(blob => {  
          // 创建一个表示该Blob对象的URL  
          const newUrl = URL.createObjectURL(blob);  
          // 将处理后的灰度图像的URL发送回主线程  
          _self.postMessage(newUrl);  
        });  
  
      }).catch(e => {  
        // 如果在处理ImageBitmap或Canvas时出现错误,抛出一个新的错误  
        // 这里可以添加更详细的错误处理逻辑  
        throw new Error();  
      });  
  
    } catch (e) {  
      // 如果在尝试获取或处理图像时发生任何错误(如网络错误、fetch失败等)  
      // 则将原始的图像URL发送回主线程,表示处理失败  
      _self.postMessage(imageUrl);  
    }  
  };  
};  
  
// 将workerCode函数转换为字符串,并截取大括号内的内容作为Web Worker的代码  
let code = workerCode.toString();  
code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));  
  
// 创建一个包含处理过的代码的Blob对象  
const blob = new Blob([code], { type: 'application/javascript' });  
// 为这个Blob对象创建一个URL,这个URL可以被用作Web Worker的脚本源  
const workerScriptURL = URL.createObjectURL(blob);  
  
// 导出这个URL,以便其他模块可以使用它来创建一个新的Web Worker  
export default workerScriptURL;

这样组件就封装完成了,在你需要的地方使用下面的代码来调用上面的组件

import ImageProcessor from "@/component/ImageProcessor";
<ImageProcessor originSrc = {CompressImageUrl} />

你可以将上述代码的这部分抽取成一个函数,放到公用工具库中:

// 遍历每个像素,将彩色图像转换为灰度图像  
// 灰度是通过计算RGB三个通道的平均值,并将其设置为每个通道的值来得到的  
for (let i = 0; i < data.length; i += 4) {  
  const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;  
  data[i] = avg;     // R  
  data[i + 1] = avg; // G  
  data[i + 2] = avg; // B  
}  

这样一来,通过替换像素算法就可以实现更加有趣且复杂的图像处理了。

最后总结一下上面代码中值得注意的细节:

以上就是React处理复杂图片样式的方法详解的详细内容,更多关于React处理图片样式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文