Skip to main content

Your First Product

Build a working t-shirt customizer in under 10 minutes. By the end of this tutorial you'll have a two-sided product with editable text and image upload.

Prerequisites​

  • Node.js >= 18
  • A package manager (npm, pnpm, or yarn)

1. Set Up the Project​

mkdir my-customizer && cd my-customizer
npm init -y
npm install @chamevo/customizer

Create an index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>T-Shirt Customizer</title>
<style>
body { margin: 0; font-family: sans-serif; }
#customizer-container { width: 100%; height: 100vh; }
</style>
</head>
<body>
<div id="customizer-container"></div>
<script type="module" src="main.js"></script>
</body>
</html>

2. Define the Product​

Create main.js:

import { createCustomizer } from '@chamevo/customizer';

// Define a t-shirt with front and back views
const tshirtProduct = {
title: 'Classic T-Shirt',
views: [
{
title: 'Front',
thumbnail: 'https://your-domain.com/images/tshirt-front-thumb.jpg',
elements: [
{
type: 'image',
source: 'https://your-domain.com/images/tshirt-front.png',
title: 'T-Shirt Front',
parameters: {
left: 450,
top: 300,
draggable: false,
removable: false,
resizable: false,
zChangeable: false,
},
},
],
options: {
stageWidth: 900,
stageHeight: 600,
},
},
{
title: 'Back',
thumbnail: 'https://your-domain.com/images/tshirt-back-thumb.jpg',
elements: [
{
type: 'image',
source: 'https://your-domain.com/images/tshirt-back.png',
title: 'T-Shirt Back',
parameters: {
left: 450,
top: 300,
draggable: false,
removable: false,
resizable: false,
zChangeable: false,
},
},
],
},
],
};

The product data model is simple:

  • A product has one or more views (front, back, sleeve, etc.)
  • Each view has elements (images, text, SVGs)
  • Each element has a type, source, title, and parameters

3. Initialize the Customizer​

Add this below the product definition in main.js:

// Create the customizer and load the product
const customizer = createCustomizer('#customizer-container', {
stageWidth: 900,
stageHeight: 600,
fonts: [
{ name: 'Arial' },
{ name: 'Lobster', url: 'google' },
{ name: 'Playfair Display', url: 'google' },
],
});

customizer.addEventListener('cvReady', async (e) => {
const { chamevo, canvas } = e.detail;

// Load the t-shirt product
await customizer.loadProduct(tshirtProduct);

console.log('Customizer ready!');
});

createCustomizer() does three things:

  1. Registers all cv-* web components
  2. Creates a <cv-customizer> element with your options
  3. Appends it to the target container

4. Add Editable Text​

Let's pre-place an editable text element on the front view. Add it to the front view's elements array:

// Add this as the second element in the Front view
{
type: 'text',
source: 'Your Text Here',
title: 'Custom Text',
parameters: {
left: 450,
top: 350,
fill: '#333333',
fontSize: 32,
fontFamily: 'Lobster',
draggable: true,
resizable: true,
rotatable: true,
removable: true,
editable: true,
colors: '#000000', // enables color picker
},
}

Users can now double-click the text to edit it, drag it around, resize it, and change its color.

5. Listen to Events​

Track what the user is doing:

customizer.addEventListener('cvReady', async (e) => {
const { chamevo } = e.detail;

await customizer.loadProduct(tshirtProduct);

// Track element changes
chamevo.on('elementModify', ({ element, changes }) => {
console.log(`${element.title} modified:`, changes);
});

// Track price changes
chamevo.on('priceChange', ({ price, singleProductPrice }) => {
console.log(`Price: $${price.toFixed(2)}`);
});

// Track view switches
chamevo.on('viewSelect', ({ view, index }) => {
console.log(`Switched to ${view.title} (view ${index})`);
});
});

6. Get the Customized Product​

When the user is done, retrieve their customization:

// Add a "Save" button handler
document.getElementById('save-btn')?.addEventListener('click', async () => {
const product = await customizer.getProduct();
console.log('Customized product:', product);

// Send to your server
await fetch('/api/save-design', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(product),
});
});

getProduct() returns a CVProduct with all views and their current element states serialized. You can later reload this product to restore the user's design.

7. Add Print Areas (Optional)​

Print areas define the printable region on each view. Elements are constrained within print areas:

// Add printAreas to the Front view
{
title: 'Front',
elements: [ /* ... */ ],
printAreas: [
{
printingBox: {
left: 310,
top: 140,
width: 280,
height: 320,
},
},
],
}

Complete Example​

import { createCustomizer } from '@chamevo/customizer';

const tshirtProduct = {
title: 'Classic T-Shirt',
views: [
{
title: 'Front',
thumbnail: 'https://your-domain.com/images/tshirt-front-thumb.jpg',
elements: [
{
type: 'image',
source: 'https://your-domain.com/images/tshirt-front.png',
title: 'T-Shirt Front',
parameters: {
left: 450, top: 300,
draggable: false, removable: false,
resizable: false, zChangeable: false,
},
},
{
type: 'text',
source: 'Your Text Here',
title: 'Custom Text',
parameters: {
left: 450, top: 350,
fill: '#333333', fontSize: 32, fontFamily: 'Lobster',
draggable: true, resizable: true,
rotatable: true, removable: true, editable: true,
colors: '#000000',
},
},
],
printAreas: [
{
printingBox: { left: 310, top: 140, width: 280, height: 320 },
},
],
options: { stageWidth: 900, stageHeight: 600 },
},
{
title: 'Back',
thumbnail: 'https://your-domain.com/images/tshirt-back-thumb.jpg',
elements: [
{
type: 'image',
source: 'https://your-domain.com/images/tshirt-back.png',
title: 'T-Shirt Back',
parameters: {
left: 450, top: 300,
draggable: false, removable: false,
resizable: false, zChangeable: false,
},
},
],
},
],
};

const customizer = createCustomizer('#customizer-container', {
stageWidth: 900,
stageHeight: 600,
fonts: [
{ name: 'Arial' },
{ name: 'Lobster', url: 'google' },
{ name: 'Playfair Display', url: 'google' },
],
});

customizer.addEventListener('cvReady', async (e) => {
const { chamevo } = e.detail;
await customizer.loadProduct(tshirtProduct);

chamevo.on('priceChange', ({ price }) => {
document.getElementById('price').textContent = `$${price.toFixed(2)}`;
});
});

Next Steps​