Claude Code से Canvas विकास: HiDPI, RAF, Pointer Events और परीक्षण
Claude Code से भरोसेमंद Canvas UI बनाएं: HiDPI, requestAnimationFrame, Pointer Events, state और Playwright जांच।
Canvas में असली चुनौती
Canvas ब्राउज़र का वह drawing surface है जहाँ आप chart, particle effect, image annotation, छोटा game, simulation या custom visual बना सकते हैं। यह बहुत लचीला है, लेकिन DOM की तरह अपने आप layout, state और input नहीं संभालता। इसलिए Claude Code से सिर्फ “एक अच्छा Canvas animation बनाओ” कहने पर अक्सर ऐसा demo मिलता है जो desktop पर ठीक दिखता है, पर phone पर blur, touch issue या horizontal scroll दे देता है।
Production quality के लिए कुछ बातें पहले तय करनी पड़ती हैं: HiDPI screen पर line sharp है या नहीं, requestAnimationFrame loop duplicate तो नहीं हो रहा, mouse/touch/pen input एक ही flow में है या नहीं, और Canvas article text, ads या CTA को नीचे तो नहीं धकेल रहा। Claude Code तभी उपयोगी बनता है जब उसे code के साथ constraints और verification भी दिए जाएं।
संबंधित लेख: Claude Code animation guide, Claude Code Three.js 3D guide, और Claude Code data visualization। Official docs के लिए Claude Code Docs, MDN Canvas API, requestAnimationFrame, Pointer Events, और Playwright screenshots देखें।
Claude Code के लिए अच्छा prompt
HiDPI का मतलब high pixel density screen है। एक CSS pixel कई physical pixels में दिख सकता है। अगर Canvas की CSS width बदलती है लेकिन अंदर का canvas.width और canvas.height नहीं बदलते, तो browser छोटी image को stretch करता है और drawing blur हो जाती है।
Canvas 2D demo implement करें।
Requirements:
- CSS pixels और internal Canvas pixels अलग रखें, devicePixelRatio support करें
- requestAnimationFrame से render करें और dt को clamp करें
- Pointer Events से mouse, touch और pen input संभालें
- drawing state एक state object में रखें; render(ctx) सिर्फ draw करे
- ResizeObserver से container size follow करें
- 375px mobile width पर horizontal scroll न हो
- Playwright से visibility, non-blank pixels, screenshot और mobile width test करें
- बदली हुई files, risks, failure cases और manual checks बताएं
इस तरह Claude Code सिर्फ सुंदर effect नहीं बनाता, बल्कि maintainable rendering system बनाता है।
Architecture पहले तय करें
Canvas code को input, state update, animation update, render और verification में बांटना बेहतर रहता है।
Pointer Events
|
v
input handler ---> state update ---> update(dt)
|
ResizeObserver ---> resize(dpr) v
render(ctx)
|
v
Playwright checks
render(ctx) में event listener register न करें, DOM न बदलें और नया animation loop शुरू न करें। वह सिर्फ state पढ़े और draw करे। इससे undo, eraser, WebGL fallback और screenshot test जोड़ना आसान हो जाता है।
उपयोग के उदाहरण
पहला use case data visualization है। Live trace, dense scatter plot, audio waveform, animated map overlay या custom educational graphic के लिए Canvas अच्छा विकल्प है। Claude Code loop बना सकता है, पर empty data, loading state और mobile layout आपको check कराना होगा।
दूसरा use case image annotation है। Screenshot review, design feedback या learning platform में line, arrow, rectangle, label और undo चाहिए। Pointer Events mouse, finger और stylus को एक ही path में लाते हैं। Supported device पर pressure से line width बदल सकती है।
तीसरा use case education और light games है। Physics demo, typing trainer, vocabulary cards और particle simulation frame-based state से अच्छी तरह चलते हैं। लेकिन route change के बाद requestAnimationFrame बंद न हुआ तो hidden CPU usage बढ़ता है।
चौथा use case product page preview है। User drag करके color, option या flow समझ सकता है। यह monetization में मदद करता है, बशर्ते Canvas CTA को hide या push न करे।
चलने वाला example
इसे HTML file में रखें और browser में खोलें। ctx.setTransform हर resize पर scale reset करता है, इसलिए ctx.scale की repeated accumulation नहीं होती।
<style>
body { margin: 0; display: grid; min-height: 100vh; place-items: center; background: #111827; }
canvas { width: min(100%, 720px); aspect-ratio: 16 / 9; display: block; background: #020617; border: 1px solid #374151; border-radius: 8px; touch-action: none; }
</style>
<canvas id="demo" aria-label="Canvas particle demo"></canvas>
<script type="module">
const canvas = document.querySelector("#demo");
const ctx = canvas.getContext("2d");
const state = { width: 1, height: 1, dpr: 1, last: 0, pointer: { x: 0, y: 0, down: false }, dots: [] };
function resize() {
const rect = canvas.getBoundingClientRect();
state.width = Math.max(1, rect.width);
state.height = Math.max(1, rect.height);
state.dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.round(state.width * state.dpr);
canvas.height = Math.round(state.height * state.dpr);
ctx.setTransform(state.dpr, 0, 0, state.dpr, 0, 0);
}
function point(event) {
const rect = canvas.getBoundingClientRect();
return { x: event.clientX - rect.left, y: event.clientY - rect.top, pressure: event.pressure || 0.5 };
}
function emit(x, y, pressure = 0.5) {
for (let i = 0; i < 8; i += 1) {
const angle = Math.random() * Math.PI * 2;
const speed = 90 + Math.random() * 180;
state.dots.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 1, size: 4 + pressure * 8 });
}
state.dots = state.dots.slice(-360);
}
canvas.addEventListener("pointerdown", (event) => {
canvas.setPointerCapture(event.pointerId);
const p = point(event);
state.pointer = { x: p.x, y: p.y, down: true };
emit(p.x, p.y, p.pressure);
});
canvas.addEventListener("pointermove", (event) => {
const events = event.getCoalescedEvents ? event.getCoalescedEvents() : [event];
for (const item of events) {
const p = point(item);
state.pointer.x = p.x;
state.pointer.y = p.y;
if (state.pointer.down) emit(p.x, p.y, p.pressure);
}
});
canvas.addEventListener("pointerup", () => (state.pointer.down = false));
canvas.addEventListener("pointercancel", () => (state.pointer.down = false));
function frame(now) {
const dt = state.last ? Math.min((now - state.last) / 1000, 0.033) : 0;
state.last = now;
for (const dot of state.dots) {
dot.vy += 220 * dt;
dot.x += dot.vx * dt;
dot.y += dot.vy * dt;
dot.life -= dt;
}
state.dots = state.dots.filter((dot) => dot.life > 0);
ctx.clearRect(0, 0, state.width, state.height);
ctx.fillStyle = "#020617";
ctx.fillRect(0, 0, state.width, state.height);
for (const dot of state.dots) {
ctx.fillStyle = `rgba(56,189,248,${dot.life})`;
ctx.beginPath();
ctx.arc(dot.x, dot.y, dot.size * dot.life, 0, Math.PI * 2);
ctx.fill();
}
ctx.fillStyle = "#e5e7eb";
ctx.fillText(`dpr ${state.dpr.toFixed(2)} / dots ${state.dots.length}`, 16, 24);
requestAnimationFrame(frame);
}
new ResizeObserver(resize).observe(canvas);
resize();
requestAnimationFrame(frame);
</script>
State, mobile और Playwright
Canvas में drawn strokes अपने आप save नहीं होते। Undo, redo, active tool, color और replay चाहिए तो points और metadata JavaScript state में रखें। Claude Code से कहें कि state update small functions में हो और rendering deterministic रहे।
Mobile पर fixed width सबसे आम गलती है। width: 800px 375px screen पर horizontal scroll बनाता है। केवल width: 100% भी काफी नहीं; aspect-ratio या height constraint चाहिए। Canvas को text, code block, ads, related cards और CTA के साथ देखना जरूरी है।
import { expect, test } from "@playwright/test";
test("canvas renders on mobile", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/canvas-demo");
const canvas = page.locator("canvas").first();
await expect(canvas).toBeVisible();
const box = await canvas.boundingBox();
expect(box?.width ?? 0).toBeLessThanOrEqual(390);
const paintedPixels = await canvas.evaluate((node) => {
const context = node.getContext("2d");
if (!context) return 0;
const data = context.getImageData(0, 0, node.width, node.height).data;
let painted = 0;
for (let i = 3; i < data.length; i += 4) if (data[i] > 0) painted += 1;
return painted;
});
expect(paintedPixels).toBeGreaterThan(1000);
await expect(canvas).toHaveScreenshot("canvas-mobile.png", { maxDiffPixelRatio: 0.03 });
});
सामान्य गलतियां और परिणाम
सामान्य failures हैं: सिर्फ CSS size बदलना, ctx.scale बार-बार जोड़ना, केवल mousemove सुनना, RAF loop साफ न करना, desktop width hard-code करना, और blank screenshot को pass मान लेना। Claude Code review prompt में इन्हें साफ लिखें।
Canvas monetization में तब मदद करता है जब वह static image से बेहतर समझाता है: interactive tutorial, annotation tool, product preview या data tool। लेकिन अगर वह article text और CTA को दबा देता है, तो नुकसान है। Team workflow के लिए Claude Code training और consultation में prompts, implementation rules और Playwright checks को साथ डिजाइन किया जा सकता है।
इस workflow को आजमाने पर सबसे उपयोगी step दूसरा review pass था, जिसमें Claude Code से सिर्फ failure cases देखने को कहा गया। इससे fixed width, accumulated ctx.scale, touch input gap और mobile scroll पहले ही पकड़े गए। Demo थोड़ा कम flashy था, लेकिन real page के लिए ज्यादा भरोसेमंद बना।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code permission safety ladder: access धीरे-धीरे बढ़ाएं
read-only से limited edits, proof commands और deploy checks तक permission बढ़ाने की सुरक्षित ladder.
Claude Code Small PR Proof Pack: छोटे PR को review-ready बनाना
Claude Code PR के लिए diff, checks, public URL, CTA path और rollback वाला practical proof pack.
Claude Code Review Gate Before Commit: diff, test, public URL और CTA जांच
Claude Code से commit से पहले review gate बनाएं: diff, build, public URL, Gumroad, consultation, tests और unrelated files।