Troubleshooting: ES6 Import causing "not a constructor" error in Android Webview

Incorporating an HTML webpage into a WebView for rendering a 3D model using three.js has been successful so far. The next step involves adding touch/drag controls to manipulate the camera's view. Following example code, I have implemented the relevant sections as shown below:

import * as tc from './TrackballControls.js';
//....
controls = new tc.TrackballControls( camera , renderer.domElement);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;

The functionality works perfectly on Chrome and Firefox browsers on PC, and also when loaded on Android devices through the local network. However, encountering an error in the WebView within my application:

Uncaught TypeError: tc.TrackballControls is not a constructor

The issue seems to arise specifically within the WebView environment, while functioning seamlessly on the same phone's Chrome browser. Looking at the directory structure of my HTML app:

https://i.sstatic.net/Dr04X.png

I have made adjustments in the code to account for the location of 'three.module.js' and 'TrackballControls.js', detailed below:

// in index.html js:
import * as THREE from './three.module.js';
import * as tc from './TrackballControls.js';    

// in TrackballControls.js:
import {
  EventDispatcher,
  MOUSE,
  Quaternion,
  Vector2,
  Vector3
} from "./three.module.js";

Is there any modification that can be made in the HTML/JavaScript or Android Java code to rectify this issue within the WebView? The userAgent strings for two different devices where I tested are:

"Mozilla/5.0 (Linux; Android 6.0.1; S60 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.93 Mobile Safari/537.36", source: https://appassets.androidplatform.net/assets/www/cpb_3d_model_wgt/index.html (27)

and

"Mozilla/5.0 (Linux; Android 8.1.0; LM-V405 Build/OPM1.171019.026; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.93 Mobile Safari/537.36", source: https://appassets.androidplatform.net/assets/www/cpb_3d_model_wgt/index.html (27)

EDIT Further details: Initially serving the page from the Assets directory using the provided method led to the 'not a constructor' error. Using Chrome remote device inspector, I found discrepancies in the network request headers between requests made via PC and Android devices. This observation prompted me to serve the page using NanoHTTPD which resolved the issue.

Another EDIT: Upon revisiting the Asset Loader/intercept request code, it became apparent that I mistakenly hardcoded the wrong JavaScript file to be returned for all '.js' requests, hence triggering the 'not a constructor error'. Apologies for overlooking this detail earlier.

Answer №1

TrackballControls.js was not designed as a module for direct import. Instead, it assumes the global availability of the THREE variable and adds itself to THREE. To resolve this issue, you may need to manually copy and paste its source code into your own file (let's name it TC.js) and make some modifications at the beginning and end:

TC.js:

// 1. Import THREE instead of assuming it's globally available
import * as THREE from './three.module.js';

// 2. Remove "THREE." prefix and make it an independent variable
var TrackballControls = function (object, domElement) {

    // ... All 600+ lines of internal content remain the same

};

// 2. Remove "THREE." at the end of the file as well
TrackballControls.prototype = Object.create(THREE.EventDispatcher.prototype);
TrackballControls.prototype.constructor = TrackballControls;

// 3. Export the object for use in other files
export default TrackballControls;

Now you can utilize your custom TC.js file in your project:

main.js:

import * as THREE from './three.module.js';
import TrackballControls from 'TC.js';

const controls = new TrackballControls(camera, renderer.domElement);

I have provided a sample below to exhibit how copying and pasting TrackballControls can still function by converting it into a separate variable. The top 600+ lines are essentially copied with the specified changes. The remainder of the scene setup is located towards the end of the JavaScript code block:

// In your code, you would import THREE here, rather than through a <script> tag
// import * as THREE from 'three';

const TrackballControls = function (object, domElement) {

if (domElement === undefined) console.warn('THREE.TrackballControls: The second parameter "domElement" is now mandatory.');
if (domElement === document) console.error('THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.');

var _this = this;
var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };

// More internal functionalities follow...

};

// Additional functions and event listeners continue...

//////////////////////////// END OF TRACKBALL SOURCE CODE ////////////////////////////

// Essential Three setup
const renderer = new THREE.WebGLRenderer({ canvas: document.querySelector("canvas") });
const camera = new THREE.PerspectiveCamera(70, 1, 1, 1000);
camera.position.z = 20;

const scene = new THREE.Scene();
const geometry = new THREE.TorusBufferGeometry(8, 3, 16, 32);
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// Utilizing the controls appropriately
const controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;

