Canvas Coordinates
Understanding the canvas coordinate system is essential for positioning elements correctly in Klint.
Coordinate System Basics
With the default origin: 'corner', the canvas uses a coordinate system where:
- (0, 0) is the top-left corner
- X increases to the right
- Y increases downward
With origin: 'center', (0, 0) is the center, while X still increases right and Y still increases down.
const draw = (K) => {
K.background('#f0f0f0');
K.fillColor('black');
// Top-left corner
K.circle(0, 0, 10);
K.text('(0, 0)', 20, 20);
// Top-right corner
K.circle(K.width, 0, 10);
K.text(`(${K.width}, 0)`, K.width - 80, 20);
// Bottom-left corner
K.circle(0, K.height, 10);
K.text(`(0, ${K.height})`, 20, K.height - 10);
// Bottom-right corner
K.circle(K.width, K.height, 10);
K.text(`(${K.width}, ${K.height})`, K.width - 100, K.height - 10);
// Center
K.circle(K.width/2, K.height/2, 10);
K.text('Center', K.width/2 - 25, K.height/2 - 15);
};
Logical pixels and DPR
K.width, K.height, drawing coordinates, pointer coordinates, offscreen dimensions, and pixel helper coordinates are all expressed in logical CSS pixels. Klint scales the backing store and base transform internally using K.dpr.
For example, a 400 × 300 logical canvas at DPR 2 has an 800 × 600 backing store, but you still draw its bottom-right corner at (400, 300). Do not multiply coordinates by devicePixelRatio yourself.
Use dpr: 'default' to follow the device (capped by maxDpr, which defaults to 3), or pass a positive number for a fixed DPR.
Common Positions
Canvas Boundaries
const draw = (K) => {
// Useful position references
const left = 0;
const right = K.width;
const top = 0;
const bottom = K.height;
const centerX = K.width / 2;
const centerY = K.height / 2;
// Draw cross at center
K.strokeColor('red');
K.line(centerX - 20, centerY, centerX + 20, centerY);
K.line(centerX, centerY - 20, centerX, centerY + 20);
};
Grid Positions
const draw = (K) => {
K.background('#fff');
// Create a 3x3 grid
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
const x = (col + 0.5) * (K.width / 3);
const y = (row + 0.5) * (K.height / 3);
K.fillColor('#333');
K.circle(x, y, 20);
K.alignText('center', 'middle');
K.text(`${col},${row}`, x, y);
}
}
};
Shape Anchor Points
Different shapes use different anchor points:
Circles - Center Point
const draw = (K) => {
// Circle is drawn from its center
const x = 100;
const y = 100;
const radius = 50;
K.fillColor('lightblue');
K.circle(x, y, radius);
// Mark the center
K.fillColor('red');
K.circle(x, y, 3);
};
Rectangles - Configurable origin
const draw = (K) => {
// Rectangle uses the top-left corner by default.
// K.setRectOrigin('center') changes its anchor to the center.
const x = 100;
const y = 100;
const width = 100;
const height = 50;
K.fillColor('lightgreen');
K.rectangle(x, y, width, height);
// Mark the anchor point
K.fillColor('red');
K.circle(x, y, 3);
};
Text - Varies by Alignment
const draw = (K) => {
const x = K.width / 2;
const y = K.height / 2;
// Default: left, alphabetic
K.fillColor('black');
K.text('Default', x, y);
// Center, middle
K.alignText('center', 'middle');
K.text('Centered', x, y + 50);
// Mark anchor points
K.fillColor('red');
K.circle(x, y, 3);
K.circle(x, y + 50, 3);
};
Transformations
Transformations change the coordinate system:
Translation
const draw = (K) => {
K.background('#f0f0f0');
// Draw at origin
K.fillColor('blue');
K.rectangle(0, 0, 50, 50);
// Translate coordinate system
K.push();
K.translate(100, 100);
// Now (0,0) is at (100,100)
K.fillColor('red');
K.rectangle(0, 0, 50, 50);
K.pop();
};
Rotation
const draw = (K) => {
K.background('#f0f0f0');
// Rotate around origin (0,0)
K.push();
K.rotate(Math.PI / 4); // 45 degrees
K.fillColor('green');
K.rectangle(0, 0, 100, 50);
K.pop();
// Rotate around center
K.push();
K.translate(K.width/2, K.height/2);
K.rotate(Math.PI / 4);
K.fillColor('purple');
K.rectangle(-50, -25, 100, 50); // Centered rectangle
K.pop();
};
Useful Patterns
Converting Mouse to Canvas Coordinates
function CanvasSketch() {
const { context, KlintMouse } = useKlint();
const { mouse } = KlintMouse();
const draw = (K) => {
K.background('#fff');
// Mouse coordinates are already in canvas space
K.fillColor('black');
K.text(`Mouse: ${mouse.x}, ${mouse.y}`, 10, 20);
K.fillColor('red');
K.circle(mouse.x, mouse.y, 10);
};
return <Klint context={context} draw={draw} />;
}
Centering Objects
const draw = (K) => {
// Center a rectangle
const rectWidth = 200;
const rectHeight = 100;
const x = (K.width - rectWidth) / 2;
const y = (K.height - rectHeight) / 2;
K.rectangle(x, y, rectWidth, rectHeight);
// Center a circle (already centered by default)
K.circle(K.width/2, K.height/2, 50);
// Center text
K.alignText('center', 'middle');
K.text('Centered Text', K.width/2, K.height/2);
};
Responsive Positioning
const draw = (K) => {
// Position elements as percentage of canvas
const margin = 0.1; // 10% margin
// Draw in corners with margin
const cornerSize = 30;
// Top-left
K.circle(K.width * margin, K.height * margin, cornerSize);
// Top-right
K.circle(K.width * (1 - margin), K.height * margin, cornerSize);
// Bottom-left
K.circle(K.width * margin, K.height * (1 - margin), cornerSize);
// Bottom-right
K.circle(K.width * (1 - margin), K.height * (1 - margin), cornerSize);
};
Circular Positioning
const draw = (K) => {
K.background('#000');
const centerX = K.width / 2;
const centerY = K.height / 2;
const radius = 100;
const count = 12;
for (let i = 0; i < count; i++) {
const angle = (i / count) * Math.PI * 2;
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
K.fillColor(`hsl(${i * 30}, 70%, 50%)`);
K.circle(x, y, 20);
}
};
Debugging Coordinates
const draw = (K) => {
K.background('#fff');
// Draw grid
K.strokeColor('#ddd');
K.strokeWidth(1);
// Vertical lines
for (let x = 0; x <= K.width; x += 50) {
K.line(x, 0, x, K.height);
K.fillColor('#999');
K.textSize(10);
K.text(x.toString(), x + 2, 12);
}
// Horizontal lines
for (let y = 0; y <= K.height; y += 50) {
K.line(0, y, K.width, y);
K.fillColor('#999');
K.textSize(10);
K.text(y.toString(), 2, y - 2);
}
// Your drawing code here
K.fillColor('red');
K.circle(150, 200, 30);
};
Common Pitfalls
- Manual DPR scaling - Coordinates are already logical pixels; do not multiply them by
K.dpr. - Y-axis direction - Y increases downward in both origin modes.
- Origin mode - With
origin: 'center', canvas coordinates range roughly from-width / 2towidth / 2. - Rectangle anchor - Rectangles use the corner unless changed with
setRectOrigin(). - Rotation origin - Rotation happens around the current
(0,0)unless you translate first. - Text baseline - Text anchoring follows the current
alignText()setting.
Next Steps
- Function Reference - Learn shape drawing functions
- Transformations - Master coordinate transformations
- React Integration - Handle responsive canvases