Table of Contents

  1. Introduction
  2. Available APIs
  3. UI Components
  4. Basic Examples
  5. Intermediate Examples
  6. Advanced Examples
  7. Best Practices

Introduction

Custom plugins in Yo! Draw let you extend functionality with JavaScript. Your code has full access to:

  • canvas - Main Fabric.js canvas instance
  • fabric - Fabric.js library
  • animState - Animation system state
  • history - Undo/redo history
  • All global functions and variables

Available APIs

Canvas Operations

// Get canvas properties
canvas.width
canvas.height
canvas.backgroundColor

// Get all objects
const allObjects = canvas.getObjects();

// Get selected object
const obj = canvas.getActiveObject();

// Add object
canvas.add(myObject);

// Remove object
canvas.remove(obj);

// Render changes (ALWAYS call after modifications)
canvas.renderAll();

// Save to undo/redo history
saveHistory();

// Clear selection
canvas.discardActiveObject();

// Select object
canvas.setActiveObject(obj);

Object Properties

const obj = canvas.getActiveObject();

// Common properties
obj.type          // 'rect', 'circle', 'i-text', 'image', etc.
obj.left          // X position
obj.top           // Y position
obj.width         // Object width
obj.height        // Object height
obj.scaleX        // Horizontal scale
obj.scaleY        // Vertical scale
obj.angle         // Rotation angle
obj.opacity       // 0 to 1
obj.fill          // Fill color
obj.stroke        // Border color
obj.strokeWidth   // Border width
obj.visible       // true/false

// Modify properties
obj.set({
 left: 100,
 top: 50,
 fill: '#ff0000',
 opacity: 0.5,
 angle: 45
});

// Clone object
obj.clone(function(cloned) {
 canvas.add(cloned);
});

Creating Shapes

// Rectangle
const rect = new fabric.Rect({
 left: 100,
 top: 100,
 width: 200,
 height: 100,
 fill: '#ff0000',
 stroke: '#000000',
 strokeWidth: 2
});

// Circle
const circle = new fabric.Circle({
 left: 100,
 top: 100,
 radius: 50,
 fill: '#00ff00'
});

// Triangle
const triangle = new fabric.Triangle({
 left: 100,
 top: 100,
 width: 100,
 height: 100,
 fill: '#0000ff'
});

// Line
const line = new fabric.Line([50, 50, 200, 200], {
 stroke: '#000000',
 strokeWidth: 3
});

// Text
const text = new fabric.IText('Hello World', {
 left: 100,
 top: 100,
 fontSize: 40,
 fill: '#000000',
 fontFamily: 'Arial'
});

// Polygon (Star example)
const star = new fabric.Polygon([
 {x: 50, y: 0},
 {x: 61, y: 35},
 {x: 98, y: 35},
 {x: 68, y: 57},
 {x: 79, y: 91},
 {x: 50, y: 70},
 {x: 21, y: 91},
 {x: 32, y: 57},
 {x: 2, y: 35},
 {x: 39, y: 35}
], {
 left: 100,
 top: 100,
 fill: '#ffff00'
});

Loading Images

// From URL
fabric.Image.fromURL('https://example.com/image.png', function(img) {
 img.scaleToWidth(200);
 img.set({ left: 100, top: 100 });
 canvas.add(img);
 canvas.renderAll();
});

// From data URL
const dataURL = 'data:image/png;base64,...';
fabric.Image.fromURL(dataURL, function(img) {
 canvas.add(img);
 canvas.renderAll();
});

Working with SVG

// Load SVG from string
const svgString = '<svg>...</svg>';
fabric.loadSVGFromString(svgString, function(objects, options) {
 const svg = fabric.util.groupSVGElements(objects, options);
 canvas.add(svg);
 canvas.renderAll();
});

// Load SVG from URL
fabric.loadSVGFromURL('https://example.com/image.svg', function(objects, options) {
 const svg = fabric.util.groupSVGElements(objects, options);
 canvas.add(svg);
 canvas.renderAll();
});

