React處理復(fù)雜圖片樣式的方法詳解
正如文章題目所示,本文的目的是為了記錄工作中遇到的,在頁面中處理復(fù)雜圖片樣式的解決方案。使用的技術(shù)棧有:
- web Worker
- fetch
- createImageBitmap
- OffscreenCanvas
- Blob
之所以稱之為“兜底”方案,是因為下文中提到的方法能夠以像素級別操作圖片。而,如果只是這樣,也沒有什么值得記錄的。但是,本文為了防止這種像素級別的操作對頁面渲染性能造成大的沖擊,結(jié)合 web worker 異步處理,解決了這個問題。
從本文的思路出發(fā),至少可以收獲兩點技術(shù):
- web worker 的實際使用案例
- 前端 PS 的基礎(chǔ)知識
下面讓我們開始吧~
本文分為三個小節(jié),第一小節(jié)簡單的介紹上面提到的技術(shù)棧;第二小節(jié)在非工程化 Demo 中演示這個解決方案的流程;第三小節(jié)將此方法分裝成一個 React 組件,以便后面復(fù)用和維護(hù)。
1. 技術(shù)棧介紹
- Web Worker: Web Worker是瀏覽器提供的一種能在后臺線程中運行JavaScript的技術(shù),它不會阻塞或影響頁面的性能。通過創(chuàng)建一個新的Worker對象,可以將耗時的計算或數(shù)據(jù)處理任務(wù)放在后臺執(zhí)行,從而避免UI線程被長時間占用,提高頁面的響應(yīng)性和用戶體驗。Web Worker通過postMessage和onmessage進(jìn)行主線程和工作線程之間的通信。
- Fetch: Fetch是一個現(xiàn)代、強(qiáng)大且靈活的網(wǎng)絡(luò)請求API,它提供了一個全局fetch()方法,用于異步獲取網(wǎng)絡(luò)資源。與XMLHttpRequest相比,F(xiàn)etch更加簡潔、高效,且支持Promise模式,使得異步處理更加直觀和方便。Fetch還支持跨域請求、請求和響應(yīng)的攔截、處理HTTP管道等高級功能,是現(xiàn)代Web開發(fā)中網(wǎng)絡(luò)請求的首選方式。
- createImageBitmap: createImageBitmap是一個用于創(chuàng)建位圖圖像的API,它可以直接從各種圖像源(如Blob、ImageData、ImageBitmap、HTMLCanvasElement等)生成一個高效的位圖。這個API是異步的,不會阻塞主線程,因此非常適合用于處理大量圖像數(shù)據(jù)。生成的ImageBitmap對象可以直接用于Canvas的drawImage方法,極大地提高了圖像渲染的性能。
- OffscreenCanvas: OffscreenCanvas是HTML5 Canvas API的一個擴(kuò)展,它允許在Web Worker中使用Canvas功能。這意味著圖像處理、渲染等計算密集型任務(wù)可以在不阻塞主線程的情況下進(jìn)行。OffscreenCanvas通過transferControlToOffscreen或getContext('2d', {offscreen: true})等方式創(chuàng)建,并可以在主線程和工作線程之間傳遞,從而實現(xiàn)了真正的后臺渲染。
- Blob: Blob(Binary Large Object)是一個用于處理二進(jìn)制數(shù)據(jù)的JavaScript對象。它可以存儲大量的二進(jìn)制數(shù)據(jù),并允許通過URL.createObjectURL()方法創(chuàng)建一個指向該數(shù)據(jù)的URL,這個URL可以直接用于img、audio、video等元素的src屬性。Blob常用于處理文件上傳、下載以及圖像、音頻、視頻的動態(tài)生成等場景。此外,Blob還支持切片操作,便于大數(shù)據(jù)的處理和傳輸。
這些知識不能說不常見吧,反正是有點高級的。所以這個解決方案還是有點東西的,你可以用來在面試中吹牛
2. 兜底方案流程圖
本文介紹的解決方案可以用下面的流程圖表示:

