ef-transform-handles
Attributes
boundsRequiredTransformBoundsBounding box to display handles for
targetstring | HTMLElementTarget element (for rotation calculation)
canvas-scalenumberCanvas zoom scale (fallback when context unavailable)
1enable-rotationbooleanShow rotation handle
falseenable-resizebooleanShow resize handles
trueenable-dragbooleanEnable drag to move
truecorners-onlybooleanShow only corner handles (hide edge handles)
falselock-aspect-ratiobooleanMaintain aspect ratio during resize
falserotation-stepnumberSnap rotation to degrees (e.g. 15 for 15deg increments)
min-sizenumberMinimum width/height during resize
10Properties
interactionMode'idle' | 'dragging' | 'resizing' | 'rotating'Current interaction state
Interactive resize and rotation handles for elements.
Basic Usage
Display handles for a positioned element:
<div class="relative w-[600px] h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-50"><ef-transform-handles.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}enable-rotationenable-resize></ef-transform-handles></div>
Drag handles to resize, drag rotation handle to rotate.
Bounds
Transform handles require bounds in screen coordinates:
const handles = document.querySelector('ef-transform-handles');handles.bounds = {x: 100, // Left positiony: 100, // Top positionwidth: 200, // Widthheight: 150, // Heightrotation: 0 // Optional rotation in degrees};
Resize Handles
Enable resize with corner and edge handles:
<ef-transform-handles.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}enable-resize></ef-transform-handles>
Corner Handles
Four corner handles resize proportionally by default:
- Northwest: Top-left corner
- Northeast: Top-right corner
- Southwest: Bottom-left corner
- Southeast: Bottom-right corner
Edge Handles
Four edge handles resize in one dimension:
- North: Top edge (height only)
- East: Right edge (width only)
- South: Bottom edge (height only)
- West: Left edge (width only)
Corners Only
Hide edge handles, show only corners:
<ef-transform-handles.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}enable-resizecorners-only></ef-transform-handles>
Rotation Handle
Enable rotation with top-center handle:
<div class="relative w-[600px] h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-50"><ef-transform-handles.bounds=${{ x: 150, y: 100, width: 200, height: 150, rotation: 15 }}enable-rotationenable-resize></ef-transform-handles></div>
Drag the rotation handle to rotate the bounds.
Rotation Step
Snap rotation to specific increments:
<ef-transform-handles.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}enable-rotationrotation-step="15"></ef-transform-handles>
Rotation snaps to 15deg increments: 0deg, 15deg, 30deg, 45deg, etc.
Aspect Ratio Lock
Maintain aspect ratio during resize:
<ef-transform-handles.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}enable-resizelock-aspect-ratio></ef-transform-handles>
All resize operations maintain the original aspect ratio.
Drag to Move
Enable dragging to move the bounds:
<ef-transform-handles.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}enable-drag></ef-transform-handles>
Click and drag the overlay to move.
Disable Drag
<ef-transform-handles.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}enable-drag="false"></ef-transform-handles>
Events
Listen for transformation events:
const handles = document.querySelector('ef-transform-handles');// Bounds changed (resize or move)handles.addEventListener('bounds-change', (e) => {const { bounds } = e.detail;console.log('New bounds:', bounds);// Update elementelement.style.left = `${bounds.x}px`;element.style.top = `${bounds.y}px`;element.style.width = `${bounds.width}px`;element.style.height = `${bounds.height}px`;});// Rotation changedhandles.addEventListener('rotation-change', (e) => {const { rotation } = e.detail;console.log('New rotation:', rotation);// Update elementelement.style.transform = `rotate(${rotation}deg)`;});
Interaction Modes
Transform handles track current interaction:
const handles = document.querySelector('ef-transform-handles');console.log(handles.interactionMode);// 'idle' | 'dragging' | 'resizing' | 'rotating'// Only one mode active at a time
Coordinate System
Transform handles work in screen pixel coordinates:
// Bounds are in screen pixels (not canvas coordinates)handles.bounds = {x: 100, // Screen xy: 100, // Screen ywidth: 200, // Screen widthheight: 150 // Screen height};// Events dispatch screen coordinateshandles.addEventListener('bounds-change', (e) => {const { bounds } = e.detail;// bounds.x, bounds.y are screen coordinates});
When using with canvas, convert between canvas and screen coordinates.
Canvas Scale
Provide canvas scale for zoom-aware rendering:
const handles = document.querySelector('ef-transform-handles');// Set scale directlyhandles.canvasScale = 1.5;// Or via context (automatic with pan-zoom)// <ef-pan-zoom>// <ef-transform-handles></ef-transform-handles>// </ef-pan-zoom>
Canvas scale ensures handles render at consistent size regardless of zoom.
Target Element
Provide target element for rotation calculations:
const handles = document.querySelector('ef-transform-handles');const element = document.getElementById('my-element');// String selectorhandles.target = '#my-element';// Or element referencehandles.target = element;
Target element's center is used as rotation origin.
Minimum Size
Set minimum bounds during resize:
<ef-transform-handles.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}enable-resizemin-size="50"></ef-transform-handles>
Width and height cannot be resized below 50 pixels.
Styling
Transform handles use CSS custom properties:
ef-transform-handles {/* Border color */--ef-transform-handles-border-color: #2196f3;/* Border during drag */--ef-transform-handles-dragging-border-color: #1976d2;/* Handle background */--ef-transform-handles-handle-color: #fff;/* Handle border */--ef-transform-handles-handle-border-color: #2196f3;/* Rotation handle color */--ef-transform-handles-rotate-handle-color: #4caf50;}
One-Way Data Flow
Transform handles follow one-way data flow:
- Parent sets
boundsprop - User interacts with handles
- Handles dispatch events
- Parent updates element
- Parent updates
boundsprop - Handles re-render
Never mutate the bounds prop directly from handle events.
Wheel Events
Transform handles forward wheel events to parent pan-zoom for seamless zooming:
<ef-pan-zoom><ef-canvas><!-- Wheel events on handles zoom the canvas --></ef-canvas></ef-pan-zoom>
Mouse wheel over handles zooms the canvas instead of being blocked.
Rotation Calculation
Rotation is calculated relative to target element's center:
// For rotated elementshandles.target = element;handles.bounds = {x: element.offsetLeft,y: element.offsetTop,width: element.offsetWidth,height: element.offsetHeight,rotation: getCurrentRotation(element)};// Rotation handle calculates delta from target center
Usage with Canvas
Transform handles integrate with ef-canvas:
const canvas = document.querySelector('ef-canvas');const handles = document.querySelector('ef-transform-handles');// Get element bounds from canvasconst data = canvas.getElementData('element-id');// Convert to screen coordinatesconst screenPos = canvas.canvasToScreenCoords(data.x, data.y);const scale = panZoom.scale;handles.bounds = {x: screenPos.x,y: screenPos.y,width: data.width * scale,height: data.height * scale,rotation: data.rotation};// Listen for changeshandles.addEventListener('bounds-change', (e) => {// Convert back to canvas coordinatesconst canvasPos = canvas.screenToCanvasCoords(e.detail.bounds.x, e.detail.bounds.y);canvas.updateElementPosition('element-id', canvasPos.x, canvasPos.y);});
Handle Features
- 8 resize handles: nw, n, ne, e, se, s, sw, w
- Rotation handle: Top-center circular handle (when enabled)
- Drag area: Click anywhere inside bounds to drag
- Visual feedback: Handles highlight on hover
- Smart cursors: Automatically set appropriate resize cursors
- Shift key: Hold to maintain aspect ratio during resize
CSS Customization
Use CSS variables to customize handle appearance:
.transform-handles {--ef-transform-handles-border-color: #3b82f6;--ef-transform-handles-handle-color: #ffffff;--ef-transform-handles-handle-border-color: #3b82f6;--ef-transform-handles-rotate-handle-color: #10b981;}
Pointer Events
Transform handles have pointer-events: auto for interactivity. Parent overlay layer should have pointer-events: none.
Important Notes
- Bounds are in absolute pixel coordinates
- Use
canvas-scalewhen inside pan-zoom to maintain handle size - Rotation is in degrees (0-360)
lock-aspect-ratiomaintains the initial aspect ratio- Handles capture pointer events but allow wheel events to pass through
- Minimum size prevents resizing below practical limits