UI Components

Dialog Box

// Simple message
showDialog('Title', 'Your message here');

// With button callback
showDialog('Success', 'Operation completed!');

Prompt (Get User Input)

// Get text input
showPrompt('Enter Name', 'Please enter your name:', 'Default Value').then(result => {
 if (result) {
 showDialog('Hello', 'Hello ' + result + '!');
 } else {
 showDialog('Cancelled', 'User cancelled');
 }
});

// Get number input
showPrompt('Enter Angle', 'Enter rotation angle:', '45').then(result => {
 if (result) {
 const angle = parseFloat(result);
 const obj = canvas.getActiveObject();
 if (obj) {
 obj.set('angle', angle);
 canvas.renderAll();
 saveHistory();
 }
 }
});

Confirmation Dialog

showConfirm('Delete All', 'Are you sure you want to delete everything?').then(confirmed => {
 if (confirmed) {
 canvas.clear();
 canvas.backgroundColor = '#ffffff';
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Canvas cleared!');
 }
});

Chaining Prompts

async function multiStepInput() {
 const name = await showPrompt('Step 1', 'Enter text:', 'Hello');
 if (!name) return;

 const size = await showPrompt('Step 2', 'Enter font size:', '40');
 if (!size) return;

 const color = await showPrompt('Step 3', 'Enter color (hex):', '#ff0000');
 if (!color) return;

 const text = new fabric.IText(name, {
 left: 100,
 top: 100,
 fontSize: parseInt(size),
 fill: color
 });

 canvas.add(text);
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Text created!');
}

multiStepInput();

Basic Examples

1. Change Selected Object Color

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Please select an object first!');
} else {
 obj.set('fill', '#ff0000');
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Color changed to red!');
}

2. Rotate Selected Object

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Please select an object first!');
} else {
 showPrompt('Rotate', 'Enter angle in degrees:', '45').then(result => {
 if (result) {
 obj.set('angle', parseFloat(result));
 canvas.renderAll();
 saveHistory();
 }
 });
}

3. Set Random Colors

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16);
 obj.set('fill', randomColor);
 canvas.renderAll();
 saveHistory();
 showDialog('Random Color', 'Applied: ' + randomColor);
}

4. Duplicate Object Multiple Times

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 showPrompt('Duplicate', 'How many copies?', '5').then(result => {
 if (result) {
 const count = parseInt(result);
 for (let i = 1; i <= count; i++) {
 obj.clone(function(cloned) {
 cloned.set({
 left: obj.left + (i * 20),
 top: obj.top + (i * 20)
 });
 canvas.add(cloned);
 });
 }
 canvas.renderAll();
 saveHistory();
 showDialog('Done', count + ' copies created!');
 }
 });
}

5. Center Selected Object

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 obj.set({
 left: canvas.width / 2,
 top: canvas.height / 2,
 originX: 'center',
 originY: 'center'
 });
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Object centered!');
}

Intermediate Examples

6. Create Grid of Shapes

showPrompt('Grid', 'Enter grid size (e.g., 5 for 5x5):', '5').then(result => {
 if (!result) return;

 const gridSize = parseInt(result);
 const spacing = 60;
 const size = 40;

 for (let row = 0; row < gridSize; row++) {
 for (let col = 0; col < gridSize; col++) {
 const rect = new fabric.Rect({
 left: col * spacing + 50,
 top: row * spacing + 50,
 width: size,
 height: size,
 fill: '#' + Math.floor(Math.random()*16777215).toString(16),
 stroke: '#000000',
 strokeWidth: 1
 });
 canvas.add(rect);
 }
 }

 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Created ' + (gridSize * gridSize) + ' squares!');
});

7. Add Border to All Objects