在一個空白的文件夾下面創(chuàng)建以下文件
- 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>
// 創(chuàng)建一個Web Worker實例
const worker = new Worker('worker.js');
// 監(jiān)聽Web Worker的消息
worker.onmessage = function (e) {
const processedImageUrl = e.data;
const _ = document.getElementById('processedImage');
_.onload = () => {
worker.terminate();
}
_.src = processedImageUrl;
};
// 圖片地址,你可以根據(jù)需要修改
const imageUrl = 'https://images.pexels.com/photos/12196392/pexels-photo-12196392.jpeg?auto=compress&cs=tinysrgb&w=600&lazy=load';
// 當(dāng)原始圖片加載完成后,向Web Worker發(fā)送消息
document.getElementById('originalImage').onload = function () {
worker.postMessage(imageUrl);
};
</script>
</body>
</html>// worker.js
// 當(dāng)此Web Worker接收到消息時,會調(diào)用此函數(shù)
self.onmessage = async function (e) {
// 從接收到的消息中提取出圖片的URL
const imageUrl = e.data;
try {
// 使用fetch API從給定的URL異步獲取圖片資源
const response = await fetch(imageUrl);
// 檢查HTTP響應(yīng)狀態(tài),如果不是200-299之間,則拋出錯誤
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 將響應(yīng)體轉(zhuǎn)換為Blob對象,這通常用于處理二進(jìn)制數(shù)據(jù)
const blob = await response.blob();
// 使用Blob對象創(chuàng)建一個ImageBitmap,這是一個可以高效繪制到Canvas上的位圖圖像
const imgBitmap = createImageBitmap(blob);
// ImageBitmap對象創(chuàng)建是異步的,所以使用.then()來處理創(chuàng)建成功后的操作
imgBitmap.then(function (bitmap) {
// 創(chuàng)建一個離屏Canvas,其尺寸與位圖相同
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
// 獲取Canvas的2D渲染上下文
const ctx = canvas.getContext('2d');
// 在Canvas上繪制位圖
ctx.drawImage(bitmap, 0, 0);
// 從Canvas上獲取圖像數(shù)據(jù)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 獲取圖像數(shù)據(jù)的像素數(shù)組
const data = imageData.data;
// 遍歷每個像素,將其轉(zhuǎn)換為灰度(通過計算RGB通道的平均值,并將其設(shè)置為每個通道的值)
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
}
// 將處理后的圖像數(shù)據(jù)放回Canvas
ctx.putImageData(imageData, 0, 0);
// 將Canvas轉(zhuǎn)換為Blob對象
canvas.convertToBlob().then(blob => {
// 創(chuàng)建一個表示該Blob對象的URL
const newUrl = URL.createObjectURL(blob);
// 將新的圖像URL發(fā)送回主線程
self.postMessage(newUrl);
});
}).catch(e => {
// 如果在處理過程中發(fā)生錯誤,則拋出一個新的錯誤(這里可以添加更詳細(xì)的錯誤處理)
throw new Error();
});
} catch (e) {
// 如果在嘗試獲取或處理圖像時發(fā)生錯誤,則將原始圖像URL發(fā)送回主線程
self.postMessage(imageUrl);
}
};
理解上面代碼的邏輯可以結(jié)合代碼注釋和流程圖,在此就不過多贅述了。
完成之后使用 live server 啟動 index.html 可以看到如下效果:

左邊是原圖,而右邊是經(jīng)過 web worker 處理之后的圖像。
在這個示例中,你可以簡單的將 web worker 理解成為 PS。它接受原始圖片的地址,返回處理之后的圖片的地址。
需要注意的是,圖片是二進(jìn)制數(shù)據(jù),所以我們用到了 Blob 將數(shù)據(jù)轉(zhuǎn)成 URL。
3. 封裝成 React 組件
在 React 中使用 web worker 需要一點技巧,請參考我之前的文章: 在react項目中使用web worker的方法 - 掘金 (juejin.cn)
在你的前端工程目錄中創(chuàng)建 src/component/ImageProcessor 目錄,然后創(chuàng)建下面兩個文件:

