Normal stroked lines get thicker when you scale a Graphics object. Setting pixelLine: true on a stroke keeps lines exactly 1 screen pixel wide regardless of any transforms, zoom, or scaling applied to the object.
import { Application, Container, Graphics, Text } from'pixi.js';/** * Creates a grid pattern using Graphics lines * @paramgraphics - The Graphics object to draw on * @returns The Graphics object with the grid drawn */functionbuildGrid(graphics:Graphics) {// Draw 10 vertical lines spaced 10 pixels apartfor (let i =0; i <11; i++) {// Move to top of each line (x = i*10, y = 0) graphics .moveTo(i *10, 0)// Draw down to bottom (x = i*10, y = 100) .lineTo(i *10, 100); }// Draw 10 horizontal lines spaced 10 pixels apartfor (let i =0; i <11; i++) {// Move to start of each line (x = 0, y = i*10) graphics .moveTo(0, i *10)// Draw across to end (x = 100, y = i*10) .lineTo(100, i *10); }return graphics;}(async () => {// Create and initialize a new PixiJS applicationconstapp=newApplication();await app.init({ antialias: true, resizeTo: window }); document.body.appendChild(app.canvas);// Create two grids - one with pixel-perfect lines and one withoutconstgridPixel=buildGrid(newGraphics()).stroke({ color: 0xffffff, pixelLine: true, width: 1 });constgrid=buildGrid(newGraphics()).stroke({ color: 0xffffff, pixelLine: false });// Position the grids side by side grid.x =-100; grid.y =-50; gridPixel.y =-50;// Create a container to hold both gridsconstcontainer=newContainer(); container.addChild(grid, gridPixel);// Center the container on screen container.x = app.screen.width /2; container.y = app.screen.height /2; app.stage.addChild(container);// Animation variableslet count =0;// Add animation to scale the grids over time app.ticker.add(() => { count +=0.01; container.scale =1+ ((Math.sin(count) +1) *2); });// Add descriptive labelconstlabel=newText({ text: 'Grid Comparison: Standard Lines (Left) vs Pixel-Perfect Lines (Right)', style: { fill: 0xffffff }, });// Position label in top-left corner label.position.set(20, 20); label.width = app.screen.width -40; label.scale.y = label.scale.x; app.stage.addChild(label);})();
Basic usage
// Create a Graphics object and draw a pixel-perfect line let graphics =newGraphics().moveTo(0, 0).lineTo(100, 100).stroke({ color: 0xff0000, pixelLine: true });
// Add it to the stage app.stage.addChild(graphics);
// Even if we scale the Graphics object, the line remains 1 pixel wide graphics.scale.set(2);
In this example, no matter how you transform or zoom the Graphics object, the red line will always appear 1 pixel thick on the screen.
Why use pixelLine?
Common use cases:
1. Retro or pixel art games
Pixel art games need sharp, precise visuals. pixelLine ensures lines don't blur or scale inconsistently.
Example: pixel-perfect grids for tile-based maps.
constgrid=newGraphics();
for (let i =0; i <10; i++) { grid.moveTo(i *10, 0).lineTo(i *10, 100); // vertical grid.moveTo(0, i *10).lineTo(100, i *10); // horizontal }
For borders, separators, or underlines, a consistent 1-pixel thickness provides a clean look.
Example: a separator line in a menu or a progress bar border.
// Create a separator line that will always be 1 pixel thick constseparator=newGraphics() // Start at x=0, y=50 .moveTo(0, 50) // Draw a horizontal line 200 pixels to the right .lineTo(200, 50) // Stroke in green with pixel-perfect 1px width .stroke({ color: 0x00ff00, pixelLine: true });
3. Debugging and prototyping
Pixel-perfect lines are useful for debugging layouts, collision boxes, or grids. Since they don't scale, they provide a consistent reference point.
Example: displaying collision boundaries in a physics-based game.
// Create a debug box with pixel-perfect stroke constgraphicsBox=newGraphics().rect(0, 0, 100, 100).stroke({ color: 0xff00ff, pixelLine: true });
/** * Updates the debug box to match the bounds of a given object * @param{Container}obj - The object to draw bounds for */ functiondrawDebugBounds(obj) { // Get the bounds of the object constbounds= obj.getBounds();
// Position and scale the debug box to match the bounds // this is faster than using `moveTo` and `lineTo` each frame! graphicsBox.position.set(bounds.x, bounds.y); graphicsBox.scale.set(bounds.width /100, bounds.height /100); }
How it works
Under the hood, pixelLine uses WebGL or WebGPU's native line rendering methods.
Pixel lines are faster to draw than regular lines for two reasons:
Simpler drawing process: Regular lines require extra steps. PixiJS calculates line thickness and builds a shape from triangles.
Direct line drawing: With pixelLine, the GPU draws a line from point A to point B directly, skipping triangle construction.
Caveats and gotchas
1. Always 1px thick
The line is always 1px thick. There's no way to change this because the GPU draws the line directly.
2. Hardware may render differently
Different GPUs may render the line with slight variations in position or anti-aliasing. This is an inherent limitation of GPU line rendering.
3. Scaling behavior
Line thickness remains constant, but other properties (position, start/end points) are still affected by scaling. This can create unexpected results when combined with other scaled objects.
Example: box with pixel-perfect stroke
A filled box with a pixel-perfect stroke. The box scales, but the stroke remains 1 pixel wide:
// Create a Graphics object and draw a filled box with a pixel-perfect stroke let box =newGraphics().rect(0, 0, 100, 100).fill('white').stroke({ color: 0xff0000, pixelLine: true });
// Add it to the stage app.stage.addChild(box);
// Scale the box box.scale.set(2);
The box grows as it scales, but the red stroke stays at 1 pixel thickness.