showPrompt('Border Width', 'Enter border width in pixels:', '3').then(result => {
 if (!result) return;

 const width = parseInt(result);
 const objects = canvas.getObjects();

 objects.forEach(obj => {
 obj.set({
 stroke: '#000000',
 strokeWidth: width
 });
 });

 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Added borders to ' + objects.length + ' objects!');
});

8. Fade Animation

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 let opacity = 1;
 const interval = setInterval(() => {
 opacity -= 0.05;
 if (opacity <= 0) {
 opacity = 1;
 }
 obj.set('opacity', opacity);
 canvas.renderAll();
 }, 50);

 setTimeout(() => {
 clearInterval(interval);
 obj.set('opacity', 1);
 canvas.renderAll();
 showDialog('Done', 'Animation complete!');
 }, 3000);
}

9. Circular Arrangement

const objects = canvas.getActiveObjects();
if (!objects || objects.length < 2) {
 showDialog('Error', 'Select multiple objects (Ctrl+Click)');
} else {
 const centerX = canvas.width / 2;
 const centerY = canvas.height / 2;
 const radius = 150;
 const angleStep = (2 * Math.PI) / objects.length;

 objects.forEach((obj, index) => {
 const angle = index * angleStep;
 obj.set({
 left: centerX + radius * Math.cos(angle),
 top: centerY + radius * Math.sin(angle),
 originX: 'center',
 originY: 'center'
 });
 });

 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Arranged ' + objects.length + ' objects in a circle!');
}

10. Text Shadow Effect

const obj = canvas.getActiveObject();
if (!obj || (obj.type !== 'i-text' && obj.type !== 'text')) {
 showDialog('Error', 'Select a text object first!');
} else {
 obj.set('shadow', new fabric.Shadow({
 color: 'rgba(0,0,0,0.5)',
 blur: 10,
 offsetX: 5,
 offsetY: 5
 }));
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Shadow effect applied!');
}

Advanced Examples

11. Spiral Generator

showPrompt('Spiral', 'Enter number of points:', '100').then(result => {
 if (!result) return;

 const points = [];
 const numPoints = parseInt(result);
 const angleStep = 360 / 20;

 for (let i = 0; i < numPoints; i++) {
 const angle = (i * angleStep) * Math.PI / 180;
 const radius = i * 2;
 points.push({
 x: 400 + radius * Math.cos(angle),
 y: 300 + radius * Math.sin(angle)
 });
 }

 const spiral = new fabric.Polyline(points, {
 fill: 'transparent',
 stroke: '#ff0000',
 strokeWidth: 2
 });

 canvas.add(spiral);
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Spiral created!');
});

12. Gradient Generator

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 const gradient = new fabric.Gradient({
 type: 'linear',
 coords: {
 x1: 0,
 y1: 0,
 x2: obj.width,
 y2: 0
 },
 colorStops: [
 { offset: 0, color: '#ff0000' },
 { offset: 0.5, color: '#00ff00' },
 { offset: 1, color: '#0000ff' }
 ]
 });

 obj.set('fill', gradient);
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Rainbow gradient applied!');
}

13. Pattern Fill from Image

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 showPrompt('Pattern', 'Enter image URL:', 'https://via.placeholder.com/50').then(url => {
 if (!url) return;

 fabric.util.loadImage(url, function(img) {
 const pattern = new fabric.Pattern({
 source: img,
 repeat: 'repeat'
 });
 obj.set('fill', pattern);
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Pattern applied!');
 });
 });
}

14. Object Inspector

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 const info = `
Type: ${obj.type}
Position: (${Math.round(obj.left)}, ${Math.round(obj.top)})
Size: ${Math.round(obj.width * obj.scaleX)} x ${Math.round(obj.height * obj.scaleY)}
Rotation: ${Math.round(obj.angle)}°
Opacity: ${obj.opacity}
Fill: ${obj.fill}
Stroke: ${obj.stroke || 'none'}
`;
 showDialog('Object Inspector', info);
}

15. Batch Resize

