当前位置: 代码网 > it编程>编程语言>Javascript > React处理复杂图片样式的方法详解

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

2024年05月28日 Javascript 我要评论
正如文章题目所示,本文的目的是为了记录工作中遇到的,在页面中处理复杂图片样式的解决方案。使用的技术栈有:web workerfetchcreateimagebitmapoffscreencanvasb

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

  • web worker
  • fetch
  • createimagebitmap
  • offscreencanvas
  • blob

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

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

  • web worker 的实际使用案例
  • 前端 ps 的基础知识

下面让我们开始吧~

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

1. 技术栈介绍

  • web worker: web worker是浏览器提供的一种能在后台线程中运行javascript的技术,它不会阻塞或影响页面的性能。通过创建一个新的worker对象,可以将耗时的计算或数据处理任务放在后台执行,从而避免ui线程被长时间占用,提高页面的响应性和用户体验。web worker通过postmessage和onmessage进行主线程和工作线程之间的通信。
  • fetch: fetch是一个现代、强大且灵活的网络请求api,它提供了一个全局fetch()方法,用于异步获取网络资源。与xmlhttprequest相比,fetch更加简洁、高效,且支持promise模式,使得异步处理更加直观和方便。fetch还支持跨域请求、请求和响应的拦截、处理http管道等高级功能,是现代web开发中网络请求的首选方式。
  • createimagebitmap: createimagebitmap是一个用于创建位图图像的api,它可以直接从各种图像源(如blob、imagedata、imagebitmap、htmlcanvaselement等)生成一个高效的位图。这个api是异步的,不会阻塞主线程,因此非常适合用于处理大量图像数据。生成的imagebitmap对象可以直接用于canvas的drawimage方法,极大地提高了图像渲染的性能。
  • offscreencanvas: offscreencanvas是html5 canvas api的一个扩展,它允许在web worker中使用canvas功能。这意味着图像处理、渲染等计算密集型任务可以在不阻塞主线程的情况下进行。offscreencanvas通过transfercontroltooffscreen或getcontext('2d', {offscreen: true})等方式创建,并可以在主线程和工作线程之间传递,从而实现了真正的后台渲染。
  • blob: blob(binary large object)是一个用于处理二进制数据的javascript对象。它可以存储大量的二进制数据,并允许通过url.createobjecturl()方法创建一个指向该数据的url,这个url可以直接用于img、audio、video等元素的src属性。blob常用于处理文件上传、下载以及图像、音频、视频的动态生成等场景。此外,blob还支持切片操作,便于大数据的处理和传输。

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

2. 兜底方案流程图

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

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

  • index.html
  • worker.js

使用到的代码如下:

<!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 需要一点技巧,请参考我之前的文章:

在你的前端工程目录中创建 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 hooks的使用usestate, useeffect, useref这三个react hooks在代码中都被使用了。usestate用于存储处理后的图片url;useeffect用于在组件挂载、更新时执行某些操作(如启动和终止web worker);useref用于存储web worker的实例,并确保其在整个组件生命周期内保持不变。
  • web worker的使用:为了提高性能,代码使用了web worker来在后台处理图像转换任务,从而避免阻塞主线程。web worker通过postmessageonmessage进行通信。
  • blob和url.createobjecturl:在处理图像后,web worker使用bloburl.createobjecturl创建了一个新的url,这个url指向包含处理后图像数据的blob对象。这样可以在不将图像数据实际写入磁盘的情况下,将其作为一个可访问的资源。
  • createimagebitmap和offscreencanvas:为了提高图像处理性能,代码中使用了createimagebitmap来高效地将blob转换为位图,并使用offscreencanvas进行离屏渲染。这两个api都允许在不影响页面渲染的情况下进行高性能的图像处理。
  • 灰度图像处理:在web worker内部,代码遍历了图像的每个像素,并将其转换为灰度。这是通过计算rgb通道的平均值,并将这个平均值设置为新的rgb值来实现的。
  • 动态创建web worker脚本worker.js文件最后将自身代码转换为字符串,并通过blob和url.createobjecturl创建了一个url,这个url被用作web worker的脚本源。这是一种动态创建和执行javascript代码的技术,允许在不依赖外部文件的情况下运行web worker。
  • 错误处理和异常捕获:在web worker内部和外部都有错误处理和异常捕获的逻辑。例如,在尝试获取或处理图像时发生错误,会将原始的图像url发送回主线程表示处理失败。
  • 使用nullish coalescing操作符:在jsx中,width ?? '100%'height ?? '100%'使用了nullish coalescing操作符(??),这是es2020引入的新特性。它允许在左侧操作数为nullundefined时返回右侧的值,否则返回左侧的值。这提供了一种简洁的方式来为变量提供默认值。
  • 图片加载完成后的处理:在图片加载完成后,会终止当前的web worker。这有助于释放资源并避免不必要的后台处理。
  • 代码组织和模块导出imageprocessor组件和workerscripturl都被导出,以便在其他模块中使用。这体现了良好的模块化和代码复用实践。

以上就是react处理复杂图片样式的方法详解的详细内容,更多关于react处理图片样式的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com