Building an Open-Source Design Platform Like Doory

Building an Open-Source Design Platform Like Doory
Photo by Tima Miroshnichenko on Pexels

Building an Open-Source Design Platform Like Doory

The GitHub trending list just surfaced something interesting: Doory, an open-source alternative to Canva. While another “open-source X” project might sound like just another weekend hackathon output, what makes Doory worth your attention isn’t the marketing pitch—it’s what’s hiding under the hood. This project offers a masterclass in building browser-based design tools, and whether you’re building internal tools for your team or dreaming of launching your own SaaS, the architectural patterns here are pure gold.

Let’s dissect what makes a design platform like this tick, then build something real you can actually deploy.

Table of Contents

Understanding the Canvas API Foundation

Every browser-based design tool starts with a fundamental decision: Canvas API or SVG? Doory and similar platforms typically lean on HTML5 Canvas for performance, especially when users are dragging, rotating, and layering dozens of elements simultaneously. The Canvas API gives you pixel-level control and blazing fast rendering—critical when someone’s moving a text box across a 4K poster design.

The Canvas element acts as a bitmap surface where you draw programmatically. Unlike DOM manipulation, canvas operations happen in immediate mode—once you draw something, it’s just pixels. There’s no “text object” you can query later. This sounds limiting until you realize it’s also what makes canvas so fast.

⚠️ Common Mistake: Developers new to canvas often redraw the entire scene on every frame. Instead, implement dirty rectangle tracking to only redraw changed regions. Your users’ laptop fans will thank you.

Here’s a minimal canvas implementation showing how you’d handle a draggable rectangle—the building block of any design tool:

<!DOCTYPE html>
<html>
<body>
<canvas id="designCanvas" width="800" height="600" style="border:1px solid #ccc;"></canvas>
<script>
// Basic draggable rectangle on canvas - foundation for design tools
const canvas = document.getElementById('designCanvas');
const ctx = canvas.getContext('2d');

let rect = { x: 100, y: 100, width: 200, height: 150, isDragging: false, dragOffsetX: 0, dragOffsetY: 0 };

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#6d28d9';
  ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
  ctx.strokeStyle = '#db2777';
  ctx.lineWidth = 3;
  ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
}

canvas.addEventListener('mousedown', (e) => {
  const mouseX = e.offsetX;
  const mouseY = e.offsetY;
  if (mouseX >= rect.x && mouseX <= rect.x + rect.width && 
      mouseY >= rect.y && mouseY <= rect.y + rect.height) {
    rect.isDragging = true;
    rect.dragOffsetX = mouseX - rect.x;
    rect.dragOffsetY = mouseY - rect.y;
  }
});

canvas.addEventListener('mousemove', (e) => {
  if (rect.isDragging) {
    rect.x = e.offsetX - rect.dragOffsetX;
    rect.y = e.offsetY - rect.dragOffsetY;
    draw();
  }
});

canvas.addEventListener('mouseup', () => { rect.isDragging = false; });
draw();
</script>
</body>
</html>

This 40-line snippet contains the core interaction pattern you’ll find in Figma, Canva, and yes, Doory. The hit detection, drag offset calculation, and render loop form the foundation. From here, you’d layer on rotation matrices, z-index management, and selection states.

Component Architecture for Design Tools

Modern design platforms don’t build everything from scratch. Doory leverages React (you can verify this in its package.json), which makes sense—you need a robust component system to manage the chaos. The left panel with templates, the top toolbar, the properties inspector on the right, the canvas in the center—each is an isolated component with its own state and lifecycle.

The trick is separating your UI components from your canvas entities. Your React components handle user interactions and property panels. The canvas renders the actual design elements. A common pattern involves maintaining a design state object that both systems reference. When someone changes text color in the properties panel (React), that updates the state, which triggers a canvas redraw.

If you’re looking to level up your React architecture skills for projects like this, Coursera offers courses specifically on advanced React patterns that cover exactly these state synchronization challenges.

The Layer System

Every element in your design exists in a layer stack. You’ll typically model this as an array of objects:

// Layer management system for multi-element canvas designs
const designState = {
  layers: [
    { id: 'bg-1', type: 'rectangle', x: 0, y: 0, width: 800, height: 600, fill: '#ffffff', zIndex: 0 },
    { id: 'text-1', type: 'text', x: 200, y: 150, content: 'Hello Design', fontSize: 48, fill: '#1e1b4b', zIndex: 1 },
    { id: 'shape-1', type: 'circle', x: 400, y: 300, radius: 80, fill: '#db2777', zIndex: 2 }
  ],
  selectedLayer: null
};

