Mastering Custom Renderers: Part 1 - Fundamentals
In this multi-part tutorial series, we'll take a deep dive into creating custom renderers for Blockly. This first installment covers the fundamentals of the rendering system and how to create your first custom renderer.
Understanding the Renderer Architecture
Since Blockly 3.0, the rendering system has been completely modular, making it possible to entirely customize how blocks appear in your application. Before diving into code, let's understand the architecture:
- Renderer: The top-level class that orchestrates the rendering process
- Renderer Constants: Configuration values that control block appearances
- Measurables: Classes that calculate and store block dimensions
- Drawer: Classes that draw the actual SVG elements
The default renderers included with Blockly are:
- Geras: The classic Blockly look with beveled edges
- Thrasos: A modern, flat design style
- Zelos: A sleek, minimalist style with different block shapes
Setting Up Your Development Environment
First, let's set up a basic Blockly environment where we can develop our custom renderer:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Custom Renderer Development</title>
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
<style>
#blocklyDiv {
height: 600px;
width: 800px;
}
</style>
</head>
<body>
<div id="blocklyDiv"></div>
<script>
// We'll add our renderer code here
const workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox')
});
</script>
<xml id="toolbox" style="display: none">
<block type="controls_if"></block>
<block type="logic_compare"></block>
<block type="controls_repeat_ext"></block>
<block type="math_number">
<field name="NUM">123</field>
</block>
<block type="text"></block>
</xml>
</body>
</html>
Creating Your First Custom Renderer
Now, let's create a simple custom renderer by extending the base Blockly renderer:
// Step 1: Create a custom constants provider
class MyRendererConstants extends Blockly.blockRendering.ConstantProvider {
constructor() {
super();
// Override constants to customize block appearance
this.NOTCH_WIDTH = 20; // Wider notches
this.NOTCH_HEIGHT = 10; // Taller notches
// Set custom corner radius
this.CORNER_RADIUS = 10; // Rounder corners
// Custom colors
this.FIELD_TEXT_COLOUR = '#FFFFFF';
this.FIELD_BORDER_RECT_COLOUR = '#006699';
}
// Override the notch shape
makeNotch() {
const width = this.NOTCH_WIDTH;
const height = this.NOTCH_HEIGHT;
// Create a custom notch shape (a simple triangle)
const path = `l ${width/2},${height} l ${width/2},-${height}`;
return {
type: this.SHAPES.NOTCH,
width: width,
height: height,
pathLeft: path
};
}
}
// Step 2: Create the custom renderer
class MyRenderer extends Blockly.blockRendering.Renderer {
constructor() {
super('my_renderer');
}
makeConstants_() {
return new MyRendererConstants();
}
}
// Step 3: Register the renderer
Blockly.blockRendering.register('my_renderer', MyRenderer);
// Step 4: Use the custom renderer
const workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox'),
renderer: 'my_renderer'
});
Understanding the Results
With this simple renderer, you should see several changes to your blocks:
- Wider, triangular notches instead of the default puzzle-piece shape
- Rounder corners on all blocks
- Custom field colors with white text on a blue background
This is just scratching the surface, but it demonstrates the power of the renderer system. You can control virtually every visual aspect of your blocks.
Debugging Your Renderer
When developing custom renderers, it's helpful to visualize the block metrics. Let's add a debug helper:
function enableRendererDebug(workspace) {
// Display block bounds and connection locations
workspace.addChangeListener(function(e) {
if (e.type === Blockly.Events.BLOCK_MOVE) {
const block = workspace.getBlockById(e.blockId);
if (block) {
console.log('Block metrics:', {
id: block.id,
type: block.type,
bounds: block.getBoundingRectangle(),
connections: block.getConnections_().map(c => ({
type: c.type,
position: c.getOffsetInBlock()
}))
});
}
}
});
}
// Enable debug mode
enableRendererDebug(workspace);
Next Steps
In Part 2 of this series, we'll explore:
- Creating custom field renderers
- Adding animations to block connections
- Implementing theme-based rendering variations
- Optimizing renderer performance
Exercises
Before moving on to Part 2, try these exercises to deepen your understanding:
- Modify the corner radius to create completely square blocks
- Change the input shapes from the default indentation to a distinct visual element
- Implement a custom connection shape for value inputs
- Create a "dark mode" variation of your renderer with appropriate colors
<CodeDemo title="Try It Yourself" height="400px" defaultCode={` // Try modifying the renderer constants here class MyRendererConstants extends Blockly.blockRendering.ConstantProvider { constructor() { super();
// Your customizations go here
this.CORNER_RADIUS = 10;
this.NOTCH_WIDTH = 20;
} } `} />
<NextStepCTA nextTutorial="mastering-custom-renderers-part-2" seriesTitle="Mastering Custom Renderers" nextTitle="Part 2 - Advanced Techniques" />