Skip to content

anikrish05/Couples-Canvas

Repository files navigation

Couple's Canvas

A real-time collaborative drawing app where two people can draw together on a shared canvas. Built with Expo and Convex.

Live demo: candle-take-home.vercel.app

Features

  • Real-time collaboration — Draw together on the same canvas with live updates
  • Shareable sessions — Each canvas gets a unique link; share it to invite someone
  • Responsive canvas — Drawings scale proportionally across any screen size or orientation
  • Drawing tools — Pen, eraser, and eyedropper
  • Color picker — Popup spectrum with tap and slide support
  • Brush size slider — Adjustable stroke width (1–20px)
  • Undo & clear — Undo your last stroke or clear the entire canvas
  • Pinch-to-zoom — Two-finger zoom and pan for drawing fine details, double-tap to reset
  • Live cursors — See your collaborator's cursor and initials in real time
  • Connection indicator — Shows connection status and collaborator count
  • Smooth strokes — Pressure-simulated freehand drawing via perfect-freehand
  • GPU-accelerated rendering — Skia-powered canvas for smooth performance

Tech Stack

Layer Technology
Framework Expo (React Native)
Routing Expo Router (file-based)
Drawing @shopify/react-native-skia
Stroke smoothing perfect-freehand
Gestures react-native-gesture-handler
Backend & real-time sync Convex
Identity persistence AsyncStorage

How It Works

All stroke coordinates are normalized to a 0–1 range before being stored. This means drawings look correct on any screen size — a stroke drawn on a phone renders proportionally on a tablet or desktop browser.

Strokes are sent to Convex when a gesture ends (not per-point), with optimistic updates so the drawer sees their stroke immediately without waiting for a server round-trip. Remote collaborators receive updates through Convex's real-time subscriptions.

Cursor positions are broadcast via a separate presence system, throttled to one update per 80ms, with a 30-second TTL to automatically clean up stale cursors.

Local Setup

Prerequisites

Steps

# Clone the repo
git clone https://github.com/anikrish05/CandleTakeHome.git
cd CandleTakeHome/couples-canvas

# Install dependencies
npm install

# Set up Convex
npx convex dev
# This will prompt you to create a project and set CONVEX_URL in .env.local

# Start the app
npx expo start

Press w to open in a web browser, or scan the QR code with Expo Go for mobile.

Environment Variables

Variable Description
EXPO_PUBLIC_CONVEX_URL Your Convex deployment URL (set automatically by npx convex dev)

Project Structure

app/
  _layout.tsx          # Root layout with Convex provider
  index.tsx            # Landing page (create/join canvas)
  canvas/[sessionId].tsx  # Canvas session page

components/
  CanvasRoom.tsx       # Main orchestrator (state, mutations, layout)
  DrawingCanvas.tsx    # Skia canvas with gesture handling
  Toolbar.tsx          # Bottom toolbar (tools, brush size, colors)
  ColorSpectrum.tsx    # Popup color picker with tap and slide
  CursorOverlay.tsx    # Remote cursor display (zoom-aware)
  ShareButton.tsx      # Share link generation
  ConnectionIndicator.tsx  # Connection status pill

hooks/
  useIdentity.ts       # Persistent random user identity
  usePresence.ts       # Cursor broadcasting & subscription
  useCanvasSize.ts     # Canvas dimension tracking
  useCanvasZoom.ts     # Pinch-to-zoom state & gesture handling

lib/
  strokeUtils.ts       # Stroke path generation & eyedropper hit testing
  drawingUtils.ts      # Coordinate normalization
  identity.ts          # Identity generation & persistence
  constants.ts         # Colors and defaults
  types.ts             # TypeScript interfaces

convex/
  schema.ts            # Database schema (canvases, strokes, presence)
  canvases.ts          # Canvas CRUD
  strokes.ts           # Stroke mutations & queries
  presence.ts          # Cursor presence system

Design Decisions

  • Normalized coordinates — Storing points as 0–1 values rather than pixels means drawings are resolution-independent. A stroke drawn on a 400px-wide canvas renders identically on a 1200px-wide one.
  • Optimistic updates — Every mutation (add stroke, undo, clear) updates the local Convex cache immediately, so the UI never feels laggy even with network latency.
  • Stroke-on-end, not per-point — Points accumulate locally during a gesture and are sent as a single mutation when the gesture ends. This reduces server load significantly compared to per-point mutations.
  • Eraser as colored stroke — The eraser draws strokes in the canvas background color rather than performing true deletion. This keeps the rendering pipeline simple and consistent.
  • Convex for real-time — Convex's reactive queries eliminate the need to manage WebSocket connections, reconnection logic, or manual cache invalidation.
  • Platform-aware zoom — On native, RNGH pinch/pan gestures handle zoom reliably. On web, native touchstart/touchmove events bypass RNGH's weaker multi-touch support. A shared isPinching flag cancels any in-progress draw stroke the instant a second finger lands, preventing draw/zoom conflicts.

What I Focused On

I prioritized real-time collaboration quality and cross-device consistency. The normalized coordinate system, optimistic updates, and throttled presence broadcasting all serve the goal of making two people drawing together feel seamless and responsive. I also focused on matching Candle's aesthetic — warm colors, soft shadows, rounded corners — to show design sensibility alongside technical depth.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors