A text box has a width, and its height follows the text. People resize a box with handles: a side handle changes the
width and the text rewraps, and a corner handle scales the text itself. A side handle never makes the box narrower
than its longest word. On Konva the handles are your own Konva.Transformer; on a plain canvas the library draws
them.
What each handle does
| Handle | What changes | Limit |
|---|---|---|
| Left or right side | The width. The text rewraps at the new width. | Never narrower than the longest word |
| Corner | The type, in proportion: font sizes, outline widths, letter spacing and the shadow scale together, and the width with them | At least 20 pixels wide and 20 tall |
- Hold Alt while dragging to resize about the box's center instead of the opposite side or corner.
- A box has no top or bottom side handle, because stretching type vertically would distort it.
- A curved box has corner handles only: its width does not change the curve.
- A corner drag scales the font sizes in the document. The box is never left scaled, so text stays sharp and a 48-pixel headline scaled to double reads as 96 in your toolbar.
- Each resize is one undo step.
Set the width from code
// Konva
headline.width(320);
// Plain canvas
headline.setWidth(320);
The text rewraps at the new width. If one word is wider than the width you set, the box widens to fit that word.
getLongestWordWidth() returns that width in pixels, if you want to use it as your own minimum:
const narrowest = headline.getLongestWordWidth();
Konva: your transformer
enableRichText uses your transformer. Attach it to a box the way you attach it to any node; the library sets which handles the box offers and handles the drag:
const selection = new Konva.Transformer();
layer.add(selection);
const rt = enableRichText(stage);
stage.on("mousedown", (e) => {
if (e.target instanceof RichText) selection.nodes([e.target]);
else if (e.target === stage) selection.nodes([]);
});
By default any Konva.Transformer on the stage is the selection transformer. If your stage has several, name yours
with a selector: enableRichText(stage, { transformers: { selection: "#selection", groupChild: "#inGroup" } }).
groupChild is the transformer for a box selected inside a group; a flat box in a group offers its side handles only,
and a curved one none (the group's own handles scale it).
To ungroup, move each box out before you destroy the group, or group.destroy() destroys the boxes with it. Keep
where it is on screen with absolutePosition() and getAbsoluteRotation(), then call
box.bakeScale(group.scaleX(), group.scaleY()) so the group's scale goes into the type instead of staying on the
node. A box you move stays editable; enableRichText follows it.
With several boxes in one transformer, a corner drag scales each box's type and keeps each box centered where the transformer put it; a side drag changes each box's width.
Each box fires richtext:transformStart and richtext:transformEnd when a resize with the transformer starts and
ends. The event's target is the box:
headline.on("richtext:transformEnd", (e) => saveBox(e.target)); // e.target: the box
A box without enableRichText
A RichText handles its own transforms by default. Give it a transformer, or settings for one it makes:
const box = new RichText({ x: 40, y: 40, width: 400, document: doc, transformer: myTransformer });
const other = new RichText({ x: 40, y: 200, width: 400, document: doc, transformerConfig: { anchorSize: 10 } });
layer.add(other.getTransformer()!); // the transformer it made: add it to a layer yourself
To leave transforms to Konva's own scaling, pass autoHandleTransforms: false.
Scaling from code
| Method | Does |
|---|---|
box.bakeScale(scaleX, scaleY) |
Moves the node's Konva scale into the type: font sizes scale by scaleY, the width by scaleX, and the node's scale goes back to 1. For your own transform code. |
box.resize(pageScaleX, pageScaleY) |
Fits the box to a page drawn at a new scale: its position scales by each factor, its type and width by the smaller one. |
Plain canvas: built-in handles
createCanvasRichText draws the selected box's outline and handles in rt.paintOverlay(ctx).
A click selects the box under it, a second click edits it, a drag moves it, and the handles resize it by the rules
above. Each move and resize is one undo step.
const rt = createCanvasRichText({
onChange: render,
objectControls: { size: 10, fill: "#ffffff", stroke: "#5e2dc1" },
selectionColor: "rgba(94, 45, 193, 0.25)",
caretWidth: 2,
});
| Option | Default | Does |
|---|---|---|
objectControls |
true |
false turns off the handles, click-to-select and dragging, for an app with its own selection UI. An object sets their look. |
objectControls.size |
8 |
Handle size, in CSS pixels on screen |
objectControls.fill |
"#ffffff" |
The handles' fill |
objectControls.stroke |
"#007bff" |
The outline around the box and each handle |
selectionColor |
"rgba(0, 0, 255, 0.3)" |
The highlight behind selected text |
caretWidth |
1.5 |
The cursor's width, in CSS pixels |
pixelRatio |
window.devicePixelRatio |
The device-pixel scale your canvas is drawn at. Handles and the cursor keep their size on screen by it. |
The handles and the cursor are the same size on screen at any zoom. Set pixelRatio only when you draw at a ratio other
than the screen's.
With objectControls: false, your app selects with rt.select(box), moves with box.setPosition(x, y), rotates with
box.setRotation(degrees) and resizes with box.setWidth(width). Those three box calls are not undo steps. To find the box under a
pointer, call rt.hitTest(point): the topmost box at a point in your scene, or null.
A canvas box is never scaled: scaleX() and scaleY() always return 1, and a corner drag changes the font sizes.
In the API reference
- RichTextConfig -
autoHandleTransforms,transformer,transformerConfig - EnableRichTextOptions -
transformers - RichText.bakeScale, RichText.resize
- RichText.getLongestWordWidth
- CanvasRichTextOptions -
objectControls,selectionColor,caretWidth,pixelRatio - CanvasTextBox.setWidth