a painterly vector playground with jelly motion, brush-based shapes, and interactive effects
inspired by excalidraw, miro, figma
demo -- https://brushie.vercel.app/
initially planned as a proper high-performancewhiteboard/vector editor
but after implementing fundamental features i realized that wanna gamify it a bit and take a deeper dive into shaders
so dont measure the performance here hahahah
the app is not about productivity but just vibing
planned to create a proper 1-2 minute experience by turning it into a game but maaaybe not
also syncing became not a priority cuz of that, didnt test that well
there are a lot of bugs and missing features, look at the notes. also use project search for "TODO:". and left some "NOTE:" comments
use it as a realworld vue3+threejs+glsl+yjs example or as a reference app -- you can see the documentation below, hope it helps ๐๐ค why threejs and not pixijs? well... forgot that it existed xD
glad to any PRs and feel free to ask any questions on linkedin/telegramใ thanks for the star and support!
- about
- video
- screenshots
- features
- how to use (for users)
- how to understand the code (for devs)
- installation
- notes
todo
- draw with pencil, lines, rectangles -- brush-based + painterly
- draft mode for line/arrow with real-time preview before creation
- radial menu on right click: pick color, delete shape
- shape transformations: move, resize, rotate with bounding box handles
- clipboard support: copy & paste shapes with ctrl/cmd + c/v
- undo/redo system (ctrl/cmd + z/y)
- multi-select by dragging
- interactive eraser
- dynamic grid dots (zoom dependent)
- click on shapes to interact with them
- destroy shapes stroke-by-stroke with juicy particles
- reactive jelly motion and stroke animation
- painterly materials with custom fragment/vertex shaders
- inertial UI with floaty toolbars
- zoom and pan with scroll and modifiers
- tooltip hints on hover
- separate overlay canvas for selections, cursors, bounding boxes
- post-processing and mesh highlight for subtle visual feedback
- velocity-based jelly deformation
- quadtree-based shape lookup for fast hit detection
- modular feature-based architecture (not strict FSD)
- canvas rendered with three.js + raw glsl shaders (custom materials for a background, for painterly+jelly shapes, and for particles)
- debug canvas menu (memory, fps, draw calls, vertex count, mesh count). disabled by default
- multiplayer sync with yjs
- remote cursors and selections
- network provider switch: websocket or webrtc
- undo/redo across clients
- shape locking & awareness system
- persistent shape storage in indexeddb
- offline support and pwa integration
- save/load from browser cache
- draw shapes using tools in the top-left corner
- resize, move, rotate
- right-click a shape to access radial menu (color, delete)
- drag to select multiple
- ctrl/cmd + z/y to undo/redo
- scroll to zoom, ctrl/shift/cmd + scroll to pan
- hover on a button to see a tooltip
- game mode allows interaction with rect shapes (click to emit particles and remove shape stroke by stroke)
used modular feature-based architecture
modules/features reference
-
editor-core
base visual components and rendering (3d canvas, debug, materials, shaders)
hooks for animations, camera control, effects, rendering, zooming -
editor-game
game mode mechanics, layer logic, and interactive shape gameplay. in a very wip shape
and actually making it as a game would require some arch changes
to prevent fundamental editor features be tightly coupled with game logic -
editor-input
user input handling: mouse, 3d tools, cursors -
editor-shapes
all about shapes: radial menu, hooks for creating, editing, finding, selecting, clipboard, draft, bounding box -
editor-ui
canvas ui components (container, toolbar) and hooks for tool management. and inertial ui (its looking very fun!!!) -
editor-sync
yjs syncing integration, network providers (webrtc/websocket), remote cursors, locked shapes, drafts, undo/redo support
- shared constants, hooks, services (quadtree), shaders, types
- and a couple of ui components -- menu, button, tooltip
- helper utils for geometry, lerp, transforms, easing
- progressive web app support
hooks reference
useCanvasBackground
-- used to make threejs plane to make proper retro pass. initially there was just css backgrounduseJellyVelocityAnimation
-- calculates jelly velocity for animated distortion based on motionuseGridDots
-- renders subtle grid dots on canvas for alignment and spatial referenceuseMeshAnimation
-- fade/brush animations for shapesuseMeshesManager
-- central shape-to-mesh syncing with create/update/remove logicuseMeshHighlight
-- handles mesh color shift effects on hover or selectionuseOverlayCanvas
-- overlay canvas for selections, cursors, bounding boxesusePan3D
-- pan logic for 3D canvasuseParticleSystem
-- manages particles, destruction effectsusePointer3DTools
-- 3d helpers (ndc, raycast, world to screen, screen to world)usePostProcessing
-- applies retro effects using composer passesuseResizeHandler
-- listens to window size changes and updates viewportuseWebGlRenderer
-- sets up threejs contextuseZoom3D
-- manages zoom and camera
useGameLayer
-- mounts additional game layer for interactivityuseGameMode
-- game mode stateusePainterlyInteractions
-- handles interaction logic with painterly shapes (click-to-destroy)
useInputEvents
-- global mouse event capture and delegation per toolusePointerCursor
-- manages cursor style and snap updates
useBoundingBox
-- provides bounding box handles for shape/group transforms: move, resize, rotateuseEraserDraft
-- eraser draftuseLineDraft
-- line draftusePencilDraft
-- pencil draftuseShapeClipboard
-- handles copy/paste with local clipboarduseShapeCore
-- shape creation, deletion, update logicuseShapeDraft
-- draft component that handles sync and encapsulates line/pencil/eraser logicuseShapeFinder
-- finds shapes at pointer position using quadtreeuseShapeRadialMenu
-- radial menu logic with color/delete optionsuseShapeSelection
-- shape selection states
useInertialUI
-- floaty motion for UI panelsuseToolManager
-- global state for selected tool
useLockedShapes
-- syncs locked shape state across clientsuseRemoteCursors
-- renders real-time cursors from other usersuseRemoteSelection
-- shows other users' shape selectionsuseSyncDraftShapes
-- syncs draft shapes across clientsuseSyncShapes
-- syncs shapes across clientsuseUndoRedo
-- undo/redo logicuseYjs
-- yjs setup and sync config
useOnline
-- tracks network online/offline state
npm install
npm run dev
# or desktop version
npm run tauri dev
# and also start the server with
cd ./node_modules/y-webrtc
node ./bin/server.js
planned a lot more but realized wanna do something else. took ~2-3 weeks
bugs:
- recreating line draft and cuz of that seeing material recreation every time
- fix multiple pencils in one position click detection (make click transparent?)
- undo/redo after shape moving - not working
- undo action removes many shapes at once (groups them?)
- do not save drafts in indexeddb
- wrong bounding box if another user moving a line
important todo:
- use https://shihn.ca/posts/2020/resizing-rotated-elements/ for resize rotated
- update shape color sync + saveShapeDB
- useEraserDraft - sync erasedIDs. and if was erasing and pressed escape at the same time then it doesnt removing draft shape on another client
- recheck offline
not important/planned for the future:
- shape z-index layers
- shape customization
- shape draft
- useShapeSelection - lasso selection
- useToolManager - custom tools
- useOverlayCanvas - multitouch
- useZoom - sync zoom like figma
- usePencilDraft - pressure, color lineWidth
- useBoundingBox - snap
- render - instanced mesh
- usePencilDraft - excalidraw drawing
- add vr support
- add text tool
- proper game mode (spawn new shape with timer ?)