Possible to write offline 3js after version 128?

I have written several 3js physics simulations that can run offline in a single file (they can be downloaded from kirbyx.com: most are plain js, and about five of them are 3js), which is great for high schools with very restrictive IT depts. I’ve been writing them in version 128… ‘modules’ arrived in later versions. I can’t find a way to write 3js that’ll run offline in more recent releases. Is there any way to do it? Thank you.

Yeah totally. You just have to set up an “importmap” in your html to point to the version of threejs you want to use online (or locally).
Then you include your js as a script tag with type=“module”
and in there, import *from three as THREE;

<head>
        <script type='importmap'>
            {
                "imports": {
                    "three": "https://cdn.jsdelivr.net/npm/three@0.164/build/three.module.js",
                    "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.164/examples/jsm/"
                }
    }</script>
    </head>
    <body>
        <script type="module" src='./test.js'></script>
//or inline..

        <script type="module">
import *as THREE from "three"
import {OrbitControls} from "three/addons/controls/OrbitControls.js"

//your threejs code here
    </script>

    </body>

You can also pull the threejs code into your directory

2 Likes

Wow!… Thank you for such a speedy response!… In the advice that you give, the 3js code seems to be imported from a server (local or out there somewhere). My 3js simulations run offline… no internet connection, and no local server set up on the users computer. Is it still possible to create similar standalone offline 3js simulations using the latest version of 3js?

It still relies on the files being hosted… somewhere.
If you have the threejs library code in the same directory as your html, it might work.

Even before modules, you would still have to have the library physically present on your filesystem where the html can access it.

The mechanism I showed just shows how to redirect the required references. If you set up the “three” importmap to point to wherever your three.module.js is, it .. should work?

Edit: try downloading this .html and doubleclicking it..

city_delaunay_voronoi_relax.html (15.6 KB)

(For me, even just clicking the link just works immediately.)

if it works on your local machine.. you should be set. It does import three from cdn, but that could just as well be a folder in the same directory as the html, as long as you update the importmap to point to it.. so it would be something like:

“three”: “./myThreejsInstall/build/three.module.js”

I’ve read that using modules just isn’t possible with the file:// protocol. When I try to do it myself, my browser throws a “Cross-Origin Request Blocked” error.

Out of curiosity, I did a quick test and found out that using modern three.js without modules is actually fairly easy. After downloading the ZIP file from the website, you can grab the three.core.js file from the build folder and simply comment out the last line which starts with “export” and causes an error.

// export { ACESFilmicToneMapping, AddEquation, AddOperation...

Then, you can use a normal script tag to include it:

<script src="three.core.js"></script>
<script>

const obj = new Object3D();
console.log(obj); // Object { isObject3D: true, uuid: "8ddfe8e3-7f93-4f95-a7bd-1b6a6f6c6f16", name: ""...

</script>

This was tested with r179. It’s likely that many things have changed since the move to modules, so it may take some work to update your code to be compatible with the latest version.

You can edit three.module.js as follows: wrap whole code in {…} to hide auxiliary variables and constants from global scope, and replace export { .. } with window.THREE = {..}

And then you can use three.module.js in classic way

1 Like

Thank you, Manthrax, BrokenRock and Trueshko…. I will spend the next couple of days following your suggestions (hopefully far less!)…

1 Like

Hi BrokenRock…

Your approach works… I included three.core.js in the local folder, followed your advice, switched the internet off, and it worked!… Thank you. however (there’s always a however…)…

All of my physics simulations (kirbyx.com) are contained in one html file so that they can be used without access to the internet, no IT instructions needed for the user, and that can be used on ipad, phones etc. Most of my simulations are 2D; for the four or five 3D ones I was able to do this with 3js up to version 128.

So the question: how do I include the three.core.js contents into the ‘master’ file? (I’ve already successfully done so with the ‘addons’).

Thank you for letting me mine your brain.

Martin

I run and test my programs locally by using GitHub to hold files that I need to load.

1 Like

And I’ll add to this.. it’s like 2 clicks to enable Github Pages to serve a static html app.. then it’s just automatically in sync with your latest commits/release branch. I’ve ditched my web host and converted my entire personal experiments portfolio to this setup, and it’s been great. https://manthrax.github.io/

3 Likes

To make sure I wasn’t leading you down the wrong path, I wrote a more complete test to make sure it worked. It turns out many things are also defined in the module file, so it’s a bit more difficult than I thought.

You will have to copy the entire contents of three.core.js and three.module.js into your master file and combine them. I’ve attached a file with the combined code.

three.no-module.js (2.0 MB)

You can copy the code from the file into your script tag before your own code. Thanks to @trueshko for coming up with an easy method for keeping the scopes separate.

<!DOCTYPE html>
<html>
  <head>
    <script>

{
/**
 * @license
 * Copyright 2010-2025 Three.js Authors
 * SPDX-License-Identifier: MIT
 */
const REVISION = '179';

// The rest of the code from the attached file...

window.MODULE = { ACESFilmicToneMapping, AddEquation, AddOperation /*...*/ };
}

window.THREE = Object.assign({}, CORE, MODULE);

