Custom Elements
Create and render your own element types on the canvas
Important: this is an experimental feature, please proceed with caution. We're kindly asking you to report any bugs and follow the changelog for the most recent updates.
Custom elements allow you to create your own shapes and add them to the canvas.
By design Polotno supports several main types of elements: text
, image
, svg
, line
, group
. In some cases you may want to create your own custom elements.
Before you start, please note that:
- Custom elements are not supported in Cloud Render API.
- By default polotno-node (for backend rendering) does not support custom elements. But with some extra configuration you can make it work.
- You will need to know some Polotno internals to support animations for custom elements. Please write us if you need help.
- In the future there will be more options for design export (e.g. to SVG, print‑ready PDF, etc). You will need to write some adapters to make it work with custom elements.
- If possible, try to use built‑in elements. For example you can draw some complex shapes using
svg
element (you can generatesrc
at runtime).
How to create custom shapes with Polotno?
As a demonstration, we will create a custom star element. Creating new elements consists of three main steps.
1. Create model for your element
First define any additional attributes for the new element. All the basic attributes such as id
, x
, y
, rotation
, filters attributes, etc. are defined by default. You only need to define extra fields and their defaults:
import { unstable_registerShapeModel } from 'polotno/config';
unstable_registerShapeModel(
// define properties
{
type: 'star',
radius: 100,
fill: 'black',
numPoints: 6,
},
// optional extend function
(starModel) => {
// starModel is a model from mobx-state-tree
// we can define some additional methods here
// and return it back
return starModel.actions((self) => {
return {
setNumPoints(numPoints) {
self.numPoints = numPoints;
},
};
});
}
);
Now Polotno store knows that we can define a star
model.
2. Create React component for new element
Define how to display the model. Create a React component with react‑konva shapes.
// polotno is made with mobx library
// we will need its tools to make reactive components
import { observer } from 'mobx-react-lite';
// import Konva components
import { Star } from 'react-konva';
import { unstable_registerShapeComponent } from 'polotno/config';
// now we need to define how element looks on canvas
export const StarElement = observer(({ element, store }) => {
const ref = React.useRef<any>(null);
const handleChange = (e: any) => {
const node = e.currentTarget;
const scaleX = node.scaleX();
// Konva.Transformer changes scale by default; reset it
node.scaleX(1);
node.scaleY(1);
// save changes back to the model
element.set({
x: node.x(),
y: node.y(),
rotation: e.target.rotation(),
radius: element.radius * scaleX,
});
};
// Important: element.x and element.y must define top-left corner of the shape
// so positions are consistent across all elements
return (
<Star
ref={ref}
// use name="element" so Polotno can find the node
name="element"
id={element.id}
x={element.x}
y={element.y}
fill={element.fill}
offsetX={-element.radius}
offsetY={-element.radius}
rotation={element.rotation}
opacity={element.opacity}
draggable={!element.locked}
outerRadius={element.radius}
innerRadius={element.radius * 0.5}
onDragMove={handleChange}
onTransform={handleChange}
/>
);
});
// register new component to draw our star
unstable_registerShapeComponent('star', StarElement);
3. Create custom top toolbar (optional)
A custom toolbar can be defined to change star properties.
import React from 'react';
import { observer } from 'mobx-react-lite';
import { NumericInput, Navbar, Alignment } from '@blueprintjs/core';
import ColorPicker from 'polotno/toolbar/color-picker';
import { unstable_registerToolbarComponent } from 'polotno/config';
const StarToolbar = observer(({ store }) => {
const element = store.selectedElements[0];
return (
<Navbar.Group align={Alignment.LEFT}>
<ColorPicker
value={element.fill}
onChange={(fill) =>
element.set({
fill,
})
}
store={store}
/>
<NumericInput
onValueChange={(radius) => {
element.set({ radius });
}}
value={element.radius}
style={{ width: '50px', marginLeft: '10px' }}
min={1}
max={200}
/>
</Navbar.Group>
);
});
unstable_registerToolbarComponent('star', StarToolbar);