PixiJS Graphics & Textures — Drawing Shapes and Managing Spritesheets
PixiJS Graphics API draws vector shapes on the GPU and its texture system manages images efficiently. This tutorial shows you how to draw, cache, and reuse visuals.
What You’ll Learn
- How the
GraphicsAPI draws shapes using GPU-accelerated commands - How to draw rectangles, circles, polygons, stars, and curved lines
- The difference between
BaseTexture(raw image) andTexture(region reference) - How sprite sheets pack many images into one texture atlas
- How
RenderTexturebakes dynamic graphics into reusable textures - How to build a shape drawer tool with export functionality
Why Graphics & Textures Matter
Efficient texture management is the difference between a smooth 60fps app and a stuttery mess. Every unique texture creates a GPU draw call — too many, and your frame rate drops.
At DodaTech, Durga Antivirus Pro uses sprite sheets to render security icons, threat indicators, and map markers in a single draw call. When scanning 10,000 files, the UI shows a live grid of file status icons — each one is a sprite from a shared texture atlas, rendered as a single batched operation. Without texture atlases, this visualization would crawl.
Learning Path
flowchart LR
GS["Getting Started"] --> GT["Graphics & Textures ⬅ You Are Here"]
GT --> AI["Animation & Interactivity"]
AI --> TF["Text & Bitmap Fonts"]
TF --> PB["Performance & Best Practices"]
style GT fill:#4a90d9,stroke:#fff,color:#fff
linkStyle default stroke:#4a90d9,stroke-width:2
PixiJS Graphics vs Canvas Shapes vs Pre-rendered Sprites
| Approach | Performance | Flexibility | Use Case |
|---|---|---|---|
| PixiJS Graphics | Fast (GPU) | High — vector shapes | Dynamic shapes, UI elements |
| Canvas 2D shapes | Slow (CPU) | High | Simple drawings, no interactivity |
| Pre-rendered sprites | Fastest | Low — fixed image | Icons, backgrounds, UI |
When to use Graphics: You need shapes that change at runtime (progress rings, charts, custom cursors). For static visuals, pre-render them to a texture for maximum speed.
The Graphics API — Drawing with Code
Think of the Graphics object as a digital sketchpad. You tell it what to draw (rectangle, circle, line) and how to style it (fill color, stroke), and it sends those commands directly to the GPU.
const g = new PIXI.Graphics();
// Pick a fill color and draw
g.beginFill(0xff0000); // red fill
g.drawRect(50, 50, 200, 100); // x, y, width, height
g.endFill(); // stop filling
app.stage.addChild(g);Why call endFill()? Every beginFill() starts a fill operation. If you don’t call endFill(), PixiJS keeps applying the fill to every shape you draw after it, which causes visual artifacts.
Drawing Methods Explained
const g = new PIXI.Graphics();
g.drawRect(x, y, w, h); // Rectangle
g.drawRoundedRect(x, y, w, h, r); // Rectangle with rounded corners
g.drawCircle(x, y, radius); // Perfect circle
g.drawEllipse(x, y, rx, ry); // Oval
g.drawPolygon([x1, y1, x2, y2, ...]); // Any shape with straight edges
g.drawStar(x, y, points, outerR, innerR); // Star shape (PixiJS 7+)
g.moveTo(x, y); // Move pen without drawing
g.lineTo(x, y); // Draw a straight line
g.arc(cx, cy, r, startAngle, endAngle); // Curve an arc
g.bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY); // Smooth curved line
Why use drawPolygon for stars? Before PixiJS 7, drawStar didn’t exist. You had to manually calculate star vertex positions and pass them to drawPolygon. The built-in drawStar is both easier and faster.
Fill & Stroke — Coloring Your Shapes
const g = new PIXI.Graphics();
// Orange fill at 80% opacity
g.beginFill(0xff6600, 0.8);
// White stroke: 4px wide, full opacity
g.lineStyle(4, 0xffffff, 1);
// Circle with both fill and stroke
g.drawCircle(300, 200, 80);
g.endFill();Line style is optional. If you don’t call lineStyle(), there’s no stroke. Call lineStyle(0) to explicitly remove the stroke.
Drawing Shapes in Practice
Star
Stars are great for UI badges, ratings, or decorative elements:
function drawStar(g, cx, cy, points, outerR, innerR) {
g.beginFill(0xffff00);
g.drawStar(cx, cy, points, outerR, innerR);
g.endFill();
}
drawStar(new PIXI.Graphics(), 400, 300, 5, 100, 50);Polygon (Custom Shape)
Polygons let you draw any shape with straight edges — triangles, diamonds, arrows:
const g = new PIXI.Graphics();
g.beginFill(0x44aa88);
g.drawPolygon([
0, 0,
120, 0,
160, 60,
120, 120,
0, 120,
-40, 60,
]);
g.endFill();Textures from Images & Canvas
From an Image URL
const texture = PIXI.Texture.from('https://example.com/image.png');
const sprite = new PIXI.Sprite(texture);From an HTML Canvas Element
You can turn anything drawn on an HTML <canvas> into a PixiJS texture:
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ff6600';
ctx.beginPath();
ctx.arc(64, 64, 50, 0, Math.PI * 2);
ctx.fill();
const texture = PIXI.Texture.from(canvas);
const sprite = new PIXI.Sprite(texture);Why convert a Canvas to a texture? The Canvas 2D API makes it easy to draw gradients, text, and complex patterns that Graphics doesn’t support natively. Once it’s a texture, PixiJS can display it at GPU speed.
BaseTexture vs Texture — The Two-Layer System
This is one of the most important concepts in PixiJS. Think of it like a wall and posters:
| Concept | Analogy | Role |
|---|---|---|
BaseTexture | The wall | Raw image data stored in GPU memory |
Texture | A poster on the wall | A reference to a specific region of the wall |
// Load one large image as the "wall"
const baseTexture = PIXI.BaseTexture.from('assets/atlas.png');
// Define "posters" — regions of the wall
const texture1 = new PIXI.Texture(baseTexture, new PIXI.Rectangle(0, 0, 64, 64));
const texture2 = new PIXI.Texture(baseTexture, new PIXI.Rectangle(64, 0, 64, 64));
// Both sprites share the same GPU resource
const sprite1 = new PIXI.Sprite(texture1);
const sprite2 = new PIXI.Sprite(texture2);Why this matters: Because texture1 and texture2 share the same BaseTexture, PixiJS can render both sprites in a single draw call. This is how sprite sheets achieve their massive performance improvement.
Warning: If you modify the BaseTexture (e.g., change its size or mipmap setting), every Texture referencing it is affected. Create a separate BaseTexture when you need independent control.
Sprite Sheets with Spritesheet
A sprite sheet (or texture atlas) is a single large image with many smaller images arranged in a grid. The accompanying JSON file tells PixiJS where each sub-image is located.
app.loader
.add('characters', 'assets/characters.json')
.load((loader, resources) => {
const sheet = resources.characters.spritesheet;
const walk1 = new PIXI.Sprite(sheet.textures['walk_01.png']);
const walk2 = new PIXI.Sprite(sheet.textures['walk_02.png']);
});The JSON format looks like this:
{
"frames": {
"walk_01.png": { "frame": { "x": 0, "y": 0, "w": 64, "h": 64 } },
"walk_02.png": { "frame": { "x": 64, "y": 0, "w": 64, "h": 64 } }
},
"meta": { "image": "characters.png" }
}How to create sprite sheets: Use tools like TexturePacker, Shoebox, or Free Texture Packer. Export with the PixiJS preset for compatible JSON format.
RenderTexture — Baking Graphics into Textures
RenderTexture lets you take any display object (or group of objects) and “bake” it into a texture. Think of it like taking a photo of your scene and using that photo as a sticker.
const renderTexture = PIXI.RenderTexture.create({ width: 256, height: 256 });
const graphics = new PIXI.Graphics();
graphics.beginFill(0xff6600);
graphics.drawCircle(128, 128, 80);
graphics.endFill();
// "Take a photo" of the graphics
app.renderer.render(graphics, { renderTexture });
// Use the photo as a texture
const sprite = new PIXI.Sprite(renderTexture);
app.stage.addChild(sprite);When to use RenderTexture:
- Caching complex graphics that don’t change (saves redrawing every frame)
- Creating composite textures from multiple elements
- Building a paint/draw tool (render brush strokes into a texture)
- Exporting graphics as PNG images
Texture Regions & Clipping
You can extract a sub-region of any texture:
const fullTexture = PIXI.Texture.from('assets/character.png');
const headTexture = new PIXI.Texture(
fullTexture.baseTexture,
new PIXI.Rectangle(10, 10, 40, 40) // x, y, width, height
);You can also rotate a region:
headTexture.rotate = 8; // 90 degrees clockwise (see PIXI.groupD8)
Common Mistakes
| Mistake | Why it happens | How to fix it |
|---|---|---|
Forgetting endFill() | Each beginFill() must be closed | Always pair beginFill() with endFill() |
| Creating new Graphics every frame | Graphics rebuilds are expensive | Cache static shapes in a RenderTexture |
Modifying BaseTexture accidentally | Changing shared base affects all textures | Create separate BaseTexture for independent data |
| Wrong rotation pivot | Graphics rotates around (0,0) by default | Use pivot.set() or wrap in a Container |
| Sprite sheet JSON format mismatch | Different tools produce different JSON | Use TexturePacker with the PixiJS preset |
| Trying to tint a Graphics object | tint works on Sprites, not Graphics | Convert to RenderTexture then use a Sprite |
Practice Questions
What is the difference between
BaseTextureandTexture?BaseTextureholds the raw image data in GPU memory.Textureis a lightweight reference to a region within it. Multiple textures can share one BaseTexture.
Why is
RenderTextureuseful for performance?- It bakes a dynamic graphic into a static texture, avoiding expensive redraws every frame.
What happens if you forget
endFill()in a Graphics chain?- The fill persists and affects all subsequent shapes, causing visual artifacts.
How do sprite sheets reduce draw calls?
- All sub-images share one
BaseTexture, so PixiJS renders them in a single batch.
- All sub-images share one
Can you draw a gradient with the Graphics API?
- Not natively. Draw the gradient on an offscreen
<canvas>, create aTexturefrom it, and use aSprite.
- Not natively. Draw the gradient on an offscreen
Challenge: Build a simple pie chart using arc() and beginFill() with different colors. Display the percentage values as labels using Text objects.
FAQ
Try It Yourself
Open this interactive shape drawer in your browser. Pick shapes, colors, and sizes, then export your creation as a PNG texture.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>PixiJS Shape Drawer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #1a1a2e; font-family: system-ui, sans-serif; display: flex; justify-content: center; padding: 20px; }
.app { display: flex; gap: 20px; flex-wrap: wrap; }
canvas { border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
.panel { background: #16213e; border-radius: 12px; padding: 20px; color: #fff; width: 220px; display: flex; flex-direction: column; gap: 12px; }
.panel h2 { font-size: 18px; }
.panel label { font-size: 13px; color: #a8b2d1; }
.panel input[type="range"] { width: 100%; accent-color: #4a90d9; }
.panel input[type="color"] { width: 100%; height: 36px; border: none; border-radius: 6px; cursor: pointer; }
.panel select { width: 100%; padding: 8px; border-radius: 6px; border: none; background: #0f3460; color: #fff; }
.btn { padding: 10px; border: none; border-radius: 8px; background: #4a90d9; color: #fff; font-weight: 600; cursor: pointer; }
.btn:hover { background: #357abd; }
.btn.success { background: #2d6a4f; }
</style>
</head>
<body>
<div class="app">
<div id="canvas-container"></div>
<div class="panel">
<h2>Shape Drawer</h2>
<label>Shape</label>
<select id="shape">
<option value="star" selected>Star</option>
<option value="rect">Rectangle</option>
<option value="circle">Circle</option>
<option value="ellipse">Ellipse</option>
<option value="polygon">Triangle</option>
<option value="rounded">Rounded Rect</option>
</select>
<label>Fill Color</label>
<input type="color" id="fillColor" value="#ff6600" />
<label>Stroke Color</label>
<input type="color" id="strokeColor" value="#ffffff" />
<label>Stroke Width</label>
<input type="range" id="strokeWidth" min="0" max="12" value="3" />
<label>Rotation <span id="rotVal">0</span>°</label>
<input type="range" id="rotation" min="0" max="360" value="0" />
<label>Size</label>
<input type="range" id="size" min="20" max="200" value="80" />
<button class="btn" id="redrawBtn">Redraw</button>
<button class="btn success" id="exportBtn">Export as PNG</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.x/dist/pixi.min.js"></script>
<script>
const app = new PIXI.Application({
width: 600, height: 600,
backgroundColor: 0x1a1a2e,
antialias: true,
});
document.getElementById('canvas-container').appendChild(app.view);
let mainShape = new PIXI.Graphics();
function drawShape() {
app.stage.removeChild(mainShape);
mainShape.destroy();
mainShape = new PIXI.Graphics();
const shape = document.getElementById('shape').value;
const fill = parseInt(document.getElementById('fillColor').value.slice(1), 16);
const stroke = parseInt(document.getElementById('strokeColor').value.slice(1), 16);
const sw = parseFloat(document.getElementById('strokeWidth').value);
const size = parseFloat(document.getElementById('size').value);
const deg = parseFloat(document.getElementById('rotation').value);
if (sw > 0) mainShape.lineStyle(sw, stroke, 1);
mainShape.beginFill(fill, 1);
const cx = 300, cy = 300;
switch (shape) {
case 'rect': mainShape.drawRect(cx - size / 2, cy - size / 2, size, size); break;
case 'circle': mainShape.drawCircle(cx, cy, size / 2); break;
case 'ellipse': mainShape.drawEllipse(cx, cy, size / 2, size / 3); break;
case 'polygon': mainShape.drawPolygon([cx, cy - size / 2, cx + size / 2, cy + size / 3, cx - size / 2, cy + size / 3]); break;
case 'star': mainShape.drawStar(cx, cy, 5, size / 2, size / 5); break;
case 'rounded': mainShape.drawRoundedRect(cx - size / 2, cy - size / 2, size, size, size / 5); break;
}
mainShape.endFill();
mainShape.rotation = (deg * Math.PI) / 180;
app.stage.addChild(mainShape);
}
document.getElementById('shape').addEventListener('change', drawShape);
document.getElementById('fillColor').addEventListener('input', drawShape);
document.getElementById('strokeColor').addEventListener('input', drawShape);
document.getElementById('strokeWidth').addEventListener('input', drawShape);
document.getElementById('size').addEventListener('input', drawShape);
document.getElementById('rotation').addEventListener('input', e => {
document.getElementById('rotVal').textContent = e.target.value;
drawShape();
});
document.getElementById('redrawBtn').addEventListener('click', drawShape);
document.getElementById('exportBtn').addEventListener('click', () => {
const rt = PIXI.RenderTexture.create({ width: 600, height: 600 });
app.renderer.render(mainShape, { renderTexture: rt });
const canvas = app.renderer.extract.canvas(rt);
const link = document.createElement('a');
link.download = 'shape-texture.png';
link.href = canvas.toDataURL('image/png');
link.click();
rt.destroy();
});
drawShape();
window.addEventListener('resize', () => {
const s = Math.min(window.innerWidth - 280, 600, window.innerHeight - 40);
app.renderer.resize(Math.max(s, 200), Math.max(s, 200));
});
</script>
</body>
</html>What’s Next
| Topic | Link |
|---|---|
| Animation and interactivity | https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-animation-interactivity/ |
| Text and bitmap fonts | https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-text-fonts/ |
| WebGL fundamentals | WebGL Explained |
| JavaScript graphics basics | JavaScript for Graphics |
Related Tutorials
- https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-getting-started/ — start here first
- https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-performance/ — optimize texture batching
- Canvas API vs WebGL — when to use each rendering approach
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Durga Antivirus Pro uses texture atlases to render thousands of threat indicators at 60fps — the techniques you learned here power real security tools.
What’s Next
Congratulations on completing this Pixijs Graphics Textures tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro