Search code examples
vue.jsvue-componentvuejs3

Vue project - Uncaught TypeError: Cannot read properties of null (reading 'offsetWidth') error for a custom js script


so I'm converting a custom html template to a vuejs project. I've imported all css and js files to be used in my homepage. Css files are loading fine. For JS files, the theme came with a few custom js scripts along with usual libraries like three.js etc. I'm importing all of them below my HomeComponent in the script tag. But I run into this error:

"Uncaught TypeError: Cannot read properties of null (reading 'offsetWidth')
    at eval (demo3.js?dad6:2:20)
    at ./src/assets/assets/js/demo3.js (app.js:92:1)
    at __webpack_require__ (app.js:1124:33)
    at fn (app.js:1357:21)
    at eval (index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/HomeComponent.vue?vue&type=script&lang=js:26:85)
    at ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/HomeComponent.vue?vue&type=script&lang=js (app.js:30:1)
    at __webpack_require__ (app.js:1124:33)
    at fn (app.js:1357:21)
    at eval (HomeComponent.vue?vue&type=script&lang=js:5:216)
    at ./src/components/HomeComponent.vue?vue&type=script&lang=js (app.js:296:1)"

Below is my App.vue code:

<template>
  <router-view />
</template>
<script>
export default {
  name: "App",
  components: {},
};
</script>

Below is my homecomponent.vue code (partial - some code hidden from inside template):

<template>
  <header class="offcanvas-menu">
    <input type="checkbox" id="toogle-menu" />

    <label for="toogle-menu" class="toogle-open"><span></span></label>

    <nav>
      <div>
        <label for="toogle-menu" class="toogle-close">
          <span></span>
        </label>
      </div>
      <ul>
        <li><a href="#section1">Section </a></li>
        <li><a href="#section2">Section </a></li>
        <li><a href="#section3">Section </a></li>
        <li><a href="#section4">Section </a></li>
        <li><a href="#section5">Section </a></li>
      </ul>
    </nav>
  </header>
  <main>
    <div class="content">
      <canvas class="scene scene--full" id="scene"></canvas>
      <div class="content__inner">
        <h2 class="content__title">مؤشر المعلوماتية</h2>
        <div class="content-button mt-2">
          <button
            type="button"
            class="btn btn-relief-primary btn-large menu-trigger display-5"
          >
            <i data-feather="menu"></i> القائمة
          </button>
        </div>
      </div>
    </div>

    <nav class="grim">
      <div class="grim__item">
        <div class="grim__item-bg grim__item-bg--5"></div>
      </div>
      <div class="grim__item">
        <div class="grim__item-bg grim__item-bg--5"></div>
      </div>
      <div class="grim__item">
        <div class="grim__item-bg grim__item-bg--5"></div>
        <div class="grim__item-content">
          <div class="grim__item-inner"></div>
        </div>
      </div>
  ..
   
    
      <div class="grim__item">
        <div class="grim__item-bg grim__item-bg--9"></div>
        <div class="grim__item-img grim__item-img--4"></div>
        <a href="dashboard-region.html" class="grim__link grim__item-content">
          <div class="grim__item-inner">
            <h3 class="grim__item-title">test</h3>
            <span class="grim__item-desc">test</span>
          </div>
        </a>
        <div class="grim__item-bg grim__item-bg-cover grim__item-bg--9"></div>
      </div>
      <div class="grim__item">
        <div class="grim__item-bg grim__item-bg--10"></div>
        <div class="grim__item-img grim__item-img--5" style=""></div>
        <a href="wizard.html" class="grim__link grim__item-content">
          <div class="grim__item-inner">
            <h3 class="grim__item-title">test</h3>
            <span class="grim__item-desc"
              >test</span
            >
          </div>
        </a>
        <div class="grim__item-bg grim__item-bg-cover grim__item-bg--10"></div>
      </div>
    </nav>
  </main>
  <!-- home -->
</template>
<style>
@import url("https://fonts.googleapis.com/css?family=Barlow:400,500,700|Poppins:600");
@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,400;0,500;0,600;1,400;1,500;1,600");
</style>
<script>
//All css files
import "../assets/assets/css/base.css";

import "../assets/app-assets/css-rtl/bootstrap.css";
import "../assets/app-assets/css-rtl/bootstrap-extended.css";
import "../assets/app-assets/css-rtl/colors.css";
import "../assets/app-assets/css-rtl/themes/dark-layout.css";
import "../assets/assets/css/menu.css";

import "../assets/app-assets/css-rtl/custom-rtl.css";
import "../assets/assets/css/style-rtl.css";

export default {
  name: "HomeComponent",
};

//All JS files
import "../assets/assets/js/demo.js";
import "../assets/assets/js/three.min.js";
import "../assets/assets/js/perlin.js";
import "../assets/assets/js/TweenMax.min.js";
import "../assets/assets/js/demo3.js";
import "../assets/assets/js/grid.js";
import "../assets/assets/js/imagesloaded.pkgd.min.js";
import "../assets/assets/js/anime.min.js";
import "../assets/assets/js/menu2.js";
</script>

The issue is with the demo3.js file.

Demo3.js

var canvas = document.querySelector("#scene");
var width = canvas.offsetWidth,
  height = canvas.offsetHeight;

var renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
});
renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);
renderer.setSize(width, height);
renderer.setClearColor(0x161d31);

var scene = new THREE.Scene();

var camera = new THREE.PerspectiveCamera(100, width / height, 0.1, 10000);
camera.position.set(120, 0, 300);

var light = new THREE.HemisphereLight(0xffffff, 0x0c056d, 0.6);
scene.add(light);

var light = new THREE.DirectionalLight(0x590d82, 0.5);
light.position.set(100, 300, 400);
scene.add(light);
var light2 = light.clone();
light2.position.set(-100, 300, 400);
scene.add(light2);

var geometry = new THREE.IcosahedronGeometry(120, 4);
for (var i = 0; i < geometry.vertices.length; i++) {
  var vector = geometry.vertices[i];
  vector._o = vector.clone();
}
var material = new THREE.MeshPhongMaterial({
  emissive: 0x23f660,
  emissiveIntensity: 0.4,
  shininess: 0,
});
var shape = new THREE.Mesh(geometry, material);
scene.add(shape);

function updateVertices(a) {
  for (var i = 0; i < geometry.vertices.length; i++) {
    var vector = geometry.vertices[i];
    vector.copy(vector._o);
    var perlin = noise.simplex3(
      vector.x * 0.006 + a * 0.0002,
      vector.y * 0.006 + a * 0.0003,
      vector.z * 0.006
    );
    var ratio = perlin * 0.4 * (mouse.y + 0.3) + 0.9;
    vector.multiplyScalar(ratio);
  }
  geometry.verticesNeedUpdate = true;
}

function render(a) {
  requestAnimationFrame(render);
  updateVertices(a);
  renderer.render(scene, camera);
}

function onResize() {
  canvas.style.width = "";
  canvas.style.height = "";
  width = canvas.offsetWidth;
  height = canvas.offsetHeight;
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  renderer.setSize(width, height);
}

var mouse = new THREE.Vector2(0.8, 0.5);
function onMouseMove(e) {
  TweenMax.to(mouse, 0.8, {
    y: e.clientY / height,
    x: e.clientX / width,
    ease: Power1.easeOut,
  });
}

requestAnimationFrame(render);
window.addEventListener("mousemove", onMouseMove);
var resizeTm;
window.addEventListener("resize", function () {
  resizeTm = clearTimeout(resizeTm);
  resizeTm = setTimeout(onResize, 200);
});

So, my app is not loading at all. now I'm lost how to resolve this. This is my first time converting html template to vuejs. Idk what to expect. Screenshot of file scructure (https://i.sstatic.net/dKzTb.png)

screenshot of error details in console.log (https://i.sstatic.net/9Rzap.png)


Solution

  • You are importing your demo3.js file (and thus executing it) before the app rendered the DOM. So the element #scene doesn't exist yet, resulting in being null.

    Several solutions for this:

    1. You import dynamically your demo3.js file after mounted hook:
    mounted() {
      import('../assets/assets/js/demo3.js') // dynamic import
    }
    
    1. You wrap your demo3.js code inside a function and export it.
    // demo3.js
    
    export function renderScene() {
      var canvas = document.querySelector("#scene");
      var width = canvas.offsetWidth,
        height = canvas.offsetHeight;
      // [...]
    }
    
    import { renderScene } from '../assets/assets/js/demo3.js'
    
    export default {
      mounted() {
        renderScene()
      }
    }
    
    1. You move your demo3.js code to a vue component, and use template refs.
    mounted() {
      const scene = this.$refs.scene
      if (scene) {
        const width = canvas.offsetWidth,
          height = canvas.offsetHeight;
    
        const renderer = new THREE.WebGLRenderer({
          canvas: canvas,
          antialias: true,
        });
        // [...]
      }
    }