// Working test
function init() {
  const canvas = document.getElementById("canvas");
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, 800 / 600);
  camera.position.z = 3;
  const renderer = new THREE.WebGLRenderer({canvas: canvas});

  const geometry = new THREE.BoxGeometry(1, 1, 1);
  const material = new THREE.MeshBasicMaterial({color: 0x00ff00});
  const cube = new THREE.Mesh(geometry, material);
  cube.rotation.set(Math.PI * 0.25, Math.PI * 0.25, 0);
  scene.add(cube);
  
  renderer.render(scene, camera);
}

    </script>
  </head>
  <body onload="init();">
    <canvas id="canvas" width="800" height="600"></canvas>
  </body>
</html>

This is r179. The code will have to be combined again if you want to update to future versions. There may be a better way to do this, but this is what I was able to do with my current knowledge.

Hi BrockenRock…

Firstly, I am deeply grateful for your help.

Secondly… I followed your instructions: I copied the HTML + js file into my IDE (WebStorm). I then copied and pasted the entire contents of three.no-module.js (good name), as instructed at the line indicated in the HTML+js file.

Unfortunately, it doesn’t work for me… i get “uncaught typeError: assignment to constant variabe” at line 58706 ({ Matrix3 = CORE.Matrix3, Vector2 = CORE.Vector2….) and Uncaught ReferenceError: THREE isnot defined at line 76998 (const scene = new THREE.Scene() ).

I tried various ways that I thought might work, searched the internet, studied some books so that I wouldn’t bug you more, but came up empty handed.

Could you give me a little more direction as to how to make the code work? If you would like to email a large file: workirby@gmail.com works :slight_smile:

Many thank yous- Martin

For up to r158 you could try the demoduler. For releases above r158 it may work if the source code uses the same style. At least until the WebGL-WebGPU split there is some chance to convert the main three.module.js and some of the addons like OrbitControls.js.

Although this might be an easier approach, a better solution is to use a bundler. Here is a story:

I also have a project that relies to be used by students in a protected environment (i.e. they cannot install other software, and they need to be able to work off-line with local files only). A month ago I converted the software to use Three.js modules, then used a bundler (rollup.js) to pack everything in a single file that can be used locally without internet or local web server. And it works beautifully. But I had two months of trial-and-error nightmares until I did the conversion. And I’m still not sure why it works.

Edit:

Spent half an hour trying to convert one of your nice demos into r179. I think I managed to do it. Attached is a ZIP file that contains:

  • Lens 3D Sim - original.html - this is your original file
  • three.js - modified three.js to be without modules
  • OrbitControls.js - modified OrbitControls to be without modules
  • Lens 3D Sim - external.html - your file that uses these two modified files
  • Lens 3D Sim - embedded.html - your file with embedded modified files

Could you try whether Lens 3D Sim - embedded.html works the way your expect? This should be the single file that works as in the old times.

Lens 3D Sim.zip (1.1 MB)

Here is what it looks like when I start the HTML file from a local folder:

NB. I had to fix the names of few classes, as they have changed, Geometry → BufferGeometry and SphereBufferGeometry → SphereGeometry. All changes are marked by a cat ^..^

2 Likes

It would probably be easiest for me to just give you the fully-assembled example file.

example.html (2.0 MB)

It should draw a canvas that looks like this:

1 Like

I think you can write:

<script>
{
  // code from three.core.min.js (or three.core.js) before "export {...}"

  // then code from three.module.js after "import {...} from ./three.core.js"
  // Instead of export {...} at the end of the code, write window.THREE = {...}

}
</script>

I tried pretty much that at first, but the files have some variable overlap, so it throws redefinition errors. You have to separate the scopes or manually rename the variables, and I don’t know how many there are.

Hi BrockenRock and PavelBoytchev… Thank you so much, both of you, for your time and expertise. I am in awe of how much you put into helping someone you’ve never met. Your help is invaluable (and works!!!…fabulous!!), and very, very good for my mental health! I will do my best to pay it forward.

1 Like

Hi BrockenRock and PavelBoytchev… Thank you so much, both of you, for your time and expertise. I am in awe of how much you put into helping someone you’ve never met. Your help is invaluable (and works!!!…fabulous!!), and very, very good for my mental health! I will do my best to pay it forward.

3 Likes

I’ve been using your fix… it works great! Thank you.

However (there’s always a however…), you may notice in the ‘fixed’ simulation that the object and image don’t reflect light fro the light sources. I have created a couple of seperate tests to see if this is true, and that it’s not something in my code, and to the best of my rather limited abilities, I can’t see the source of my error in my code.

Could you suggest a fix? I’ll keep on trying to find one myself, but it might take me a couple of years.

As always, thank you.

The light model has changed into more adhering to physics laws. So, I just removed lights decay and increased slightly intensities – you may need to finetune the parameters to your liking. With decay the intensities must be much larger (hundreds or even thousands times larger), as their effect depends on the square of the distance.

const directionalLight4 = new THREE.PointLight( "white", 3 ); // increased
directionalLight4.decay=0; // added
directionalLight4.position.set(-60, 10, 70);
scene.add( directionalLight4 );

const directionalLight5 = new THREE.PointLight( "white", 3 ); // increased
directionalLight5.decay=0; // added
directionalLight5.position.set(60, 10, 70);
scene.add( directionalLight5 );

const pointLight6 = new THREE.PointLight( "white", 3 ); // increased
pointLight6.decay=0; // added
pointLight6.position.set(60, 10, 40);
scene.add( pointLight6 );

const pointLight7 = new THREE.PointLight( "red", 6 ); // increased
pointLight7.decay=0; // added
pointLight7.position.set(60, 10, 40);
scene.add( pointLight7 );

PS. It is not polite to store a point light in a variable named as if it is a directional light