function resize() {
  var width = renderer.domElement.clientWidth;
  var height = renderer.domElement.clientHeight;
  if (renderer.domElement.width !== width || renderer.domElement.height !== height) {
    renderer.setSize(width, height, false);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function animate(time) {
  controls.update();
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

resize();
requestAnimationFrame(animate);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

Answer №2

It appears that <code>TrackballControls.js
is not structured as an ES6 module and therefore does not export the TrackballControls constructor function. Instead, the TrackballControls functionality is attached to the THREE object. Based on the information provided, it seems that the ts object may be empty, rendering ts.TrackballControls as undefined. To properly define controls, consider using the following syntax:

controls = new THREE.TrackballControls( camera , renderer.domElement);

This solution is derived from the code found in this TrackballControls.js file on Github.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Calculating the total number of days in each month within a specified date range using PHP in a dynamic way

Thank you for taking the time to help me with my issue. For instance, if a user selects a date range from 10 Dec 2016 to 20 April, how can I calculate the number of days for each month within that range? For example, Dec=22 Days, Jan=31 Days, Feb=28 Days, ...

Are you in the business of building JavaScript hubs?

I have a unique setup where my express server is in charge of handling all routing and session functionalities. I've envisioned a system where logged-in users can connect to distinct "hubs" based on the location of each hub. My idea was to treat each ...

Optimizing the way JavaScript is incorporated into Django templates for maximum efficiency

Before, I followed this structure in a template <html> ... <script> {% include "myapp/includes/jquery-1.7.1.min.js" %} {% include "myapp/includes/myscript.js" %} </script> ... However, this resulted in all the JavaScript code being vis ...

Tips for populating an array with Firestore Objects

I am currently working on developing an e-book application using Firebase and Firestore. This is how the database structure looks: books Book1 -title:abc -category:123 -description:123 Book2 ...

What is the best way to pass a dynamically updated value to the controller?

<form id="participantsForGd"> <span id="GroupID"></span> //example: group id 2 will be displayed here <button id="GdStartTest">Start test</button> </form> I need to pass the value of GroupID to the controller through an ...

How to dynamically populate a Vue multiple select dropdown with v-for loop?

I have been attempting to implement multi-select questions in Vue using v-for. The Select menu and its options are populated via JSON data. Unfortunately, I am facing difficulty in retrieving the selected results as expected. Whenever I select an option ...

Creating a mask overlay that covers the entire screen while excluding a specific sibling div

My goal is to create a div that acts as a mask covering the entire window. It should have an opacity of 0.5 and a background color of lightgray. However, there is one particular div on the screen that I do not want to be affected by the mask. I attempted t ...

What is the most effective method for displaying an error code when a JavaScript error occurs?

I'm currently dealing with a library that is throwing errors: throw new Error('The connection timed out waiting for a response') This library has the potential to throw errors for various reasons, making it challenging for users to handle ...

What steps can I take to display lines in a textarea so that it closely resembles the appearance of a notepad document

Is there a way to display lines in a text-area to give it the appearance of a notepad? I only have one text-area available. See the example notepad below for reference. ...

Implement a vertical scrolling animation in datatables

I am trying to utilize datatables to display all data. My goal is to have the data automatically scroll row by row every 3 seconds. This is my code, and you can also check it out on jsfiddle The intention is to showcase this data on a large screen. < ...

Tutorial on implementing a _variables.scss file for Vue components with sass-resource-loader on Vue CLI 3.04

In my VueJs project created with the Vue CLI 3.0.4, I am aiming to utilize SCSS variables across all components without the need to import _variables.scss into each one individually. After some research, I found that I can achieve this using sass-resource- ...

Determining the rotation direction following an object collision using three.js

In my three.js demo, collision detection is achieved using a Raycaster that extends from the front of the camera. The movement of the camera is restricted to follow its facing direction, although the mouse controls allow for steering in different direction ...

Using Angular 6's httpClient to securely post data with credentials

I am currently working with a piece of code that is responsible for posting data in order to create a new data record. This code resides within a service: Take a look at the snippet below: import { Injectable } from '@angular/core'; import { H ...

What is the best way to prevent labels from floating to the top when in focus?

How can I prevent the label from floating on top when focusing on a date picker using Material UI? I have tried several methods but nothing seems to work. I attempted using InputLabelProps={{ shrink: false }} but it did not resolve the issue. Here is a li ...

Callback in React Setstate triggered, leading to a delay in rendering

Recently, I embarked on a journey to learn React just 2 days ago. Despite my enthusiasm, I have encountered some challenges with React's setState method. As far as my understanding goes, I should utilize the prevState parameter when I need to alter th ...

Information vanishes as the element undergoes modifications

I am currently working with a JSON file that contains information about various events, which I am displaying on a calendar. Whenever an event is scheduled for a particular day, I dynamically add a div element to indicate the presence of an event on the c ...

Determining the adjustment for HTML5 video playback

I am currently implementing a feature that involves tracing a point in an HTML5 video using a canvas overlay. The canvas sits on top of the video tag and is styled as follows: #my-canvas { width: 100%; height: 100%; position:absolute !important; z-index:1 ...

How to utilize dot notation in HTML to iterate through nested JSON in AngularJS?

I'm struggling with displaying nested objects loaded from a JSON file in Angular. I've seen examples of using dot notations in HTML to access nested data, but I'm new to Angular and can't seem to get it right. The JSON is valid, but I j ...

How to delete the final character from a file stream using node.js and the fs module

My current project involves using node.js to create an array of objects and save them to a file, utilizing the fs library. Initially, I set up the write stream with var file = fs.createWriteStream('arrayOfObjects.json'); and wrote an opening brac ...

Adjusting the height of the Tinymce Editor in React Grid Layout after the initial initialization

I am facing a challenge with my React Component that displays a tinymce editor. The task is to dynamically adjust the height of the editor after it has been initialized. To achieve this, I am utilizing the "React Grid Layout" package for resizing the compo ...