Ruler & Guidelines
Ydesign ships with built-in Ruler + Guidelines capabilities, covering the common helpers people expect for canvas alignment, element positioning, and visual layout. The feature is split into:
RulerHandlerin@ydesign/core(registered aseditor.rulerHandler)<RulerButton />in@ydesign/react-editor(the toggle button at the bottom-right of the canvas)
These two pieces are independent and can be used separately.
Quick start
Drop <RulerButton /> into your <WorkspaceWrap>, next to <ZoomButtons />:
import { DesignEditorContainer, SidePanelWrap, WorkspaceWrap } from '@ydesign/react-editor';
import Workspace from '@ydesign/react-editor/canvas/workspace';
import { ZoomButtons } from '@ydesign/react-editor/toolbar/zoom-buttons';
import { RulerButton } from '@ydesign/react-editor/toolbar/ruler-button';
<DesignEditorContainer>
<SidePanelWrap>{/* ... */}</SidePanelWrap>
<WorkspaceWrap>
<Workspace store={store} />
<ZoomButtons store={store} />
<RulerButton store={store} />
</WorkspaceWrap>
</DesignEditorContainer>;
<RulerButton /> provides:
- Toggle – click → dropdown → "Show ruler and guides"; the on/off state is remembered in
localStorage - Clear guides – second menu item; only enabled when there's at least one guide on the canvas
- i18n – reads
translate('toolbar.ruler')/toolbar.rulerShow/toolbar.rulerClear
User interactions
Once the ruler is enabled:
| Action | Effect |
|---|---|
| Hover the ruler area | Cursor becomes ns-resize / ew-resize |
| Press inside the ruler and drag onto the canvas | Drag out a new guide |
| Click a guide to select it | The guide turns blue; the ruler shows a blue highlight at the matching position |
| Drag a selected guide | Follows the mouse; the ruler numbers update live |
| Drag a guide into the ruler area | Cursor changes to 🚫 not-allowed, hinting "release to delete" |
| Drag a guide back into the ruler area and release | The guide is removed automatically (cursor restores) |
| Zoom the canvas | Tick spacing adapts (denser at higher zoom levels) |
| Select any object | The ruler shows a blue highlight band with start / end coordinates of the object |
📍 The ruler's origin (0, 0) corresponds to the top-left of the workarea, matching the X / Y displayed in the position toolbar.
Core API (editor.rulerHandler)
Toggle & state
editor.rulerHandler.enable(); // Show the ruler
editor.rulerHandler.disable(); // Hide the ruler (guides are hidden too)
editor.rulerHandler.toggle(); // Toggle
editor.rulerHandler.isEnabled; // boolean, current state
Guide management
editor.rulerHandler.clearGuidelines(); // Remove all guides
editor.rulerHandler.showGuidelines(); // Show all guides (independent of isEnabled)
editor.rulerHandler.hideGuidelines(); // Hide all guides
editor.rulerHandler.hasGuidelines(); // boolean
Hit testing (advanced)
editor.rulerHandler.isPointOnRuler(point); // 'horizontal' | 'vertical' | false
Decides whether a given viewport point (screen pixels relative to canvas top-left) is inside the ruler area. Useful when integrating custom mouse interactions.
Customizing appearance
RulerHandler accepts options at construction:
import RulerHandler, { type RulerHandlerOptions } from '@ydesign/core/dist/handlers/RulerHandler';
const options: RulerHandlerOptions = {
ruleSize: 20, // Ruler thickness, default 20
fontSize: 10, // Tick number font size, default 10
backgroundColor: '#fff', // Ruler background
borderColor: '#ddd', // Ruler border
textColor: '#888', // Tick number color
highlightColor: '#007fff', // Highlight when an object/guide is active
guidelineColor: '#4bec13', // Default guide color
};
For most apps the defaults that <RulerButton /> ships with are good enough; reach for these only when you need bespoke styling.
Relationship with persistence & history
Guides are transparent to the persistence system:
- They do not enter
store.objects(so they don't trigger autosave) - They do not enter
historyHandler(undo / redo won't "undo" them away) excludeFromExport: trueis set — they're skipped on PNG / JSON export
In other words, guides are a per-session visual aid; close the tab and they're gone. If you need to persist a guide layout, read editor.rulerHandler state explicitly and store it yourself.
Implementation notes (skippable)
- The ruler itself is not a fabric object; it's drawn directly on the main canvas context after the
after:renderevent fires. This means it never appears incanvas.toJSON(). - Guides are plain
fabric.Lineinstances tagged withisGuideline: true; theirstrokeis set transparent and we render them ourselves inafter:render(so they're not clipped bycanvas.clipPathand can extend beyond the workarea). - Hit testing overrides
containsPointand treats anything within 6 screen pixels of a guide as a hit — far more reliable than Fabric's default long-line hit testing. - After every
object:added, all guides are pushed to the front of the stack so they remain selectable even when an image or text is rendered above them.
FAQ
Q: Why can I still click guides that visually pass through an image?
When the ruler is enabled we subscribe to object:added and bring all guides to the top of the stack on every insertion. So even when an image visually covers a guide, hit testing always finds the guide first.
Q: Are guides saved into the template JSON?
No. excludeFromExport: true is set and store.objects filters them out, so you can use them freely without polluting your data.
Q: Can I hide the ruler but keep the guides?
Yes. Calling disable() hides the ruler, and guides are flipped to visible: false. Calling enable() again restores them. If you want a permanent "guides without ruler" state, call enable() and then hide the ruler region with CSS / your own UI.