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

React Three Fiber中Canvas网格线条适配尺寸问题排查

解决React-Three-Fiber中网格适配Canvas尺寸的留白问题

问题背景

在React项目中基于react-three-fiber(r3f)搭建流场,使用drei库的Line组件绘制网格作为可视化参考。参考原生Canvas网格代码适配r3f坐标系后,网格曾出现偏移、不完整问题;调整坐标后位置恢复正确,但最后一行/列仍存在留白,需要实现完全适配Canvas尺寸、支持屏幕缩放的完整网格。

原生Canvas原网格代码

drawGrid(context){
    for (let r =0; r<this.rows; r++){
        context.beginPath();
        context.moveTo(0,this.cellSize * r);
        context.lineTo(this.width, this.cellSize * r);
        context.stroke();
    }

    for (let c =0; c<this.cols; c++){
        context.beginPath();
        context.moveTo(this.cellSize * c,0);
        context.lineTo(this.cellSize * c,this.height);
        context.stroke();
    }
}

原GridModel组件代码

import { useThree } from "react-three-fiber";
import { Line } from "@react-three/drei";
import { Vector3 } from "three";
import React from "react";

const GridModel = () => {
  const { size } = useThree();
  const cellSize = 20;
  const rows = Math.floor(size.height / cellSize);
  const cols = Math.floor(size.width / cellSize);

  const rowStart = -(size.height / 2);
  const rowEnd = (size.height / 2);
  const colStart = -(size.width / 2);
  const colEnd = size.width / 2;

  const grid = [];

  for (let i = 0; i < rows; i++) {
    let startPoint = new Vector3(rowStart, i * cellSize - colEnd, 0);
    let endPoint = new Vector3(rowEnd, i * cellSize - colEnd, 0);

    if(i==0){
      console.log(" first row startPoint: " + startPoint.x + ", " + startPoint.y + ", " + startPoint.z + " endPoint: " + endPoint.x + ", " + endPoint.y + ", " + endPoint.z);
    }

    grid.push(<Line points={[startPoint, endPoint]} color="black" />);
  }

  for(let i = 0; i < cols; i++) {
    let startPoint = new Vector3(i * cellSize + rowStart, colStart, 0);
    let endPoint = new Vector3(i * cellSize + rowStart, colEnd, 0);

    if(i==0){
      console.log(" first col startPoint: " + startPoint.x + ", " + startPoint.y + ", " + startPoint.z + " endPoint: " + endPoint.x + ", " + endPoint.y + ", " + endPoint.z);
    }
    grid.push(<Line points={[startPoint, endPoint]} color="black" />);
  }

  return (
    <mesh>
      {grid}
    </mesh>
  );
};

export default GridModel;

相关页面组件代码

BackgroundViewerPage.js

import React from 'react';
import { Canvas } from '@react-three/fiber';
import BackgroundViewerBackground from '../backgrounds/BackgroundViewerBackground';
import GridModel from '../three/models/GridModel';
import FixedOrthographicCamera from '../three/cameras/FixedOrthographicCamera';
import FixedPerspectiveCamera from '../three/cameras/FixedPerspectiveCamera';
import { OrbitControls, OrthographicCamera } from '@react-three/drei';

import '../../styles/BackgroundViewerPage.css';
const BackgroundViewerPage = () => {
  return (
    <div className='background-viewer-page-container'>
    <Canvas 
      dpr={window.devicePixelRatio}
    >
      <OrbitControls />
      <FixedOrthographicCamera />
      <BackgroundViewerBackground />
      <GridModel />
    </Canvas>
    </div>
  );
};

export default BackgroundViewerPage;

BackgroundViewerBackground.js

import React, { useRef } from "react";
import { useThree, useFrame } from "@react-three/fiber";
import * as THREE from "three";

const BackgroundViewerBackground = () => {
  const { size } = useThree();

  return (
    <mesh>
        <planeGeometry args={[size.width, size.height]} />
        <meshBasicMaterial color="white" side={THREE.DoubleSide}/>
    </mesh>
  );
};

export default BackgroundViewerBackground;

解决方案

问题根源

  1. 原代码使用Math.floor()计算行数/列数,会截断画布尺寸与单元格尺寸的余数,导致最后一行/列到画布边缘存在空白
  2. 坐标计算逻辑混淆了行/列相关变量,虽调整后位置正确,但行数/列数不足的问题未解决

修复后的GridModel组件

import { useThree } from "react-three-fiber";
import { Line } from "@react-three/drei";
import { Vector3 } from "three";
import React from "react";

const GridModel = () => {
  const { size } = useThree();
  const cellSize = 20;
  
  // 向上取整确保覆盖整个画布,避免留白
  const rows = Math.ceil(size.height / cellSize);
  const cols = Math.ceil(size.width / cellSize);

  const halfHeight = size.height / 2;
  const halfWidth = size.width / 2;

  const grid = [];

  // 绘制水平线(行)
  for (let i = 0; i <= rows; i++) {
    // 从画布底部开始,逐行向上绘制,最后一行对齐顶部边缘
    const yPos = -halfHeight + i * cellSize;
    const startPoint = new Vector3(-halfWidth, yPos, 0);
    const endPoint = new Vector3(halfWidth, yPos, 0);
    grid.push(<Line points={[startPoint, endPoint]} color="black" key={`row-${i}`} />);
  }

  // 绘制垂直线(列)
  for (let i = 0; i <= cols; i++) {
    // 从画布左侧开始,逐列向右绘制,最后一列对齐右侧边缘
    const xPos = -halfWidth + i * cellSize;
    const startPoint = new Vector3(xPos, -halfHeight, 0);
    const endPoint = new Vector3(xPos, halfHeight, 0);
    grid.push(<Line points={[startPoint, endPoint]} color="black" key={`col-${i}`} />);
  }

  return (
    <group>
      {grid}
    </group>
  );
};

export default GridModel;

关键修复点说明

  • 行数/列数计算:用Math.ceil()替代Math.floor(),确保即使画布尺寸不是单元格尺寸的整数倍,也能生成足够的线条覆盖边缘
  • 循环范围:循环从0到rows/cols(包含),让最后一条线刚好落在画布边缘
  • 坐标逻辑简化:直接基于画布中心偏移计算,避免原代码中变量混淆的问题
  • 添加唯一key:给每个Line组件添加唯一key,符合React列表渲染规范

额外优化建议

  • 可将cellSize改为props传入,支持动态调整网格密度
  • 若需渲染超大网格,建议使用BufferGeometry批量绘制线条,提升性能

内容的提问来源于stack exchange,提问作者Alex Kahn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 01:25:01