Trying to use ThreeJS (R172 and later) inside Shopify using liquid

Solved

Trying to use ThreeJS (R172 and later) inside Shopify using liquid

makzy
Tourist
7 1 0

I want to be able to insert an interactive 3D background element (and possibly more). I plan to do this using Three.js for several reasons: it's free, highly flexible, relatively fast, and easy to use for a beginner JavaScript developer.


However, when I try to import Three.js with an import map, I can't get it to work. The canvas element does not render.
Below is the code showing the import map as suggested in the Three.js documentation.

<script type="importmap">
	{
		"imports": {
		"three": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js",
		"three/addons/": "https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/"
		}
	}
</script>
import * as THREE from 'three';
...


When I use an older module, called "TroisJS", it does seem to work .

<script type="module">
  import { createApp } from 'https://unpkg.com/troisjs@0.3.3/build/trois.module.cdn.min.js';
  ...

 don't want to use that module because it is not being updated.

I've tried several approaches: importing with a bundled JS file, using older versions (R160 and R159) that include a single minified file, and using a module-type script with a CDN import. I also tested everything locally with Vite, and it worked every time—but it doesn’t seem to work in Shopify.

Can someone help me with this issue?

Accepted Solution (1)
makzy
Tourist
7 1 0

This is an accepted solution.

I actually got it working, with a build file inside the asset folder AND with the importmap.
The problem was Three JS not being able to render inside a canvas tag. Or at least it could find or make a canvas tag.

I found an old post with a (not marked) answer.
This was this person's solution that also worked for me:

// get data from Canvas
const canvas = document.querySelector('#id3DCanvas');


// Create a basic perspective camera
// var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var camera = new THREE.PerspectiveCamera( 75, canvas.clientWidth/canvas.clientHeight, 0.1, 1000 );
camera.position.z = 4;

// Create a renderer with Antialiasing
  const renderer = new THREE.WebGLRenderer({antialias: true, canvas});

// Configure renderer clear color
renderer.setClearColor("#000000");

// Configure renderer size
// renderer.setSize( window.innerWidth/2, window.innerHeight/2);
renderer.setSize( canvas.clientWidth, canvas.clientHeight);
// Append Renderer to DOM


document.getElementById("id_canvasdiv").appendChild( renderer.domElement );
// document.body.appendChild( renderer.domElement );
#id3DCanvas { width: 500px; height: 1000px }
  <div id="id_canvasdiv">
      <canvas id="id3DCanvas"></canvas>
    </div>


I don't know what happens in the background, but it looks like Three JS couldn't make a canvas directly. 

View solution in original post

Replies 12 (12)

usamadevhg
Shopify Partner
19 0 3

Have you tried to replace <version> in (https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js) with the current version of threejs 

makzy
Tourist
7 1 0

Hello Derrenhg, Yes I've tried with multiple versions. This code fragment was an example.
Sorry for not clarifying that clearly.

usamadevhg
Shopify Partner
19 0 3

import map can be tricky to deal with

can you try to upload

minified javascript files from this build to shopify assets folder to see if that works?

https://github.com/mrdoob/three.js/tree/master/build

makzy
Tourist
7 1 0

I'll try that, thnx. I'll let you know if that worked. 
I did try this before, but not like how the importmap is structured.

makzy
Tourist
7 1 0

This is an accepted solution.

I actually got it working, with a build file inside the asset folder AND with the importmap.
The problem was Three JS not being able to render inside a canvas tag. Or at least it could find or make a canvas tag.

I found an old post with a (not marked) answer.
This was this person's solution that also worked for me:

// get data from Canvas
const canvas = document.querySelector('#id3DCanvas');


// Create a basic perspective camera
// var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var camera = new THREE.PerspectiveCamera( 75, canvas.clientWidth/canvas.clientHeight, 0.1, 1000 );
camera.position.z = 4;

// Create a renderer with Antialiasing
  const renderer = new THREE.WebGLRenderer({antialias: true, canvas});

