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;
解决方案
问题根源
- 原代码使用
Math.floor()计算行数/列数,会截断画布尺寸与单元格尺寸的余数,导致最后一行/列到画布边缘存在空白 - 坐标计算逻辑混淆了行/列相关变量,虽调整后位置正确,但行数/列数不足的问题未解决
修复后的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
相关产品推荐
相关产品推荐

