React.js Canvas API在Android端无法正常工作问题求助
Android浏览器/WebView中React Canvas绘图失效的解决方案
问题根源
- Web CSS不支持
dp单位,你在设置Canvas样式尺寸时用了dp,Android浏览器无法解析,导致绘图区域异常 - Android触摸场景下,
PointerEvent的offsetX/offsetY获取不准确,坐标计算错误 - 触摸绘图时未阻止页面滚动等默认行为,干扰事件触发
修复方案
1. 修正Canvas尺寸单位与适配逻辑
把dp换成Web支持的px,同时添加窗口resize监听,确保Canvas尺寸随时适配容器:
修改CanvasContext.js的prepareCanvas,并加上resize监听:
const prepareCanvas = () => { const canvas = canvasRef.current; if (!canvas) return; // 获取Canvas实际显示区域 const rect = canvas.getBoundingClientRect(); // 适配高清屏,避免绘图模糊 const dpr = window.devicePixelRatio || 1; canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; // 用px设置样式尺寸 canvas.style.width = `${rect.width}px`; canvas.style.height = `${rect.height}px`; const context = canvas.getContext("2d"); context.scale(dpr, dpr); context.lineCap = "round"; context.strokeStyle = "black"; context.lineWidth = 5; contextRef.current = context; }; // 监听窗口变化,更新Canvas尺寸 useEffect(() => { window.addEventListener('resize', prepareCanvas); prepareCanvas(); return () => window.removeEventListener('resize', prepareCanvas); }, []);
2. 修正触摸坐标计算逻辑
改用clientX/clientY结合Canvas的位置计算真实坐标,替代不可靠的offsetX/offsetY:
在CanvasContext.js中添加坐标计算函数,修改绘图事件:
// 计算触摸点在Canvas中的真实坐标 const getCanvasCoordinates = (event) => { const canvas = canvasRef.current; if (!canvas) return { x: 0, y: 0 }; const rect = canvas.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; return { x: (event.clientX - rect.left) * dpr, y: (event.clientY - rect.top) * dpr }; }; const startDrawing = ({ nativeEvent }) => { const { x, y } = getCanvasCoordinates(nativeEvent); contextRef.current.beginPath(); contextRef.current.moveTo(x, y); setIsDrawing(true); }; const draw = ({ nativeEvent }) => { if (!isDrawing) return; // 阻止页面滚动,避免干扰绘图 nativeEvent.preventDefault(); const { x, y } = getCanvasCoordinates(nativeEvent); contextRef.current.lineTo(x, y); contextRef.current.stroke(); };
3. 禁止浏览器触摸默认行为
在Canvas元素上添加touchAction:none,直接禁止浏览器的触摸滚动、缩放等行为:
修改Canvas.js的canvas组件:
return ( <canvas style={{backgroundColor: "white", touchAction: "none", width: "100%", height: "100%"}} onPointerDown={startDrawing} onPointerUp={finishDrawing} onPointerMove={draw} ref={canvasRef} /> );
4. 确保Modal中Canvas容器尺寸正确
调整DrawingCanvas.js的Modal.Body样式,确保Canvas能占满可用空间:
<Modal.Body style={{margin: 0, height: "calc(100vh - 120px)", overflow: "hidden", padding: 0}}> <Canvas/> <ClearCanvasButton/> </Modal.Body>
修改后的完整关键代码
CanvasContext.js
import React, { useContext, useRef, useState, useEffect } from "react"; const CanvasContext = React.createContext(); export const CanvasProvider = ({ children }) => { const [isDrawing, setIsDrawing] = useState(false) const canvasRef = useRef(null); const contextRef = useRef(null); const prepareCanvas = () => { const canvas = canvasRef.current; if (!canvas) return; const rect = canvas.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; canvas.style.width = `${rect.width}px`; canvas.style.height = `${rect.height}px`; const context = canvas.getContext("2d") context.scale(dpr, dpr); context.lineCap = "round"; context.strokeStyle = "black"; context.lineWidth = 5; contextRef.current = context; }; useEffect(() => { window.addEventListener('resize', prepareCanvas); prepareCanvas(); return () => window.removeEventListener('resize', prepareCanvas); }, []); const getCanvasCoordinates = (event) => { const canvas = canvasRef.current; if (!canvas) return { x: 0, y: 0 }; const rect = canvas.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; return { x: (event.clientX - rect.left) * dpr, y: (event.clientY - rect.top) * dpr }; }; const startDrawing = ({ nativeEvent }) => { const { x, y } = getCanvasCoordinates(nativeEvent); contextRef.current.beginPath(); contextRef.current.moveTo(x, y); setIsDrawing(true); }; const finishDrawing = () => { contextRef.current.closePath(); setIsDrawing(false); }; const draw = ({ nativeEvent }) => { if (!isDrawing) { return; } nativeEvent.preventDefault(); const { x, y } = getCanvasCoordinates(nativeEvent); contextRef.current.lineTo(x, y); contextRef.current.stroke(); }; const clearCanvas = () => { const canvas = canvasRef.current; const context = canvas.getContext("2d") context.fillStyle = "white" context.fillRect(0, 0, canvas.width, canvas.height) } return ( <CanvasContext.Provider value={{ canvasRef, contextRef, prepareCanvas, startDrawing, finishDrawing, clearCanvas, draw, }} > {children} </CanvasContext.Provider> ); }; export const useCanvas = () => useContext(CanvasContext);
Canvas.js
import React from "react"; import { useCanvas } from "./CanvasContext"; export function Canvas() { const { canvasRef, startDrawing, finishDrawing, draw, } = useCanvas(); return ( <canvas style={{backgroundColor: "white", touchAction: "none", width: "100%", height: "100%"}} onPointerDown={startDrawing} onPointerUp={finishDrawing} onPointerMove={draw} ref={canvasRef} /> ); }
验证建议
- 在Android设备的Chrome、默认浏览器和WebView中分别测试绘图功能
- 调整窗口大小,确认Canvas能自动适配
- 测试触摸绘图时是否还会出现页面滚动干扰
内容的提问来源于stack exchange,提问作者Crocker
相关产品推荐
相关产品推荐