// Configure renderer clear color
renderer.setClearColor("#000000");

// Configure renderer size
// renderer.setSize( window.innerWidth/2, window.innerHeight/2);
renderer.setSize( canvas.clientWidth, canvas.clientHeight);
// Append Renderer to DOM


document.getElementById("id_canvasdiv").appendChild( renderer.domElement );
// document.body.appendChild( renderer.domElement );
#id3DCanvas { width: 500px; height: 1000px }
  <div id="id_canvasdiv">
      <canvas id="id3DCanvas"></canvas>
    </div>


I don't know what happens in the background, but it looks like Three JS couldn't make a canvas directly. 

Esuna3D
New Member
4 0 0

Hey Makzy, I am trying to achieve the same, but no luck. Trying to load a .stl viewer on my store page. Could you show your liquid code and which three.js files you uploaded? My issue is getting three.js loaded in the first place, with nonsense like this:

An import map is added after module script load was triggered.
Uncaught TypeError: Failed to resolve module specifier "three". Relative references must start with either "/", "./", or "../".

For this code:

<script type="importmap">
  {
    "imports": {
      "three": "{{ 'three.module.js' | asset_url }}",
      "OrbitControls": "{{ 'OrbitControls.js' | asset_url }}",
      "STLLoader": "{{ 'STLLoader.js' | asset_url }}"
    }
  }
</script>

<script type="module" src="{{ 'viewer.js' | asset_url }}"></script>



makzy
Tourist
7 1 0

Hey Esuna,
I've never gotten Orbit Controls working. At the moment I only import the Three.js module that is located in my assets folder.
Also importmap didn't work for me. So underneath is the solution that worked for me.

In my theme.liquid I've added the three js module in the head:

 

<script src="{{ 'three.module.min.js' | asset_url }}" defer="async" type="module"></script>

 

 
In my assets folder, I directly inserted the three.module.min.js from the build (https://github.com/mrdoob/three.js/blob/dev/build/three.module.min.js_)
I also inserted the three.core.min.js in the assets folder, for good measure (never tested without it though. I noticed some imports from the core module)


With this I got the renderer working in every page. Three.js just couldn't change my canvas tag in my case. 


FYI: Orbit Controls just break my code at the moment 😓

Esuna3D
New Member
4 0 0

Hey Makzy,

 

I actually got it all working, including OrbitControls! Uploading the entire build/ folder into assets was the key. I now have the issue that the canvas remains white/grey until I resize the window. It initiates at 0w/h. 


In theme.liquid, in the <head> tag above all the existing <script> tags:

   <script type="importmap">
      {
        "imports": {
          "three": "{{ 'three.module.js' | asset_url }}",
          "OrbitControls": "{{ 'OrbitControls.js' | asset_url }}",
          "STLLoader": "{{ 'STLLoader.js' | asset_url }}"
        }
      }
    </script>
    <script type="module" src="{{ 'viewer.js' | asset_url }}"></script>

 
then in my viewer.js I do this:

import * as THREE from 'three';
import { STLLoader } from 'STLLoader';
import { OrbitControls } from 'OrbitControls';
makzy
Tourist
7 1 0

Oh wow! Nice! So that means that the importmap should work.
So your render works only after resizing?

Esuna3D
New Member
4 0 0

Ye, but I just solved that as well!

 

const container = document.getElementById('threejs-container-viewer');
const renderer = new THREE.WebGLRenderer({ antialias: true });
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 3000);
camera.position.set(75, 0, 75);
...
renderer.setSize(container.clientWidth, container.clientHeight);
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();

 

Screenshot 2025-02-06 at 00.05.47.png

makzy
Tourist
7 1 0

XD glad that you fixed it.

 

Thank you as well, because now I know that Orbit Controls should work with an importmap.
That **bleep** is crazy to me 

Esuna3D
New Member
4 0 0

I had to download OrbitControls.js + STLLoader.js and add it to assets/ for my viewer, so you still need to get the raw files for all the plugins into Shopify.