showConfirm('Batch Resize', 'Resize all objects to 50% of current size?').then(confirmed => {
 if (!confirmed) return;

 const objects = canvas.getObjects();
 objects.forEach(obj => {
 obj.set({
 scaleX: obj.scaleX * 0.5,
 scaleY: obj.scaleY * 0.5
 });
 });

 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Resized ' + objects.length + ' objects!');
});

16. Color Palette Extractor

const obj = canvas.getActiveObject();
if (!obj || obj.type !== 'image') {
 showDialog('Error', 'Select an image first!');
} else {
 const imgElement = obj.getElement();
 const tempCanvas = document.createElement('canvas');
 tempCanvas.width = imgElement.width;
 tempCanvas.height = imgElement.height;
 const ctx = tempCanvas.getContext('2d');
 ctx.drawImage(imgElement, 0, 0);

 const colors = new Set();
 const imageData = ctx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
 const data = imageData.data;

 for (let i = 0; i < data.length; i += 4000) {
 const r = data[i];
 const g = data[i + 1];
 const b = data[i + 2];
 const hex = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
 colors.add(hex);
 if (colors.size >= 10) break;
 }

 let x = 50;
 colors.forEach(color => {
 const swatch = new fabric.Rect({
 left: x,
 top: 50,
 width: 50,
 height: 50,
 fill: color,
 stroke: '#000000',
 strokeWidth: 1
 });
 canvas.add(swatch);
 x += 60;
 });

 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Extracted ' + colors.size + ' colors!');
}

17. Text to Path Converter

const obj = canvas.getActiveObject();
if (!obj || (obj.type !== 'i-text' && obj.type !== 'text')) {
 showDialog('Error', 'Select a text object first!');
} else {
 const text = obj.text;
 const fontSize = obj.fontSize;
 const fontFamily = obj.fontFamily;

 // Create temporary SVG
 const svgString = `
 <svg xmlns="http://www.w3.org/2000/svg" width="500" height="200">
 <text x="10" y="${fontSize}" font-family="${fontFamily}" font-size="${fontSize}" fill="${obj.fill}">
 ${text}
 </text>
 </svg>
 `;

 fabric.loadSVGFromString(svgString, function(objects, options) {
 const svg = fabric.util.groupSVGElements(objects, options);
 svg.set({
 left: obj.left,
 top: obj.top
 });
 canvas.add(svg);
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Text converted to path!');
 });
}

18. Smart Duplicate (No Overlap)

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 showPrompt('Duplicate', 'How many copies?', '5').then(result => {
 if (!result) return;

 const count = parseInt(result);
 let currentX = obj.left;
 let currentY = obj.top;

 for (let i = 1; i <= count; i++) {
 obj.clone(function(cloned) {
 // Move to right, if goes off canvas, move to next row
 currentX += obj.width * obj.scaleX + 20;

 if (currentX + obj.width * obj.scaleX > canvas.width) {
 currentX = 50;
 currentY += obj.height * obj.scaleY + 20;
 }

 cloned.set({
 left: currentX,
 top: currentY
 });
 canvas.add(cloned);
 });
 }

 canvas.renderAll();
 saveHistory();
 showDialog('Done', count + ' copies created without overlap!');
 });
}

19. Object Outline Generator

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Select an object first!');
} else {
 showPrompt('Outline', 'Enter outline thickness:', '10').then(result => {
 if (!result) return;

 const thickness = parseInt(result);

 obj.clone(function(cloned) {
 cloned.set({
 fill: 'transparent',
 stroke: obj.fill,
 strokeWidth: thickness,
 scaleX: obj.scaleX * 1.05,
 scaleY: obj.scaleY * 1.05
 });
 canvas.add(cloned);
 cloned.sendToBack();
 canvas.renderAll();
 saveHistory();
 showDialog('Done', 'Outline created!');
 });
 });
}

20. Canvas Statistics

