Skip to main content

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:

  • RulerHandler in @ydesign/core (registered as editor.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:

ActionEffect
Hover the ruler areaCursor becomes ns-resize / ew-resize
Press inside the ruler and drag onto the canvasDrag out a new guide
Click a guide to select itThe guide turns blue; the ruler shows a blue highlight at the matching position
Drag a selected guideFollows the mouse; the ruler numbers update live
Drag a guide into the ruler areaCursor changes to 🚫 not-allowed, hinting "release to delete"
Drag a guide back into the ruler area and releaseThe guide is removed automatically (cursor restores)
Zoom the canvasTick spacing adapts (denser at higher zoom levels)
Select any objectThe 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: true is 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:render event fires. This means it never appears in canvas.toJSON().
  • Guides are plain fabric.Line instances tagged with isGuideline: true; their stroke is set transparent and we render them ourselves in after:render (so they're not clipped by canvas.clipPath and can extend beyond the workarea).
  • Hit testing overrides containsPoint and 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.