3D Tshirt Configurator With Three.js and Fabric.js
Published: 2024-06-09 16:13:10

Hello Dev Community,

I'm a non-code developer working on a pet project to create a 3D T-shirt configurator that allows users to customize a T-shirt model in real-time. The goal is to let users add text, images, and colors to a 3D T-shirt model, manipulate these elements, and interact with the 3D model itself. My target audience is primarily mobile users, so responsiveness and ease of use on smaller screens are critical.

Current Implementation

I've been using Three.js for rendering the 3D model and Fabric.js for 2D canvas interactions. However, I've encountered several issues that I haven't been able to resolve. Here’s what I have so far:

1. Loading the 3D Model: Successfully loading and displaying the 3D T-shirt model.
2. Fabric.js Canvas: Overlaid on top of the 3D model to add and manipulate text and images.
3. Basic Controls: Buttons for adding text and images, changing colors, rotating the model, and moving the model.

Issues I'm Facing

1. Interaction with Added Elements: Users cannot edit, resize, or move added text and images around the 3D model.
2. Toolbar Usability: The toolbar is not user-friendly on mobile devices.
3. Model Positioning: The 3D model is not centered correctly and often appears misplaced.
4. Element Manipulation: Added elements (text, images) are not properly aligned with the 3D model and sometimes cover the entire model instead of specific areas.
5. Responsiveness: The overall configurator lacks responsiveness for mobile devices.

Requirements

I need help with the following:

1. Interactivity: Ensuring that users can interact with added elements (text, images) on the 3D model—move, resize, change colors, and edit text in real-time.
2. Mobile-Friendly Toolbar: Making the toolbar responsive and user-friendly for mobile devices, possibly with gesture controls.
3. Correct Model Centering: Ensuring the 3D model is centered and properly scaled on load.
4. Accurate Element Placement: Properly mapping text and images to specific areas of the 3D model without them covering the entire model.
5. Overall Responsiveness: Making sure the configurator is fully responsive and functional across different screen sizes.

Sample Code

Here is the current version of my code:

```
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>3D T-shirt Configurator</title>
  <style>
    body { margin: 0; }
    #container { display: flex; flex-direction: column; height: 100vh; }
    #3d-view { flex: 1; }
    #toolbar { position: fixed; bottom: 0; width: 100%; background-color: #eee; display: flex; justify-content: center; padding: 10px; }
    canvas { display: block; }
  </style>
</head>
<body>
  <div id="container">
    <div id="3d-view"></div>
    <div id="toolbar">
      <!-- Toolbar buttons here -->
      <input type="file" id="uploadTexture" />
      <input type="color" id="colorPicker" />
      <button id="addText">Add Text</button>
      <button id="moveUp">Up</button>
      <button id="moveDown">Down</button>
      <button id="moveLeft">Left</button>
      <button id="moveRight">Right</button>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/three@0.138.3/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.138.3/examples/js/loaders/GLTFLoader.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/fabric@4.5.0/dist/fabric.min.js"></script>
  <script>
    let scene, camera, renderer, model, fabricCanvas;

    function initThreeJS() {
      const container = document.getElementById('3d-view');
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(window.innerWidth, window.innerHeight * 0.8);
      container.appendChild(renderer.domElement);

      const light = new THREE.AmbientLight(0xffffff, 1);
      scene.add(light);

      const loader = new THREE.GLTFLoader();
      loader.load('path/to/your/model.gltf', function(gltf) {
        model = gltf.scene;
        scene.add(model);
        camera.position.z = 3;
      });

      const controls = new THREE.OrbitControls(camera, renderer.domElement);
      controls.enableZoom = true;
      controls.enableRotate = true;
      controls.enablePan = false;

      animate();
    }

    function initFabricJS() {
      fabricCanvas = new fabric.Canvas('fabricCanvas', {
        width: window.innerWidth,
        height: window.innerHeight * 0.2,
        backgroundColor: 'transparent',
      });

      fabricCanvas.on('object:modified', generateTexture);
      fabricCanvas.on('object:added', generateTexture);
    }

    function generateTexture() {
      fabricCanvas.renderAll();
      fabricCanvas.getElement().toBlob(function(blob) {
        const texture = new THREE.TextureLoader().load(URL.createObjectURL(blob));
        texture.flipY = false;
        model.traverse(function(child) {
          if (child.isMesh) {
            child.material.map = texture;
            child.material.needsUpdate = true;
          }
        });
      });
    }

    document.getElementById('addText').addEventListener('click', function() {
      const text = new fabric.Text('Sample Text', { left: 100, top: 100, fill: 'black', fontSize: 24 });
      fabricCanvas.add(text);
    });

    document.getElementById('uploadTexture').addEventListener('change', function(event) {
      const file = event.target.files[0];
      const reader = new FileReader();
      reader.onload = function(e) {
        fabric.Image.fromURL(e.target.result, function(img) {
          img.scaleToWidth(200);
          fabricCanvas.add(img);
        });
      };
      reader.readAsDataURL(file);
    });

    document.getElementById('colorPicker').addEventListener('input', function(event) {
      const activeObject = fabricCanvas.getActiveObject();
      if (activeObject) {
        activeObject.set({ fill: event.target.value });
        fabricCanvas.renderAll();
        generateTexture();
      }
    });

    document.getElementById('moveUp').addEventListener('click', function() {
      model.position.y += 0.1;
    });

    document.getElementById('moveDown').addEventListener('click', function() {
      model.position.y -= 0.1;
    });

    document.getElementById('moveLeft').addEventListener('click', function() {
      model.position.x -= 0.1;
    });

    document.getElementById('moveRight').addEventListener('click', function() {
      model.position.x += 0.1;
    });

    window.addEventListener('resize', function() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight * 0.8);
    });

    initThreeJS();
    initFabricJS();
  </script>
</body>
</html>


```
Features Needed

1. Editable Text: Users should be able to add, edit, resize, and move text around the 3D model.
2. Image Manipulation: Users should be able to upload images, resize, move, and apply them to specific areas of the 3D model.
3. Color Customization: Users should be able to change the color of the text and images applied to the 3D model.
4. Model Controls: Users should be able to move, rotate, and zoom in/out the 3D model.
5. Mobile Responsiveness: The entire configurator should be optimized for mobile use, with easy-to-use controls and a responsive design.

Request for Help

I would appreciate any guidance, code snippets, or resources that could help me achieve the desired functionality. Specifically, I'm looking for:

1. Improved User Interactivity: How can I enable users to interact with added elements (text, images) directly on the 3D model?
2. Mobile Optimization: Tips or best practices for making the toolbar and overall interface more mobile-friendly.
3. Accurate Texture Mapping: How can I ensure that the added text and images are correctly mapped to specific areas of the 3D model?

Thank you for taking the time to read this. Any help or pointers would be greatly appreciated!