function renderAllLayers(ctx, state) {
  // Sort by zIndex to render in correct order
  const sortedLayers = [...state.layers].sort((a, b) => a.zIndex - b.zIndex);
  
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  
  sortedLayers.forEach(layer => {
    if (layer.type === 'rectangle') {
      ctx.fillStyle = layer.fill;
      ctx.fillRect(layer.x, layer.y, layer.width, layer.height);
    } else if (layer.type === 'text') {
      ctx.fillStyle = layer.fill;
      ctx.font = `${layer.fontSize}px Arial`;
      ctx.fillText(layer.content, layer.x, layer.y);
    } else if (layer.type === 'circle') {
      ctx.fillStyle = layer.fill;
      ctx.beginPath();
      ctx.arc(layer.x, layer.y, layer.radius, 0, Math.PI * 2);
      ctx.fill();
    }
    
    // Highlight selected layer
    if (state.selectedLayer === layer.id) {
      ctx.strokeStyle = '#f59e0b';
      ctx.lineWidth = 2;
      ctx.setLineDash([5, 5]);
      // Draw selection bounds based on layer type
      ctx.strokeRect(layer.x - 5, layer.y - 5, (layer.width || layer.radius * 2) + 10, (layer.height || layer.radius * 2) + 10);
      ctx.setLineDash([]);
    }
  });
}

Notice how the layer model separates concerns. Each layer is just data. The rendering function is pure—given the same state, it produces the same output. This makes debugging infinitely easier and enables features like undo/redo through state snapshots.

State Management at Scale

Here’s where weekend projects die and production apps are born. When a user has 50 layers, each with position, rotation, opacity, filters, and effects, your state tree gets complex fast. Doory’s architecture decisions around state management directly impact performance and developer sanity.

Most production design tools use either Redux or Zustand for predictable state updates. The key insight: treat your design state as immutable. Never mutate layer properties directly. Always create new state objects. This enables time-travel debugging and makes React’s reconciliation efficient.

💡 Pro Tip: Use Immer with your state management library. It lets you write “mutating” code that actually produces immutable updates. Your reducers become readable while staying performant.

The export functionality is where you’ll appreciate clean state management. When someone clicks “Download as PNG,” you’re not scraping the DOM—you’re rendering your state object to a fresh canvas at the target resolution. Need a 4K export of a design created at 1080p? Just scale your state coordinates and re-render. Clean state makes this trivial.

Building a Working Example

Let’s tie this together with something you can actually run. You don’t need a full React setup to grasp the patterns. A vanilla JavaScript implementation reveals the core mechanics more clearly anyway. For those wanting to explore the data visualization side of canvas rendering, DataCamp has interactive courses on D3.js and canvas-based charting that complement these design tool concepts perfectly.

The critical features for any minimal viable design tool: layer selection, property editing, and export. Start there, iterate from user feedback, and resist the urge to build every feature Figma has on day one.

Performance Optimization

Canvas performance degrades quickly with complexity. Render 100 layers naively and you’ll hit 15fps on modest hardware. The solution isn’t prettier code—it’s smarter rendering. Implement an offscreen canvas for complex layers that don’t change often. Render them once to the offscreen buffer, then composite that buffer onto your main canvas. Background images, static text blocks, watermarks—all candidates for offscreen rendering.

Another optimization: request animation frame throttling. Don’t redraw on every mouse move event—they fire faster than the screen refreshes. Instead, set a dirty flag on events and redraw in a RAF loop. This caps your redraws at 60fps max, smoothing performance dramatically.

Deployment Considerations

Open-source alternatives like Doory face an interesting deployment question: do you host a SaaS version or purely distribute self-hosted? The answer shapes your architecture. A self-hosted tool can be more resource-intensive since it runs on the user’s infrastructure. A SaaS needs aggressive optimization and probably a backend service for handling exports, templates, and user assets.

For the canvas-heavy frontend, consider edge deployment through Cloudflare Pages or Vercel. Static site hosting works perfectly since the heavy lifting happens client-side. Add a serverless function for handling exports larger than what client browsers can process—those 300MB PSD file generations need real compute.

Asset management becomes critical at scale. User-uploaded images, fonts, templates—this isn’t data you want clogging your React bundle. Use a CDN, implement lazy loading, and consider image optimization services that automatically serve WebP to compatible browsers while falling back to PNG for older clients.

Stay in the loop — join 125,000+ IT professionals following Networkyy: Instagram · Facebook · Threads · Medium

The Road Forward

Doory represents more than just another Canva clone—it’s a reference implementation for anyone building browser-based creative tools. The patterns you’ve explored here extend beyond design platforms. Drawing apps, diagramming tools, data visualization dashboards, collaborative whiteboards—they all share these foundational concepts.

The open-source nature means you can actually read the implementation, fork it, and adapt it for your specific use case. Maybe you need an internal design tool for your marketing team that integrates with your brand asset management system. Maybe you’re building a specialized diagram editor for network topology. The canvas rendering, layer management, and state synchronization patterns transfer directly.

Start small. Build the draggable rectangle example. Add text support. Implement layer reordering. Each feature teaches you something about the architectural decisions that projects like Doory navigate. Before you know it, you’re not just using design tools—you’re building them.

🔥 RECOMMENDED FOR YOU

Master React for Design Tools

Learn advanced React patterns, state management architectures, and component design that power production design platforms. Build portfolio-worthy projects with real-world techniques used in tools like Doory.

Scroll to Top