在React中用canvas對圖片標(biāo)注的實現(xiàn)
在審核業(yè)務(wù)中難免會有需要對圖片進(jìn)行標(biāo)注的需求,本次用一個最小demo來演示如何對圖片進(jìn)行矩形標(biāo)注。
首先我們要理解canvas是一塊畫布,而這塊畫布需要在我們要標(biāo)注的圖片上層,圖片和canvas外層的div用相對位置,內(nèi)層的圖片和canvas用絕對位置,即可保證canvas重疊于圖片之上。如圖:

我們來看下canvas的初始化,在img、canvas中都有ref屬性,不同的是img的ref屬性直接就是一個useRef引用,而canvas中的ref是一個回調(diào)函數(shù)。它在組件被加載或卸載時會立即執(zhí)行,加載時ref回調(diào)接收當(dāng)前組件實例作為參數(shù),卸載時ref回調(diào)接收null作為參數(shù)。在initCanvas函數(shù)中,用canvas的ref引用承接了canvas節(jié)點,并且通過drawImage函數(shù),初始化了一塊400*400的畫布,第一個參數(shù)為需要繪制到的上下文元素:
<img src={lancome} ref={imgInstance} className="App-logo" alt="logo" />
<canvas className="canvas" ref={initCanvas} width="400px" height="400px" />const canvasRef = useRef(null);
const imgInstance = useRef(null);
const initCanvas = useCallback((node) => {
canvasRef.current = node;
const context = node.getContext('2d');
context.drawImage(imgInstance.current, 0, 0, 400, 400);
}, []);接下來,我們通過invalidLocations來保存之前的標(biāo)注位置信息,addInvalidLocation函數(shù)是為了添加標(biāo)注位置信息。最需要注意的是我們在useEffect中所監(jiān)聽的三個函數(shù),startDraw、drawingDeal和drawingEnd。
鼠標(biāo)落下時,startDraw為起始點的x,y坐標(biāo)賦值,并且拖拽狀態(tài)位isDrawing置為true。鼠標(biāo)移動時,drawingDeal函數(shù)會邊通過clearRect函數(shù)更新畫布,邊根據(jù)鼠標(biāo)的最新位置通過highlightInvalid來更新標(biāo)注,經(jīng)過確定矩形位置大小,內(nèi)容填充,描邊三個步驟來繪制出矩形。鼠標(biāo)抬起時,drawingEnd函數(shù)會通過addInvalidLocation函數(shù)添加標(biāo)注位置,然后初始化參數(shù)。
const [invalidLocations, setInvalidLocations] = useState([]);
const addInvalidLocation = useCallback((newMark) => {
setInvalidLocations([...invalidLocations, newMark]);
}, [invalidLocations])
const highlightInvalid = (context, x1, y1, x2, y2) => {
context.beginPath();
context.rect(x1, y1, x2 - x1, y2 - y1);
context.fillStyle = 'rgba(255, 0, 0, 0.2)';
context.fill();
context.strokeStyle = '#FF0070';
context.lineWidth = 1;
context.stroke();
console.log('drawing', x2, y2);
};
const clearRect = (drawContext) => {
drawContext.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
};
useEffect(() => {
const canvasElem = canvasRef.current;
let x = 0; let y = 0;
let isDrawing = false;
const drawContext = canvasRef.current.getContext('2d');
let canvasRect;
const lastCursorPosition = {
x: 0,
y: 0,
};
const startDraw = (e) => {
console.log(e.type, 'start');
canvasRect = canvasRef.current.getBoundingClientRect();
x = e.clientX - canvasRect.left;
y = e.clientY - canvasRect.top;
if (x < 0) x = 0;
if (y < 0) y = 0;
isDrawing = true;
};
const drawingDeal = (e) => {
console.log(e.type, 'move');
if (isDrawing) {
const x1 = e.clientX - canvasRect.left;
const y1 = e.clientY - canvasRect.top;
clearRect(drawContext);
highlightInvalid(drawContext, x, y, x1, y1);
lastCursorPosition.x = x1;
lastCursorPosition.y = y1;
}
};
const drawingEnd = () => {
if (isDrawing) {
if (lastCursorPosition.x && lastCursorPosition.y) {
const width = lastCursorPosition.x - x + 1;
const height = lastCursorPosition.y - y + 1;
addInvalidLocation({ x, y, width, height });
lastCursorPosition.x = 0;
lastCursorPosition.y = 0;
}
clearRect(drawContext);
isDrawing = false;
x = 0;
y = 0;
}
};
canvasElem.addEventListener('mousedown', startDraw);
canvasElem.addEventListener('mousemove', drawingDeal);
canvasElem.addEventListener('mouseup', drawingEnd);
return () => {
canvasElem.removeEventListener('mousedown', startDraw);
canvasElem.removeEventListener('mousemove', drawingDeal);
canvasElem.removeEventListener('mouseup', drawingEnd);
};
}, [invalidLocations, addInvalidLocation]);在添加完標(biāo)注位置之后,模板中我們通過迭代返回絕對定位的div來實現(xiàn)已經(jīng)標(biāo)注過的矩形。
<div className="img-wrap">
<img src={lancome} ref={imgInstance} className="App-logo" alt="logo" />
<canvas className="canvas" ref={initCanvas} width="400px" height="400px" />
{invalidLocations && invalidLocations.map((location, index) => {
const { width, height, x, y } = location;
return <div
key={`${width}_${height}_${x}_${y}`}
tabIndex={-1}
className={'remark'}
style={{ width: `${width}px`, height: `${height}px`, left: `${x}px`, top: `${y}px` }}
></div>
})}
</div>最后效果:

到此這篇關(guān)于在React中用canvas對圖片標(biāo)注的實現(xiàn) 的文章就介紹到這了,更多相關(guān)React canvas對圖片標(biāo)注內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于React動態(tài)加載路由處理的相關(guān)問題
這篇文章主要介紹了關(guān)于React動態(tài)加載路由處理的相關(guān)問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01
React+Redux實現(xiàn)簡單的待辦事項列表ToDoList
這篇文章主要為大家詳細(xì)介紹了React+Redux實現(xiàn)簡單的待辦事項列表ToDoList,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09
React useEffect、useLayoutEffect底層機制及區(qū)別介紹
useEffect 是 React 中的一個 Hook,允許你在函數(shù)組件中執(zhí)行副作用操作,本文給大家介紹React useEffect、useLayoutEffect底層機制及區(qū)別介紹,感興趣的朋友一起看看吧2025-04-04
React中useEffect原理的代碼簡單實現(xiàn)詳解
React的useEffect鉤子是React函數(shù)組件中處理副作用,本文將通過一個簡單的例子解釋如何用代碼實現(xiàn)useEffect的基本原理,感興趣的小伙伴可以了解下2023-12-12
淺談React組件props默認(rèn)值的設(shè)置
本文主要介紹了淺談React組件props默認(rèn)值的設(shè)置,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04