文件中的代碼如下所示:
// index.js
// 引入React及其相關(guān)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(() => {
// 當(dāng)原始圖片URL與處理后的圖片URL不相同時,才進(jìn)行處理
if (originSrc === src) {
// 設(shè)置Web Worker的onmessage事件處理函數(shù)
workerInstance.current.onmessage = function (e) {
// 接收處理后的圖片URL
const processedImageUrl = e.data;
// 更新處理后的圖片URL狀態(tài)
setSrc(processedImageUrl);
};
// 向Web Worker發(fā)送原始圖片的完整URL,以便進(jìn)行處理
workerInstance.current.postMessage(window.location.origin + originSrc);
}
// 清除函數(shù),在組件卸載或狀態(tài)變化時終止Web Worker
return () => {
workerInstance.current.terminate();
}
// 當(dāng)原始圖片URL或處理后的圖片URL發(fā)生變化時,觸發(fā)此hook
}, [src, originSrc])
// 圖片加載完成后的事件處理函數(shù)
const imageLoaded = (event) => {
// 終止當(dāng)前的Web Worker
workerInstance.current.terminate();
};
// 返回JSX,表示組件的UI
return (
<div>
{/* 當(dāng)原始圖片的URL與處理后的圖片URL不相同時,顯示處理后的圖片 */}
{originSrc !== src && <img
style={{
position: 'absolute',
left: 0,
top: 0,
zIndex: 0,
width: width ?? '100%',// 使用nullish coalescing操作符來提供默認(rèn)值
height: height ?? '100%',
objectFit: 'cover',// 圖片填充方式
}}
src={src}// 設(shè)置圖片的URL
alt="Original Image"http:// 圖片的替代文本
onLoad={imageLoaded}// 圖片加載完成后的事件處理函數(shù)
/>}
</div>
);
};
// 導(dǎo)出ImageProcessor組件
export default ImageProcessor;
// worker.js
// 定義一個workerCode函數(shù),這個函數(shù)將作為Web Worker的代碼
const workerCode = () => {
// 使用_self變量來引用全局的self對象,以便在Web Worker內(nèi)部使用
// 是用來告訴eslint忽略下一行的代碼檢查,因為這里我們對self進(jìn)行了重新賦值
// eslint-disable-next-line
const _self = self;
// 當(dāng)Web Worker接收到消息時,會調(diào)用此函數(shù)
_self.onmessage = async function (e) {
// 從接收到的消息中提取出圖片的URL
const imageUrl = e.data;
try {
// 使用fetch API異步地從給定的URL獲取圖片資源
const response = await fetch(imageUrl);
// 檢查HTTP響應(yīng)狀態(tài),如果不是200-299之間,則拋出錯誤
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 將HTTP響應(yīng)的內(nèi)容轉(zhuǎn)換為Blob對象,這通常用于處理二進(jìn)制數(shù)據(jù)
const blob = await response.blob();
// 使用Blob對象創(chuàng)建一個ImageBitmap,這是一個可以高效繪制到Canvas上的位圖圖像
// 注意:createImageBitmap是異步的
const imgBitmap = createImageBitmap(blob);
// ImageBitmap對象創(chuàng)建成功后,會執(zhí)行以下操作
imgBitmap.then(function (bitmap) {
// 創(chuàng)建一個與位圖尺寸相同的離屏Canvas
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
// 獲取這個離屏Canvas的2D渲染上下文
const ctx = canvas.getContext('2d');
// 在Canvas上繪制之前創(chuàng)建的位圖
ctx.drawImage(bitmap, 0, 0);
// 從Canvas上獲取整個圖像的圖像數(shù)據(jù)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 獲取圖像數(shù)據(jù)的像素數(shù)組
const data = imageData.data;
// 遍歷每個像素,將彩色圖像轉(zhuǎn)換為灰度圖像
// 灰度是通過計算RGB三個通道的平均值,并將其設(shè)置為每個通道的值來得到的
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
}
// 將處理后的灰度圖像數(shù)據(jù)重新放回Canvas
ctx.putImageData(imageData, 0, 0);
// 將Canvas內(nèi)容轉(zhuǎn)換為Blob對象
canvas.convertToBlob().then(blob => {
// 創(chuàng)建一個表示該Blob對象的URL
const newUrl = URL.createObjectURL(blob);
// 將處理后的灰度圖像的URL發(fā)送回主線程
_self.postMessage(newUrl);
});
}).catch(e => {
// 如果在處理ImageBitmap或Canvas時出現(xiàn)錯誤,拋出一個新的錯誤
// 這里可以添加更詳細(xì)的錯誤處理邏輯
throw new Error();
});
} catch (e) {
// 如果在嘗試獲取或處理圖像時發(fā)生任何錯誤(如網(wǎng)絡(luò)錯誤、fetch失敗等)
// 則將原始的圖像URL發(fā)送回主線程,表示處理失敗
_self.postMessage(imageUrl);
}
};
};
// 將workerCode函數(shù)轉(zhuǎn)換為字符串,并截取大括號內(nèi)的內(nèi)容作為Web Worker的代碼
let code = workerCode.toString();
code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
// 創(chuàng)建一個包含處理過的代碼的Blob對象
const blob = new Blob([code], { type: 'application/javascript' });
// 為這個Blob對象創(chuàng)建一個URL,這個URL可以被用作Web Worker的腳本源
const workerScriptURL = URL.createObjectURL(blob);
// 導(dǎo)出這個URL,以便其他模塊可以使用它來創(chuàng)建一個新的Web Worker
export default workerScriptURL;
這樣組件就封裝完成了,在你需要的地方使用下面的代碼來調(diào)用上面的組件
import ImageProcessor from "@/component/ImageProcessor";
<ImageProcessor originSrc = {CompressImageUrl} />
你可以將上述代碼的這部分抽取成一個函數(shù),放到公用工具庫中:
// 遍歷每個像素,將彩色圖像轉(zhuǎn)換為灰度圖像
// 灰度是通過計算RGB三個通道的平均值,并將其設(shè)置為每個通道的值來得到的
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
}
這樣一來,通過替換像素算法就可以實現(xiàn)更加有趣且復(fù)雜的圖像處理了。
最后總結(jié)一下上面代碼中值得注意的細(xì)節(jié):
- React Hooks的使用:
useState,useEffect,useRef這三個React Hooks在代碼中都被使用了。useState用于存儲處理后的圖片URL;useEffect用于在組件掛載、更新時執(zhí)行某些操作(如啟動和終止Web Worker);useRef用于存儲Web Worker的實例,并確保其在整個組件生命周期內(nèi)保持不變。 - Web Worker的使用:為了提高性能,代碼使用了Web Worker來在后臺處理圖像轉(zhuǎn)換任務(wù),從而避免阻塞主線程。Web Worker通過
postMessage和onmessage進(jìn)行通信。 - Blob和URL.createObjectURL:在處理圖像后,Web Worker使用
Blob和URL.createObjectURL創(chuàng)建了一個新的URL,這個URL指向包含處理后圖像數(shù)據(jù)的Blob對象。這樣可以在不將圖像數(shù)據(jù)實際寫入磁盤的情況下,將其作為一個可訪問的資源。 - createImageBitmap和OffscreenCanvas:為了提高圖像處理性能,代碼中使用了
createImageBitmap來高效地將Blob轉(zhuǎn)換為位圖,并使用OffscreenCanvas進(jìn)行離屏渲染。這兩個API都允許在不影響頁面渲染的情況下進(jìn)行高性能的圖像處理。 - 灰度圖像處理:在Web Worker內(nèi)部,代碼遍歷了圖像的每個像素,并將其轉(zhuǎn)換為灰度。這是通過計算RGB通道的平均值,并將這個平均值設(shè)置為新的RGB值來實現(xiàn)的。
- 動態(tài)創(chuàng)建Web Worker腳本:
worker.js文件最后將自身代碼轉(zhuǎn)換為字符串,并通過Blob和URL.createObjectURL創(chuàng)建了一個URL,這個URL被用作Web Worker的腳本源。這是一種動態(tài)創(chuàng)建和執(zhí)行JavaScript代碼的技術(shù),允許在不依賴外部文件的情況下運行Web Worker。 - 錯誤處理和異常捕獲:在Web Worker內(nèi)部和外部都有錯誤處理和異常捕獲的邏輯。例如,在嘗試獲取或處理圖像時發(fā)生錯誤,會將原始的圖像URL發(fā)送回主線程表示處理失敗。
- 使用nullish coalescing操作符:在JSX中,
width ?? '100%'和height ?? '100%'使用了nullish coalescing操作符(??),這是ES2020引入的新特性。它允許在左側(cè)操作數(shù)為null或undefined時返回右側(cè)的值,否則返回左側(cè)的值。這提供了一種簡潔的方式來為變量提供默認(rèn)值。 - 圖片加載完成后的處理:在圖片加載完成后,會終止當(dāng)前的Web Worker。這有助于釋放資源并避免不必要的后臺處理。
- 代碼組織和模塊導(dǎo)出:
ImageProcessor組件和workerScriptURL都被導(dǎo)出,以便在其他模塊中使用。這體現(xiàn)了良好的模塊化和代碼復(fù)用實踐。
以上就是React處理復(fù)雜圖片樣式的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于React處理圖片樣式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在react-router4中進(jìn)行代碼拆分的方法(基于webpack)
這篇文章主要介紹了在react-router4中進(jìn)行代碼拆分的方法(基于webpack),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
React中事件綁定的5種實現(xiàn)方式和優(yōu)劣對比全攻略
在React中,事件綁定不僅僅是把函數(shù)和元素連接起來那么簡單,它關(guān)系到很多方面,下面我們就來一起來看看React事件綁定的5種主要方式,以及它們各自的優(yōu)缺點吧2026-01-01
react native實現(xiàn)監(jiān)控手勢上下拉動效果
這篇文章主要為大家詳細(xì)介紹了react native實現(xiàn)監(jiān)控手勢上下拉動效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05
react實現(xiàn)同頁面三級跳轉(zhuǎn)路由布局
這篇文章主要為大家詳細(xì)介紹了react實現(xiàn)同頁面三級跳轉(zhuǎn)路由布局,一個路由小案例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09
jsoneditor二次封裝實時預(yù)覽json編輯器組件react版
這篇文章主要為大家介紹了jsoneditor二次封裝實時預(yù)覽json編輯器組件react版示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
深入理解React中es6創(chuàng)建組件this的方法
this的本質(zhì)可以這樣說,this跟作用域無關(guān)的,只跟執(zhí)行上下文有關(guān)。接下來通過本文給大家介紹React中es6創(chuàng)建組件this的方法,非常不錯,感興趣的朋友一起看看吧2016-08-08

