Discuss and resolve questions on Liquid, JavaScript, themes, sales channels, and site speed enhancements.
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?
Solved! Go to the solution
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.
Have you tried to replace <version> in (https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js) with the current version of threejs
Hello Derrenhg, Yes I've tried with multiple versions. This code fragment was an example.
Sorry for not clarifying that clearly.
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?
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.
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.
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>
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 😓
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';
Oh wow! Nice! So that means that the importmap should work.
So your render works only after resizing?
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();
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
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.
June brought summer energy to our community. Members jumped in with solutions, clicked ...
By JasonH Jun 5, 2025Learn how to build powerful custom workflows in Shopify Flow with expert guidance from ...
By Jacqui May 7, 2025Did You Know? May is named after Maia, the Roman goddess of growth and flourishing! ...
By JasonH May 2, 2025