You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.js Canvas API在Android端无法正常工作问题求助

Android浏览器/WebView中React Canvas绘图失效的解决方案

问题根源

  1. Web CSS不支持dp单位,你在设置Canvas样式尺寸时用了dp,Android浏览器无法解析,导致绘图区域异常
  2. Android触摸场景下,PointerEvent的offsetX/offsetY获取不准确,坐标计算错误
  3. 触摸绘图时未阻止页面滚动等默认行为,干扰事件触发

修复方案

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 11:37:02