React處理復雜圖片樣式的方法詳解
正如文章題目所示,本文的目的是為了記錄工作中遇到的,在頁面中處理復雜圖片樣式的解決方案。使用的技術棧有:
- web Worker
- fetch
- createImageBitmap
- OffscreenCanvas
- Blob
之所以稱之為“兜底”方案,是因為下文中提到的方法能夠以像素級別操作圖片。而,如果只是這樣,也沒有什么值得記錄的。但是,本文為了防止這種像素級別的操作對頁面渲染性能造成大的沖擊,結合 web worker 異步處理,解決了這個問題。
從本文的思路出發(fā),至少可以收獲兩點技術:
- web worker 的實際使用案例
- 前端 PS 的基礎知識
下面讓我們開始吧~
本文分為三個小節(jié),第一小節(jié)簡單的介紹上面提到的技術棧;第二小節(jié)在非工程化 Demo 中演示這個解決方案的流程;第三小節(jié)將此方法分裝成一個 React 組件,以便后面復用和維護。
1. 技術棧介紹
- Web Worker: Web Worker是瀏覽器提供的一種能在后臺線程中運行JavaScript的技術,它不會阻塞或影響頁面的性能。通過創(chuàng)建一個新的Worker對象,可以將耗時的計算或數(shù)據(jù)處理任務放在后臺執(zhí)行,從而避免UI線程被長時間占用,提高頁面的響應性和用戶體驗。Web Worker通過postMessage和onmessage進行主線程和工作線程之間的通信。
- Fetch: Fetch是一個現(xiàn)代、強大且靈活的網(wǎng)絡請求API,它提供了一個全局fetch()方法,用于異步獲取網(wǎng)絡資源。與XMLHttpRequest相比,F(xiàn)etch更加簡潔、高效,且支持Promise模式,使得異步處理更加直觀和方便。Fetch還支持跨域請求、請求和響應的攔截、處理HTTP管道等高級功能,是現(xiàn)代Web開發(fā)中網(wǎng)絡請求的首選方式。
- createImageBitmap: createImageBitmap是一個用于創(chuàng)建位圖圖像的API,它可以直接從各種圖像源(如Blob、ImageData、ImageBitmap、HTMLCanvasElement等)生成一個高效的位圖。這個API是異步的,不會阻塞主線程,因此非常適合用于處理大量圖像數(shù)據(jù)。生成的ImageBitmap對象可以直接用于Canvas的drawImage方法,極大地提高了圖像渲染的性能。
- OffscreenCanvas: OffscreenCanvas是HTML5 Canvas API的一個擴展,它允許在Web Worker中使用Canvas功能。這意味著圖像處理、渲染等計算密集型任務可以在不阻塞主線程的情況下進行。OffscreenCanvas通過transferControlToOffscreen或getContext('2d', {offscreen: true})等方式創(chuàng)建,并可以在主線程和工作線程之間傳遞,從而實現(xiàn)了真正的后臺渲染。
- Blob: Blob(Binary Large Object)是一個用于處理二進制數(shù)據(jù)的JavaScript對象。它可以存儲大量的二進制數(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'; // 當原始圖片加載完成后,向Web Worker發(fā)送消息 document.getElementById('originalImage').onload = function () { worker.postMessage(imageUrl); }; </script> </body> </html>
// worker.js // 當此Web Worker接收到消息時,會調用此函數(shù) self.onmessage = async function (e) { // 從接收到的消息中提取出圖片的URL const imageUrl = e.data; try { // 使用fetch API從給定的URL異步獲取圖片資源 const response = await fetch(imageUrl); // 檢查HTTP響應狀態(tài),如果不是200-299之間,則拋出錯誤 if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // 將響應體轉換為Blob對象,這通常用于處理二進制數(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; // 遍歷每個像素,將其轉換為灰度(通過計算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 } // 將處理后的圖像數(shù)據(jù)放回Canvas ctx.putImageData(imageData, 0, 0); // 將Canvas轉換為Blob對象 canvas.convertToBlob().then(blob => { // 創(chuàng)建一個表示該Blob對象的URL const newUrl = URL.createObjectURL(blob); // 將新的圖像URL發(fā)送回主線程 self.postMessage(newUrl); }); }).catch(e => { // 如果在處理過程中發(fā)生錯誤,則拋出一個新的錯誤(這里可以添加更詳細的錯誤處理) throw new Error(); }); } catch (e) { // 如果在嘗試獲取或處理圖像時發(fā)生錯誤,則將原始圖像URL發(fā)送回主線程 self.postMessage(imageUrl); } };
理解上面代碼的邏輯可以結合代碼注釋和流程圖,在此就不過多贅述了。
完成之后使用 live server 啟動 index.html
可以看到如下效果:
左邊是原圖,而右邊是經(jīng)過 web worker 處理之后的圖像。
在這個示例中,你可以簡單的將 web worker 理解成為 PS。它接受原始圖片的地址,返回處理之后的圖片的地址。
需要注意的是,圖片是二進制數(shù)據(jù),所以我們用到了 Blob 將數(shù)據(jù)轉成 URL。
3. 封裝成 React 組件
在 React 中使用 web worker 需要一點技巧,請參考我之前的文章: 在react項目中使用web worker的方法 - 掘金 (juejin.cn)
在你的前端工程目錄中創(chuàng)建 src/component/ImageProcessor
目錄,然后創(chuàng)建下面兩個文件:
文件中的代碼如下所示:
// 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事件處理函數(shù) workerInstance.current.onmessage = function (e) { // 接收處理后的圖片URL const processedImageUrl = e.data; // 更新處理后的圖片URL狀態(tài) setSrc(processedImageUrl); }; // 向Web Worker發(fā)送原始圖片的完整URL,以便進行處理 workerInstance.current.postMessage(window.location.origin + originSrc); } // 清除函數(shù),在組件卸載或狀態(tài)變化時終止Web Worker return () => { workerInstance.current.terminate(); } // 當原始圖片URL或處理后的圖片URL發(fā)生變化時,觸發(fā)此hook }, [src, originSrc]) // 圖片加載完成后的事件處理函數(shù) 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"http:// 圖片的替代文本 onLoad={imageLoaded}// 圖片加載完成后的事件處理函數(shù) />} </div> ); }; // 導出ImageProcessor組件 export default ImageProcessor;
// worker.js // 定義一個workerCode函數(shù),這個函數(shù)將作為Web Worker的代碼 const workerCode = () => { // 使用_self變量來引用全局的self對象,以便在Web Worker內部使用 // 是用來告訴eslint忽略下一行的代碼檢查,因為這里我們對self進行了重新賦值 // eslint-disable-next-line const _self = self; // 當Web Worker接收到消息時,會調用此函數(shù) _self.onmessage = async function (e) { // 從接收到的消息中提取出圖片的URL const imageUrl = e.data; try { // 使用fetch API異步地從給定的URL獲取圖片資源 const response = await fetch(imageUrl); // 檢查HTTP響應狀態(tài),如果不是200-299之間,則拋出錯誤 if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // 將HTTP響應的內容轉換為Blob對象,這通常用于處理二進制數(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; // 遍歷每個像素,將彩色圖像轉換為灰度圖像 // 灰度是通過計算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 } // 將處理后的灰度圖像數(shù)據(jù)重新放回Canvas ctx.putImageData(imageData, 0, 0); // 將Canvas內容轉換為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)錯誤,拋出一個新的錯誤 // 這里可以添加更詳細的錯誤處理邏輯 throw new Error(); }); } catch (e) { // 如果在嘗試獲取或處理圖像時發(fā)生任何錯誤(如網(wǎng)絡錯誤、fetch失敗等) // 則將原始的圖像URL發(fā)送回主線程,表示處理失敗 _self.postMessage(imageUrl); } }; }; // 將workerCode函數(shù)轉換為字符串,并截取大括號內的內容作為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); // 導出這個URL,以便其他模塊可以使用它來創(chuàng)建一個新的Web Worker export default workerScriptURL;
這樣組件就封裝完成了,在你需要的地方使用下面的代碼來調用上面的組件
import ImageProcessor from "@/component/ImageProcessor"; <ImageProcessor originSrc = {CompressImageUrl} />
你可以將上述代碼的這部分抽取成一個函數(shù),放到公用工具庫中:
// 遍歷每個像素,將彩色圖像轉換為灰度圖像 // 灰度是通過計算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 }
這樣一來,通過替換像素算法就可以實現(xiàn)更加有趣且復雜的圖像處理了。
最后總結一下上面代碼中值得注意的細節(jié):
- React Hooks的使用:
useState
,useEffect
,useRef
這三個React Hooks在代碼中都被使用了。useState
用于存儲處理后的圖片URL;useEffect
用于在組件掛載、更新時執(zhí)行某些操作(如啟動和終止Web Worker);useRef
用于存儲Web Worker的實例,并確保其在整個組件生命周期內保持不變。 - Web Worker的使用:為了提高性能,代碼使用了Web Worker來在后臺處理圖像轉換任務,從而避免阻塞主線程。Web Worker通過
postMessage
和onmessage
進行通信。 - Blob和URL.createObjectURL:在處理圖像后,Web Worker使用
Blob
和URL.createObjectURL
創(chuàng)建了一個新的URL,這個URL指向包含處理后圖像數(shù)據(jù)的Blob對象。這樣可以在不將圖像數(shù)據(jù)實際寫入磁盤的情況下,將其作為一個可訪問的資源。 - createImageBitmap和OffscreenCanvas:為了提高圖像處理性能,代碼中使用了
createImageBitmap
來高效地將Blob轉換為位圖,并使用OffscreenCanvas
進行離屏渲染。這兩個API都允許在不影響頁面渲染的情況下進行高性能的圖像處理。 - 灰度圖像處理:在Web Worker內部,代碼遍歷了圖像的每個像素,并將其轉換為灰度。這是通過計算RGB通道的平均值,并將這個平均值設置為新的RGB值來實現(xiàn)的。
- 動態(tài)創(chuàng)建Web Worker腳本:
worker.js
文件最后將自身代碼轉換為字符串,并通過Blob和URL.createObjectURL
創(chuàng)建了一個URL,這個URL被用作Web Worker的腳本源。這是一種動態(tài)創(chuàng)建和執(zhí)行JavaScript代碼的技術,允許在不依賴外部文件的情況下運行Web Worker。 - 錯誤處理和異常捕獲:在Web Worker內部和外部都有錯誤處理和異常捕獲的邏輯。例如,在嘗試獲取或處理圖像時發(fā)生錯誤,會將原始的圖像URL發(fā)送回主線程表示處理失敗。
- 使用nullish coalescing操作符:在JSX中,
width ?? '100%'
和height ?? '100%'
使用了nullish coalescing操作符(??
),這是ES2020引入的新特性。它允許在左側操作數(shù)為null
或undefined
時返回右側的值,否則返回左側的值。這提供了一種簡潔的方式來為變量提供默認值。 - 圖片加載完成后的處理:在圖片加載完成后,會終止當前的Web Worker。這有助于釋放資源并避免不必要的后臺處理。
- 代碼組織和模塊導出:
ImageProcessor
組件和workerScriptURL
都被導出,以便在其他模塊中使用。這體現(xiàn)了良好的模塊化和代碼復用實踐。
以上就是React處理復雜圖片樣式的方法詳解的詳細內容,更多關于React處理圖片樣式的資料請關注腳本之家其它相關文章!
相關文章
ReactNative 之FlatList使用及踩坑封裝總結
本篇文章主要介紹了ReactNative 之FlatList使用及踩坑封裝總結,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11手挽手帶你學React之React-router4.x的使用
這篇文章主要介紹了手挽手帶你學React之React-router4.x的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-02-02React+hook實現(xiàn)聯(lián)動模糊搜索
這篇文章主要為大家詳細介紹了如何利用React+hook+antd實現(xiàn)聯(lián)動模糊搜索功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-02-02