const objects = canvas.getObjects();
const stats = {
 total: objects.length,
 rectangles: 0,
 circles: 0,
 triangles: 0,
 text: 0,
 images: 0,
 other: 0
};

objects.forEach(obj => {
 switch(obj.type) {
 case 'rect': stats.rectangles++; break;
 case 'circle': stats.circles++; break;
 case 'triangle': stats.triangles++; break;
 case 'i-text':
 case 'text': stats.text++; break;
 case 'image': stats.images++; break;
 default: stats.other++; break;
 }
});

const message = `
Total Objects: ${stats.total}
Rectangles: ${stats.rectangles}
Circles: ${stats.circles}
Triangles: ${stats.triangles}
Text: ${stats.text}
Images: ${stats.images}
Other: ${stats.other}

Canvas Size: ${canvas.width} x ${canvas.height}
Background: ${canvas.backgroundColor || 'transparent'}
`;

showDialog('Canvas Statistics', message);

Best Practices

1. Always Check for Selection

const obj = canvas.getActiveObject();
if (!obj) {
 showDialog('Error', 'Please select an object first!');
 return;
}
// ... your code

2. Always Call canvas.renderAll()

// After ANY modification
obj.set('fill', '#ff0000');
canvas.renderAll();  // REQUIRED!

3. Always Call saveHistory()

// After making changes you want to be undoable
canvas.add(newObject);
canvas.renderAll();
saveHistory();  // Enables undo/redo

4. Use Async/Await for Prompts

async function myPlugin() {
 const input1 = await showPrompt('Step 1', 'Enter value:');
 if (!input1) return;

 const input2 = await showPrompt('Step 2', 'Enter another value:');
 if (!input2) return;

 // Do something with input1 and input2
}

myPlugin();

5. Handle Errors Gracefully

try {
 // Your code here
 const obj = canvas.getActiveObject();
 obj.set('fill', '#ff0000');
 canvas.renderAll();
 saveHistory();
} catch (e) {
 showDialog('Error', 'Something went wrong: ' + e.message);
}

6. Validate User Input

showPrompt('Number', 'Enter a number:', '10').then(result => {
 if (!result) return;

 const num = parseFloat(result);
 if (isNaN(num)) {
 showDialog('Error', 'Please enter a valid number!');
 return;
 }

 // Use num
});

7. Give User Feedback

// Show what happened
showDialog('Success', 'Created 10 objects!');

// Show what was applied
showDialog('Color Applied', 'Applied color: #ff0000');

8. Clean Up After Animations

const interval = setInterval(() => {
 // Animation code
}, 100);

// Always clear intervals
setTimeout(() => {
 clearInterval(interval);
 showDialog('Done', 'Animation complete!');
}, 3000);

Tips & Tricks

Access Animation System

// Current frame
animState.currentFrame

// Total frames
animState.totalFrames

// Is playing?
animState.playing

// Go to frame
goToFrame(10);

// Add keyframe
addKeyframe(obj, animState.currentFrame);

Access Canvas State

// Current zoom
zoom

// Current tool
currentTool

// Current style
currentStyle.fill
currentStyle.stroke
currentStyle.strokeWidth

Iterate All Objects

canvas.getObjects().forEach(obj => {
 // Do something with each object
 obj.set('opacity', 0.5);
});
canvas.renderAll();

Find Objects by Type

const allRectangles = canvas.getObjects().filter(obj => obj.type === 'rect');
const allText = canvas.getObjects().filter(obj => obj.type === 'i-text' || obj.type === 'text');

Debugging

Log to Console

console.log('Object:', obj);
console.log('Canvas width:', canvas.width);

Inspect Object Properties

const obj = canvas.getActiveObject();
console.log('All properties:', obj);
console.log('Position:', obj.left, obj.top);
console.log('Size:', obj.width, obj.height);

Need Help?

  • Check the Fabric.js documentation
  • Experiment with small code snippets
  • Use console.log() to debug
  • Start with simple examples and build up