pixi.js
    Preparing search index...

    Pixel Lines

    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
     * @param graphics - The Graphics object to draw on
     * @returns The Graphics object with the grid drawn
     */
    function buildGrid(graphics: Graphics) {
      // Draw 10 vertical lines spaced 10 pixels apart
      for (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 apart
      for (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 application
      const app = new Application();
    
      await app.init({ antialias: true, resizeTo: window });
      document.body.appendChild(app.canvas);
    
      // Create two grids - one with pixel-perfect lines and one without
      const gridPixel = buildGrid(new Graphics()).stroke({ color: 0xffffff, pixelLine: true, width: 1 });
    
      const grid = buildGrid(new Graphics()).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 grids
      const container = new Container();
    
      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 variables
      let 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 label
      const label = new Text({
        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);
    })();
    
    // Create a Graphics object and draw a pixel-perfect line
    let graphics = new Graphics().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.


    Common use cases:

    • Pixel art games need sharp, precise visuals. pixelLine ensures lines don't blur or scale inconsistently.
    • Example: pixel-perfect grids for tile-based maps.
    const grid = new Graphics();

    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
    }

    grid.stroke({ color: 0xffffff, pixelLine: true });

    • 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
    const separator = new Graphics()
    // 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 });

    • 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
    const graphicsBox = new Graphics().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
    */
    function drawDebugBounds(obj) {
    // Get the bounds of the object
    const bounds = 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);
    }

    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:

    1. Simpler drawing process: Regular lines require extra steps. PixiJS calculates line thickness and builds a shape from triangles.

    2. Direct line drawing: With pixelLine, the GPU draws a line from point A to point B directly, skipping triangle construction.

    • The line is always 1px thick. There's no way to change this because the GPU draws the line directly.
    • Different GPUs may render the line with slight variations in position or anti-aliasing. This is an inherent limitation of GPU line rendering.
    • 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.

    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 = new Graphics().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.


    • You want a line thicker than 1px.
    • You want the line to scale with the object.