Overview
React components and hooks for interactive object manipulation in SVG canvas applications. Provides transform gizmos, rotation handles, pivot controls, and gradient pickers for building drawing and design tools.
Installation
pnpm add @cloudillo/canvas-tools
Peer Dependencies:
react >= 18
react-svg-canvas
Components
Complete transform control for SVG objects with rotation, scaling, and positioning.
import { TransformGizmo } from '@cloudillo/canvas-tools'
function CanvasEditor() {
const [bounds , setBounds ] = useState ({ x : 100 , y : 100 , width : 200 , height : 150 })
const [rotation , setRotation ] = useState (0 )
return (
<svg width = {800 } height = {600 }>
<TransformGizmo
bounds = {bounds }
rotation = {rotation }
onBoundsChange = {setBounds }
onRotationChange = {setRotation }
showRotationHandle
showPivotHandle
/>
</svg >
)
}
Props (TransformGizmoProps):
bounds: Bounds - Object position and size { x, y, width, height }
rotation?: number - Rotation angle in degrees
pivot?: Point - Pivot point { x, y }
onBoundsChange?: (bounds: Bounds) => void - Bounds change callback
onRotationChange?: (angle: number) => void - Rotation change callback
onPivotChange?: (pivot: Point) => void - Pivot change callback
showRotationHandle?: boolean - Show rotation arc handle
showPivotHandle?: boolean - Show pivot point control
RotationHandle
Circular arc handle for rotating objects.
import { RotationHandle } from '@cloudillo/canvas-tools'
<RotationHandle
center = {{ x : 200 , y : 200 }}
radius = {80 }
currentAngle = {rotation }
onRotate = {(angle ) => setRotation (angle )}
snapAngles = {[0 , 45 , 90 , 135 , 180 , 225 , 270 , 315 ]}
/>
Props (RotationHandleProps):
center: Point - Center point of rotation
radius: number - Arc radius
currentAngle: number - Current rotation angle
onRotate: (angle: number) => void - Rotation callback
snapAngles?: number[] - Angles to snap to (default: 45-degree increments)
snapZoneRatio?: number - Snap zone size ratio
PivotHandle
Draggable handle for setting the pivot/rotation center point.
import { PivotHandle } from '@cloudillo/canvas-tools'
<PivotHandle
position = {{ x : 200 , y : 200 }}
bounds = {{ x : 100 , y : 100 , width : 200 , height : 200 }}
onPivotChange = {(point ) => setPivot (point )}
snapToCenter
snapThreshold = {10 }
/>
Props (PivotHandleProps):
position: Point - Current pivot position
bounds: Bounds - Object bounds for snapping
onPivotChange: (point: Point) => void - Position change callback
snapToCenter?: boolean - Snap to center when close
snapThreshold?: number - Snap distance threshold
GradientPicker
Complete gradient editor with color stops, angle control, and presets.
import { GradientPicker } from '@cloudillo/canvas-tools'
import type { Gradient } from '@cloudillo/canvas-tools'
function GradientEditor() {
const [gradient , setGradient ] = useState <Gradient >({
type : 'linear' ,
angle : 90 ,
stops : [
{ offset : 0 , color : '#ff0000' },
{ offset : 1 , color : '#0000ff' }
]
})
return (
<GradientPicker
value = {gradient }
onChange = {setGradient }
showPresets
showAngleControl
/>
)
}
Props (GradientPickerProps):
value: Gradient - Current gradient value
onChange: (gradient: Gradient) => void - Change callback
showPresets?: boolean - Show gradient preset grid
showAngleControl?: boolean - Show angle rotation control
showPositionControl?: boolean - Show radial gradient position control
GradientBar
Horizontal bar for editing gradient color stops.
import { GradientBar } from '@cloudillo/canvas-tools'
<GradientBar
stops = {gradient .stops }
onStopsChange = {(stops ) => setGradient ({ ...gradient , stops })}
selectedStop = {selectedIndex }
onSelectStop = {setSelectedIndex }
/>
GradientPresetGrid
Grid of predefined gradient presets.
import { GradientPresetGrid , GRADIENT_PRESETS } from '@cloudillo/canvas-tools'
<GradientPresetGrid
presets = {GRADIENT_PRESETS }
onSelect = {(preset ) => setGradient (expandGradient (preset .gradient ))}
category = "warm"
/>
AngleControl
Circular control for setting gradient angle.
import { AngleControl , DEFAULT_ANGLE_PRESETS } from '@cloudillo/canvas-tools'
<AngleControl
value = {angle }
onChange = {setAngle }
presets = {DEFAULT_ANGLE_PRESETS }
/>
PositionControl
XY position control for radial gradient centers.
import { PositionControl } from '@cloudillo/canvas-tools'
<PositionControl
value = {{ x : 0.5 , y : 0.5 }}
onChange = {setPosition }
/>
Hooks
Hook for managing transform gizmo state and interactions.
import { useTransformGizmo } from '@cloudillo/canvas-tools'
function CanvasObject ({ object , onUpdate }) {
const {
state ,
handlers ,
isDragging ,
isRotating ,
isResizing
} = useTransformGizmo ({
bounds : object.bounds ,
rotation : object.rotation ,
pivot : object.pivot ,
onBoundsChange : (bounds ) => onUpdate ({ ...object , bounds }),
onRotationChange : (rotation ) => onUpdate ({ ...object , rotation }),
onPivotChange : (pivot ) => onUpdate ({ ...object , pivot }),
snapAngles : [0 , 45 , 90 , 135 , 180 , 225 , 270 , 315 ],
maintainAspectRatio : true
})
return (
<g {...handlers }>
{/* Object rendering */ }
</g >
)
}
Options (TransformGizmoOptions):
bounds: Bounds - Initial bounds
rotation?: number - Initial rotation
pivot?: Point - Initial pivot
onBoundsChange?: (bounds: Bounds) => void
onRotationChange?: (angle: number) => void
onPivotChange?: (pivot: Point) => void
snapAngles?: number[] - Rotation snap angles
snapZoneRatio?: number - Snap sensitivity
maintainAspectRatio?: boolean - Lock aspect during resize
minWidth?: number - Minimum width constraint
minHeight?: number - Minimum height constraint
Returns (UseTransformGizmoReturn):
state: TransformGizmoState - Current transform state
handlers: TransformGizmoHandlers - Event handlers
isDragging: boolean - Move operation active
isRotating: boolean - Rotation operation active
isResizing: boolean - Resize operation active
Gradient Utilities
Creating Gradients
import {
DEFAULT_LINEAR_GRADIENT ,
DEFAULT_RADIAL_GRADIENT ,
expandGradient ,
compactGradient
} from '@cloudillo/canvas-tools'
// Start with defaults
const linear = { ...DEFAULT_LINEAR_GRADIENT }
const radial = { ...DEFAULT_RADIAL_GRADIENT }
// Expand compact notation to full gradient
const full = expandGradient ({ t : 'l' , a : 90 , s : [[0 , '#f00' ], [1 , '#00f' ]] })
// Compact for storage
const compact = compactGradient (full )
Manipulating Stops
import {
addStop ,
removeStop ,
updateStop ,
sortStops ,
reverseStops
} from '@cloudillo/canvas-tools'
// Add a stop at 50%
const newStops = addStop (gradient .stops , 0.5 , '#00ff00' )
// Remove stop at index 1
const filtered = removeStop (gradient .stops , 1 )
// Update stop color
const updated = updateStop (gradient .stops , 1 , { color : '#ff00ff' })
// Sort by offset
const sorted = sortStops (stops )
// Reverse direction
const reversed = reverseStops (stops )
Converting to CSS/SVG
import {
gradientToCSS ,
createLinearGradientDef ,
createRadialGradientDef
} from '@cloudillo/canvas-tools'
// CSS background value
const css = gradientToCSS (gradient )
// "linear-gradient(90deg, #ff0000 0%, #0000ff 100%)"
// SVG gradient definition
const svgLinear = createLinearGradientDef ('myGradient' , gradient )
const svgRadial = createRadialGradientDef ('myGradient' , gradient )
Color Utilities
import {
interpolateColor ,
getColorAtPosition
} from '@cloudillo/canvas-tools'
// Blend two colors
const mixed = interpolateColor ('#ff0000' , '#0000ff' , 0.5 )
// "#800080"
// Get color at position in gradient
const colorAt25 = getColorAtPosition (gradient .stops , 0.25 )
Geometry Utilities
import {
getCanvasCoordinates ,
getCanvasCoordinatesWithElement ,
getSvgElement
} from '@cloudillo/canvas-tools'
function handleClick (event : MouseEvent ) {
const svg = getSvgElement (event .target as Element )
const point = getCanvasCoordinates (event , svg )
console .log ('Canvas position:' , point .x , point .y )
}
Rotation Matrix
Pre-calculated trigonometry for performance-critical operations.
import {
createRotationMatrix ,
rotatePointWithMatrix ,
unrotatePointWithMatrix ,
rotateDeltaWithMatrix ,
unrotateDeltaWithMatrix
} from '@cloudillo/canvas-tools'
// Create matrix once
const matrix = createRotationMatrix (45 ) // 45 degrees
// Rotate points efficiently
const rotated = rotatePointWithMatrix ({ x : 100 , y : 0 }, matrix , center )
const original = unrotatePointWithMatrix (rotated , matrix , center )
// Rotate deltas (movement vectors)
const rotatedDelta = rotateDeltaWithMatrix ({ x : 10 , y : 0 }, matrix )
View Coordinates
import {
canvasToView ,
viewToCanvas ,
isPointInView ,
boundsIntersectsView
} from '@cloudillo/canvas-tools'
// Convert between canvas and view coordinates
const viewPoint = canvasToView (canvasPoint , viewTransform )
const canvasPoint = viewToCanvas (viewPoint , viewTransform )
// Visibility checks
const visible = isPointInView (point , viewport )
const intersects = boundsIntersectsView (objectBounds , viewport )
Resize Calculations
import {
initResizeState ,
calculateResizeBounds ,
getAnchorForHandle ,
getRotatedAnchorPosition
} from '@cloudillo/canvas-tools'
// Initialize resize operation
const resizeState = initResizeState (
bounds ,
rotation ,
'se' , // corner handle
startMousePosition
)
// Calculate new bounds during drag
const newBounds = calculateResizeBounds (
resizeState ,
currentMousePosition ,
{ maintainAspectRatio : true }
)
Types
Core Types
import type {
Point ,
Bounds ,
ResizeHandle ,
RotationState ,
PivotState ,
RotatedObjectBounds ,
TransformedObject ,
RotationMatrix ,
ResizeState
} from '@cloudillo/canvas-tools'
type Point = { x : number ; y : number }
type Bounds = {
x : number
y : number
width : number
height : number
}
type ResizeHandle = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'
Gradient Types
import type {
GradientType ,
GradientStop ,
Gradient ,
CompactGradient ,
GradientPreset ,
GradientPresetCategory
} from '@cloudillo/canvas-tools'
type GradientType = 'linear' | 'radial'
type GradientStop = {
offset : number // 0-1
color : string // hex color
}
type Gradient = {
type : GradientType
angle? : number // linear: degrees
cx? : number // radial: center x (0-1)
cy? : number // radial: center y (0-1)
stops : GradientStop []
}
type CompactGradient = {
t : 'l' | 'r' // type
a? : number // angle
cx? : number
cy? : number
s : [number , string ][] // stops as tuples
}
Constants
Rotation Defaults
import {
DEFAULT_SNAP_ANGLES ,
DEFAULT_SNAP_ZONE_RATIO ,
DEFAULT_PIVOT_SNAP_POINTS ,
DEFAULT_PIVOT_SNAP_THRESHOLD
} from '@cloudillo/canvas-tools'
// [0, 45, 90, 135, 180, 225, 270, 315]
console .log (DEFAULT_SNAP_ANGLES )
// 0.1 (10% of arc)
console .log (DEFAULT_SNAP_ZONE_RATIO )
Arc Sizing
import {
ARC_RADIUS_MIN_VIEWPORT_RATIO ,
ARC_RADIUS_MAX_VIEWPORT_RATIO ,
DEFAULT_ARC_PADDING ,
calculateArcRadius
} from '@cloudillo/canvas-tools'
const radius = calculateArcRadius ({
objectBounds : bounds ,
viewportSize : { width : 800 , height : 600 },
padding : DEFAULT_ARC_PADDING
})
Angle Presets
import { DEFAULT_ANGLE_PRESETS } from '@cloudillo/canvas-tools'
// [0, 45, 90, 135, 180, 225, 270, 315]
console .log (DEFAULT_ANGLE_PRESETS )
Gradient Presets
import {
GRADIENT_PRESETS ,
getPresetsByCategory ,
getPresetById ,
getCategories
} from '@cloudillo/canvas-tools'
// Get all categories
const categories = getCategories ()
// ['warm', 'cool', 'vibrant', 'subtle', 'monochrome']
// Get presets in a category
const warmGradients = getPresetsByCategory ('warm' )
// Get specific preset
const sunset = getPresetById ('sunset' )
See Also