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

在React项目中运行PIXI.js脚本时遇appendChild空引用错误求助

解决React中PIXI.js的Cannot read property 'appendChild' of null错误

嘿,我一眼就看出问题所在了——你是在DOM元素还没被React渲染到页面上的时候,就尝试去操作它,这才导致了Cannot read property 'appendChild' of null的错误。咱们一步步拆解问题,然后修复它:

错误根源分析

  1. 你在组件类外部定义了var playground = document.getElementById('pxrender');,这时候React还没开始渲染组件,页面上根本不存在pxrender这个div,所以playground的值是null。
  2. 你在render方法里直接调用了setScene,而render执行时组件的DOM还没完成挂载,这时候去操作DOM节点肯定会失败。

修复步骤

下面是调整后的代码,我标注了关键修改点:

import React, { Component } from 'react';
import './App.css';
import image from './image.png';
import * as PIXI from 'pixi.js'

class App extends Component {
  state = { playground: null }
  // 把全局变量移到类内部,避免全局污染
  width = window.innerWidth;
  height = window.innerHeight;
  count = 0;
  raf = null;
  renderer = null;
  tp = null;
  preview = null;
  displacementSprite = null;
  displacementFilter = null;
  stage = null;

  componentDidMount() {
    // 组件挂载完成后,再获取ref并初始化场景
    const playground = this.refs.pxrender;
    this.setState({ playground });
    this.setScene(image, playground);
  }

  componentWillUnmount() {
    // 组件卸载时清理资源,防止内存泄漏
    this.removeScene();
  }

  setScene = (url, playground) => {
    // 初始化renderer
    this.renderer = PIXI.autoDetectRenderer(this.width, this.height, { transparent: true });
    this.renderer.autoResize = true;
    // 现在playground是有效的DOM节点,可以安全append了
    playground.appendChild(this.renderer.view);

    this.stage = new PIXI.Container();
    this.tp = PIXI.Texture.fromImage(url);
    this.preview = new PIXI.Sprite(this.tp);
    this.preview.anchor.x = 0;

    this.displacementSprite = PIXI.Sprite.fromImage('https://res.cloudinary.com/dvxikybyi/image/upload/v1486634113/2yYayZk_vqsyzx.png');
    this.displacementSprite.texture.baseTexture.wrapMode = PIXI.WRAP_MODES.REPEAT;
    this.displacementFilter = new PIXI.filters.DisplacementFilter(this.displacementSprite);
    this.displacementSprite.scale.y = 0.6;
    this.displacementSprite.scale.x = 0.6;

    this.stage.addChild(this.displacementSprite);
    this.stage.addChild(this.preview);
    this.animate();
  }

  removeScene = () => {
    if (this.raf) {
      cancelAnimationFrame(this.raf);
    }
    if (this.stage) {
      this.stage.removeChildren();
      this.stage.destroy(true);
    }
    if (this.state.playground && this.renderer?.view) {
      this.state.playground.removeChild(this.renderer.view);
    }
    // 重置所有变量,释放内存
    this.tp = null;
    this.preview = null;
    this.displacementSprite = null;
    this.displacementFilter = null;
    this.stage = null;
    this.renderer = null;
  }

  animate = () => {
    this.raf = requestAnimationFrame(this.animate);
    this.displacementSprite.x = this.count * 10;
    this.displacementSprite.y = this.count * 10;
    this.count += 0.05;
    this.stage.filters = [this.displacementFilter];
    this.renderer.render(this.stage);
  }

  render() {
    // render方法只负责返回JSX,不要在这里执行DOM操作或初始化逻辑
    return (
      <div ref="pxrender" id="pxrender"></div>
    );
  }
}

export default App;

额外的React最佳实践提醒

  • 尽量避免使用document.getElementById,React的ref是更安全的DOM获取方式(另外,字符串ref虽然能用,但官方更推荐使用createRef或者函数式ref,你可以后续改成const playgroundRef = React.createRef();然后在JSX里写ref={playgroundRef})。
  • 所有需要操作DOM的逻辑,都要放在componentDidMount(类组件)或者useEffect(函数组件)里,确保DOM已经挂载完成。
  • 组件卸载时一定要清理动画帧、PIXI的场景和资源,不然会造成内存泄漏,影响应用性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:15:02