Launch OpenGPEX

OpenGPEX 1.x documentation (legacy editor).

v2 Docs

Coordinate & Motion Sync Hooks

Hooks to synchronize DOM elements with coordinates in the canvas stage. These hooks bypass React's virtual DOM for 60fps performance, writing directly to the DOM via style.transform.


useLayerSync(ref, layer, extraVars?)

Synchronizes a DOM node's position and transforms to match a canvas layer in real-time.

API Specification

  • Input:
    • ref: React.RefObject<HTMLElement | null> — Target DOM element reference.
    • layer: Layer — The target layer to match.
    • extraVars: Record<string, any> (optional) — Additional CSS custom property overrides.
  • Internal Logic: Direct-writes transforms (translation, rotation, scale) to the DOM element's style.transform via a requestAnimationFrame loop. Reads volatile state for real-time accuracy during interactions. Bypasses React updates entirely.
  • Output: void (side-effect only).

Example

import { useLayerSync } from '@opengpex/editor/core/context';

function LayerOverlay({ layer }: { layer: Layer }) {
  const ref = useRef<HTMLDivElement>(null);
  useLayerSync(ref, layer);
  return <div ref={ref} className="overlay-border" />;
}

useViewportSync(stageRef, artboardRef, frame)

Aligns stage wrapper sizes and centers artboards relative to the viewport camera.

API Specification

  • Input:
    • stageRef: React.RefObject<HTMLElement | null> — Stage wrapping container.
    • artboardRef: React.RefObject<HTMLElement | null> — Artboard drawing container.
    • frame: Frame — Current active frame configurations.
  • Internal Logic: Adjusts layout margins and calculates absolute centered bounds based on camera pan/zoom. Updates on every frame via requestAnimationFrame.
  • Output: void.

useOverlayRotationSync(ref, frame)

Aligns rotation vectors of custom overlay controllers (text boxes, selection lines, crop handles) to coordinate with frame and viewport camera rotation.

API Specification

  • Input:
    • ref: React.RefObject<HTMLElement | null> — Custom overlay element.
    • frame: Frame | null — Artboard configuration (includes canvas rotation).
  • Internal Logic: Translates rotation transform matrix changes and synchronizes the DOM rotation angles in real-time.
  • Output: void.

useFastSync(ref, isActive, syncFn, options?)

General-purpose 60fps synchronization hook. Calls the provided function on every ticker frame with the latest volatile state. Used by Options plugins for zero-latency UI synchronization (e.g., zoom display).

API Specification

  • Input:
    • ref: React.RefObject<T | null> — Ref to the DOM element being synchronized. The callback is skipped while ref.current is null, so this is also used as an activity guard.
    • isActive: boolean — Whether the sync loop should run.
    • syncFn: (v: VolatileState, frame: Frame, cam: CameraState) => void — Function executed each frame with latest volatile state, merged active frame, and camera.
    • options?: { throttleHz?: number } — Throttle execution to ~N Hz while v.activeState.interacting is true (non-interacting ticks always execute).
  • Internal Logic: Driven by the shared useTicker loop (requestAnimationFrame). Merged frame/camera snapshots are computed once per tick and cached on the volatile state for all subscribers. Cleans up automatically on unmount.
  • Output: void.

Example

function ZoomDisplay() {
  const [displayK, setDisplayK] = useState(1);
  const ref = useRef<HTMLSpanElement>(null);
  const { activeFrame } = useEditorState();

  useFastSync(ref, !!activeFrame, (_v, _f, cam) => {
    setDisplayK(cam.k);
  }, { throttleHz: 15 });

  return <span ref={ref}>{Math.round(displayK * 100)}%</span>;
}

📌 Sibling specializations exported from the same module: useFastRectSync (selection/crop boxes), useFastAnchorSync, useFastMatrixSync, useFastSvgGroupSync, useFastMarchingAntsSync.


useFilterGesture(beginCommand?)

Specialized hook for adjustment slider interactions (exported from the AdjustmentDrawer's hooks/, not from core context). Implements the Interactive Transaction Pattern:

  1. On drag start → begin() fires the undoable beginCommand to set an undo checkpoint
  2. During drag → state updates coalesce into that single undo step
  3. On drag end → end() closes the gesture (one undo step for the entire drag)

API Specification

  • Input:
    • beginCommand?: FilterGestureCommand — Command instance (e.g. from usePluginCommands()) whose .execute() sets the undoable checkpoint. Omitted calls are safe.
  • Internal Logic: Manages the gesture lifecycle idempotently and toggles the filter cache's dragging flag so Worker filter jobs are suppressed mid-drag and exactly one full-res job is scheduled after settle.
  • Output: FilterGestureHandle — { begin, end, isDragging }.

Design Pattern (Undo Coalescing)

pointerdown → gesture.begin() → beginEditCmd.execute() → [UNDO CHECKPOINT SET]
pointermove → updateCmd.execute({ value }) → [NO NEW CHECKPOINT — coalesces]
pointermove → updateCmd.execute({ value }) → [NO NEW CHECKPOINT — coalesces]
pointerup   → gesture.end() → [GESTURE COMPLETE — one Cmd+Z undoes entire drag]

This produces a professional UX: dragging a slider back and forth creates exactly one undo step for the entire interaction, regardless of how many intermediate values were written.


Last updated: 2026-07-30