Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
JSON Canvas Viewer
https://github.com/hesprs/json-canvas-viewer
Admin
JSON Canvas Viewer is an extensible web-based viewer for JSON Canvas files with interactive pan and
...
Tokens:
18,787
Snippets:
180
Trust Score:
6.9
Update:
1 week ago
Context
Skills
Chat
Benchmark
77.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# JSON Canvas Viewer JSON Canvas Viewer is an extensible web-based viewer for JSON Canvas files (`.canvas`) from Obsidian. It provides interactive pan and zoom functionality, supports 100% of the JSON Canvas 1.0 specification, offers responsive design with mobile and touchpad adaptation, and features modern aesthetics with light and dark mode support. The library is TypeScript-native and supports lazy loading for performance optimization. The library is available as a monorepo with framework adapters for vanilla JavaScript/TypeScript, React, Preact, Vue 3, and a Vite plugin for build-time canvas parsing. It features a modular architecture allowing optional features like minimap navigation, zoom controls, mistouch prevention, and debug panels. The "chimp" version provides a bundled solution for quick trials via CDN, while the "full" version offers complete customizability and tree-shaking support for production use. ## JSONCanvasViewer Class The main viewer class that renders JSON Canvas content with interactive pan/zoom capabilities. ```typescript import { JSONCanvasViewer, Minimap, Controls } from 'json-canvas-viewer'; import canvasData from './demo.canvas'; // Basic instantiation const viewer = new JSONCanvasViewer({ container: document.getElementById('canvas-container'), canvas: canvasData, theme: 'dark', loading: 'lazy', attachmentDir: './attachments/', markdownParser: (md) => md, colors: { dark: { background: '#1a1a1a', text: '#ffffff', } } }, [Minimap, Controls]); // Optional modules // Control the viewer programmatically viewer.zoom(1.5); // Zoom by factor viewer.zoomToScale(2.0); // Zoom to exact scale viewer.pan({ x: 100, y: 50 }); // Pan by delta viewer.panToCoords({ x: 0, y: 0 }); // Pan to coordinates viewer.resetView(); // Fit all content in view viewer.changeTheme('light'); // Switch theme viewer.toggleFullscreen('enter'); // Enter fullscreen // Subscribe to lifecycle hooks viewer.onNodeActive.subscribe((node) => { console.log('Node selected:', node.id); }); viewer.onRefresh.subscribe(() => { console.log('Canvas rerendered'); }); // Load a different canvas viewer.load({ canvas: newCanvasData, attachmentDir: './new-attachments/' }); // Cleanup when done viewer.dispose(); ``` ## fetchCanvas Function Async function to fetch and parse canvas files at runtime (primarily for chimp version usage). ```typescript import { JSONCanvasViewer, fetchCanvas, parser } from 'json-canvas-viewer'; // Fetch canvas from URL const canvas = await fetchCanvas('./path/to/demo.canvas'); new JSONCanvasViewer({ container: document.body, canvas: canvas, markdownParser: parser, // Built-in sanitized markdown parser }); ``` ## Vite Plugin Configuration Build-time canvas parsing plugin that enables direct `.canvas` file imports. ```typescript // vite.config.ts import { defineConfig } from 'vite'; import canvas from 'vite-plugin-json-canvas'; import { marked } from 'marked'; export default defineConfig({ plugins: [ canvas({ // Optional: custom markdown parser parser: (md) => marked(md), }), // Or disable markdown parsing canvas({ parser: (md) => md, }), ], }); // In your application import canvasData from './diagram.canvas'; // TypeScript-aware import new JSONCanvasViewer({ container: document.body, canvas: canvasData, // Already parsed at build time }); ``` ## React Component React wrapper component with reactive props and custom node rendering support. ```tsx import { JSONCanvasViewerComponent, Minimap, Controls } from '@json-canvas-viewer/react'; import { useRef, useEffect } from 'react'; import canvas from './demo.canvas'; function CanvasPage() { const viewerRef = useRef(null); useEffect(() => { // Access viewer instance via ref if (viewerRef.current?.viewer) { viewerRef.current.viewer.resetView(); } }, []); return ( <JSONCanvasViewerComponent ref={viewerRef} canvas={canvas} theme="dark" attachmentDir="./attachments/" modules={[Minimap, Controls]} options={{ loading: 'lazy', shadowed: true, minimapCollapsed: false, controlsCollapsed: false, }} style={{ width: '100%', height: '600px' }} className="canvas-viewer" // Custom node renderers (optional) text={({ content, node, onActive }) => ( <div className="custom-text">{content}</div> )} link={({ content, node }) => ( <iframe src={content} title={node.id} /> )} /> ); } ``` ## Vue 3 Component Vue wrapper with reactive props, scoped slots for custom nodes, and native prerendering support. ```vue <script setup lang="ts"> import { ref } from 'vue'; import { JSONCanvasViewerComponent, Minimap, DebugPanel } from '@json-canvas-viewer/vue'; import canvas from './demo.canvas'; const viewerRef = ref(null); const currentTheme = ref<'light' | 'dark'>('light'); function toggleTheme() { currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light'; } function resetCanvas() { viewerRef.value?.viewer?.resetView(); } </script> <template> <div> <button @click="toggleTheme">Toggle Theme</button> <button @click="resetCanvas">Reset View</button> <Suspense> <JSONCanvasViewerComponent ref="viewerRef" :canvas="canvas" :theme="currentTheme" :modules="[Minimap, DebugPanel]" :options="{ loading: 'lazy' }" :is-prerendering="false" attachment-dir="./attachments/" > <!-- Custom node slots --> <template #text="{ content, node, onActive, onLoseActive }"> <div class="custom-text-node">{{ content }}</div> </template> <template #link="{ content, node }"> <iframe :src="content" :title="node.id" /> </template> </JSONCanvasViewerComponent> </Suspense> </div> </template> ``` ## Preact Component Preact wrapper with the same API as the React component. ```tsx import { JSONCanvasViewerComponent, MistouchPreventer } from '@json-canvas-viewer/preact'; import canvas from './demo.canvas'; export function App() { return ( <JSONCanvasViewerComponent canvas={canvas} theme="light" modules={[MistouchPreventer]} options={{ preventMistouchAtStart: true, mistouchPreventerBannerText: 'Tap to interact', }} style={{ width: '100%', height: '500px' }} /> ); } ``` ## renderToString Function Server-side rendering function for generating static HTML content. ```typescript import { renderToString } from 'json-canvas-viewer'; import { JSONCanvasViewerComponent } from '@json-canvas-viewer/react'; import canvas from './demo.canvas'; // Server component (e.g., Next.js RSC) export default async function CanvasPage() { const prerenderHtml = await renderToString({ canvas: canvas, attachmentDir: './attachments/', attachments: { 'image.png': '/static/image.png', }, parser: (md) => md, // Optional markdown parser }); return ( <main> <JSONCanvasViewerComponent prerenderHtml={prerenderHtml} canvas={canvas} attachmentDir="./attachments/" /> </main> ); } ``` ## Minimap Module Navigation minimap displayed at the bottom-right corner. ```typescript import { JSONCanvasViewer, Minimap } from 'json-canvas-viewer'; import canvas from './demo.canvas'; const viewer = new JSONCanvasViewer({ container: document.body, canvas: canvas, minimapCollapsed: false, // Start expanded (default) }, [Minimap]); // Toggle minimap visibility viewer.toggleMinimapCollapse(); ``` ## Controls Module Zoom and fullscreen controls displayed at the top-right corner. ```typescript import { JSONCanvasViewer, Controls } from 'json-canvas-viewer'; import canvas from './demo.canvas'; const viewer = new JSONCanvasViewer({ container: document.body, canvas: canvas, controlsCollapsed: false, // Start expanded (default) }, [Controls]); // Toggle controls visibility viewer.toggleControlsCollapse(); ``` ## MistouchPreventer Module Prevents accidental canvas interactions when embedded in scrollable pages. ```typescript import { JSONCanvasViewer, MistouchPreventer } from 'json-canvas-viewer'; import canvas from './demo.canvas'; const viewer = new JSONCanvasViewer({ container: document.body, canvas: canvas, preventMistouchAtStart: true, // Start locked mistouchPreventerBannerText: 'Click to unlock canvas', }, [MistouchPreventer]); // Programmatic control viewer.startMistouchPrevention(); // Lock canvas viewer.endMistouchPrevention(); // Unlock canvas ``` ## DebugPanel Module Developer panel showing scale and coordinates at the bottom-left corner. ```typescript import { JSONCanvasViewer, DebugPanel } from 'json-canvas-viewer'; import canvas from './demo.canvas'; const viewer = new JSONCanvasViewer({ container: document.body, canvas: canvas, }, [DebugPanel]); // Manually update debug info viewer.updateDebugPanel(); ``` ## Custom Node Components Override default node rendering with custom components. ```typescript import { JSONCanvasViewer } from 'json-canvas-viewer'; import canvas from './demo.canvas'; new JSONCanvasViewer({ container: document.body, canvas: canvas, nodeComponents: { text: (container, content, node, onBeforeUnmount, onActive, onLoseActive) => { const div = document.createElement('div'); div.innerHTML = content; div.className = 'custom-text-node'; container.appendChild(div); onActive.subscribe(() => div.classList.add('active')); onLoseActive.subscribe(() => div.classList.remove('active')); onBeforeUnmount.subscribe(() => div.remove()); }, link: (container, url, node, onBeforeUnmount) => { const iframe = document.createElement('iframe'); iframe.src = url; iframe.style.cssText = 'width:100%;height:100%;border:none;'; container.appendChild(iframe); onBeforeUnmount.subscribe(() => iframe.remove()); }, image: (container, filePath, node, onBeforeUnmount) => { const img = document.createElement('img'); img.src = filePath; img.alt = node.id; container.appendChild(img); onBeforeUnmount.subscribe(() => img.remove()); }, }, }); ``` ## Custom Module Development Create custom modules using the BaseModule class with dependency injection. ```typescript import { BaseModule, type BaseArgs, type BaseOptions, internal, canvasUtils } from 'json-canvas-viewer'; const { DataManager, Controller } = internal; interface MyModuleOptions extends BaseOptions { showNotifications?: boolean; notificationPosition?: 'top' | 'bottom'; } interface MyModuleAugmentation { showNotification: MyModule['notify']; } class MyModule extends BaseModule<MyModuleOptions, MyModuleAugmentation> { private dataManager: DataManager; private notificationEl: HTMLDivElement | null = null; constructor(...args: BaseArgs) { super(...args); // Dependency injection this.dataManager = this.container.get(DataManager); const controller = this.container.get(Controller); // Subscribe to hooks controller.hooks.onRefresh.subscribe(this.onRefresh); // Create UI elements this.notificationEl = document.createElement('div'); this.notificationEl.className = 'my-notification'; this.dataManager.data.container.appendChild(this.notificationEl); // Access custom options if (this.options.showNotifications) { this.notify('Module loaded!'); } // Register lifecycle hooks this.onStart(() => console.log('Viewer started')); this.onDispose(this.cleanup); // Augment main instance this.augment({ showNotification: this.notify }); } private onRefresh = () => { const { scale, offsetX, offsetY } = this.dataManager.data; console.log(`View: scale=${canvasUtils.round(scale, 2)}, offset=(${offsetX}, ${offsetY})`); }; notify = (message: string) => { if (this.notificationEl) { this.notificationEl.textContent = message; } }; private cleanup = () => { this.notificationEl?.remove(); this.notificationEl = null; }; } // Usage const viewer = new JSONCanvasViewer({ container: document.body, canvas: canvasData, showNotifications: true, notificationPosition: 'top', }, [MyModule]); viewer.showNotification('Hello from augmented method!'); ``` ## Chimp Version (CDN Quick Start) Bundled version for quick trials without build tools. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>JSON Canvas Viewer</title> <style> body, html { margin: 0; padding: 0; width: 100%; height: 100%; } </style> </head> <body></body> <script type="module"> import { JSONCanvasViewer, parser, fetchCanvas, Minimap, Controls, MistouchPreventer, DebugPanel } from 'https://unpkg.com/json-canvas-viewer'; const canvas = await fetchCanvas('./demo.canvas'); const viewer = new JSONCanvasViewer({ container: document.body, canvas: canvas, markdownParser: parser, // Sanitized markdown parser theme: 'light', loading: 'normal', }, [Minimap, Controls, MistouchPreventer, DebugPanel]); // Global access for debugging window.viewer = viewer; </script> </html> ``` ## Summary JSON Canvas Viewer is designed for embedding interactive Obsidian canvas files into web applications. Primary use cases include documentation sites displaying knowledge graphs, portfolio websites showcasing visual workflows, note-taking applications with canvas preview, and any web project requiring interactive diagram visualization. The library supports both quick prototyping via CDN and production-ready builds with full tree-shaking. Integration follows a consistent pattern across all frameworks: import the viewer and desired modules, provide a canvas data object (either fetched at runtime or imported at build-time via Vite plugin), configure options like theme and attachments, and optionally customize node rendering through slots or render props. The modular architecture allows adding features like minimap navigation, zoom controls, and scroll protection without impacting bundle size when not used. For server-side rendering scenarios, the `renderToString` function generates static HTML that hydrates into the full interactive viewer on the client.