transform
Apply visual transformations to move elements
The transform
plugin applies visual movement to elements on screen. While other plugins calculate where elements should go, transform applies the visual changes. Handles both HTML and SVG elements automatically and supports custom transform functions for advanced effects.
transform(); // Default CSS translate movement
transform(customFunction); // Custom transform implementation
Note: Transform is included by default with lowest priority (-1000) ensuring it runs after all other position calculations are complete.
Basic Usage
import { createSignal } from 'solid-js';
import { transform, useDraggable } from '@neodrag/solid';
function TransformExamples() {
const [standardRef, setStandardRef] = createSignal();
const [customRef, setCustomRef] = createSignal();
useDraggable(standardRef, [transform()]);
useDraggable(customRef, [
transform((data) => {
data.rootNode.style.transform = `
translate(${data.offsetX}px, ${data.offsetY}px)
rotate(${data.offsetX * 0.5}deg)
`;
}),
]);
return (
<div>
<div ref={setStandardRef}>Standard dragging</div>
<div ref={setCustomRef}>Rotates while dragging</div>
</div>
);
}
Custom Transform Effects
import { createSignal } from 'solid-js';
import { transform, useDraggable } from '@neodrag/solid';
function CustomTransforms() {
const [scaleRef, setScaleRef] = createSignal();
const [rotateRef, setRotateRef] = createSignal();
const [skewRef, setSkewRef] = createSignal();
// Scaling effect
const scaleTransform = (data) => {
const scale = 1 + Math.abs(data.offsetX) * 0.001;
data.rootNode.style.transform = `
translate(${data.offsetX}px, ${data.offsetY}px)
scale(${scale})
`;
};
// Rotation effect
const rotateTransform = (data) => {
const rotation = data.offsetX * 0.5;
data.rootNode.style.transform = `
translate(${data.offsetX}px, ${data.offsetY}px)
rotate(${rotation}deg)
`;
};
// Skew effect
const skewTransform = (data) => {
const skewX = data.offsetY * 0.1;
data.rootNode.style.transform = `
translate(${data.offsetX}px, ${data.offsetY}px)
skewX(${skewX}deg)
`;
};
useDraggable(scaleRef, [transform(scaleTransform)]);
useDraggable(rotateRef, [transform(rotateTransform)]);
useDraggable(skewRef, [transform(skewTransform)]);
return (
<div>
<div ref={setScaleRef}>Scales while dragging</div>
<div ref={setRotateRef}>Rotates while dragging</div>
<div ref={setSkewRef}>Skews while dragging</div>
</div>
);
}
Advanced Examples
Physics-Based Movement
// Simulated momentum and friction
let velocity = { x: 0, y: 0 };
let lastPosition = { x: 0, y: 0 };
let lastTime = Date.now();
const physicsTransform = (data) => {
const now = Date.now();
const deltaTime = now - lastTime;
if (deltaTime > 0) {
velocity.x = ((data.offsetX - lastPosition.x) / deltaTime) * 1000;
velocity.y = ((data.offsetY - lastPosition.y) / deltaTime) * 1000;
}
// Apply friction
velocity.x *= 0.95;
velocity.y *= 0.95;
// Visual effect based on velocity
const blur =
Math.min(Math.abs(velocity.x) + Math.abs(velocity.y), 20) * 0.1;
data.rootNode.style.transform = `
translate(${data.offsetX}px, ${data.offsetY}px)
scale(${1 + blur * 0.01})
`;
data.rootNode.style.filter = `blur(${blur}px)`;
lastPosition = { x: data.offsetX, y: data.offsetY };
lastTime = now;
};
Elastic Snapping
// Elastic movement toward snap points
const snapPoints = [
{ x: 0, y: 0 },
{ x: 200, y: 0 },
{ x: 100, y: 150 },
];
const elasticTransform = (data) => {
// Find nearest snap point
const nearest = snapPoints.reduce(
(best, point) => {
const distance = Math.sqrt(
Math.pow(data.offsetX - point.x, 2) +
Math.pow(data.offsetY - point.y, 2),
);
return distance < best.distance ? { point, distance } : best;
},
{ point: snapPoints[0], distance: Infinity },
);
// Calculate elastic effect
const elasticity = Math.min(nearest.distance / 50, 1);
const pullX = (nearest.point.x - data.offsetX) * 0.1;
const pullY = (nearest.point.y - data.offsetY) * 0.1;
data.rootNode.style.transform = `
translate(${data.offsetX + pullX}px, ${data.offsetY + pullY}px)
scale(${1 - elasticity * 0.1})
`;
};
3D Perspective
// 3D rotation based on position
const perspectiveTransform = (data) => {
const rotateX = data.offsetY * 0.2;
const rotateY = -data.offsetX * 0.2;
const translateZ = Math.abs(data.offsetX) + Math.abs(data.offsetY);
data.rootNode.style.transform = `
translate(${data.offsetX}px, ${data.offsetY}px)
rotateX(${rotateX}deg)
rotateY(${rotateY}deg)
translateZ(${translateZ * 0.5}px)
`;
data.rootNode.style.transformStyle = 'preserve-3d';
};
SVG Support
Transform automatically handles SVG elements using proper SVG transformation matrices:
// For custom SVG transforms
const svgTransform = (data) => {
if (data.rootNode instanceof SVGElement) {
const svg = data.rootNode.ownerSVGElement;
const transform = svg.createSVGTransform();
// Custom SVG transformation
transform.setMatrix(
svg
.createSVGMatrix()
.translate(data.offsetX, data.offsetY)
.rotate(data.offsetX * 0.5)
.scale(1 + Math.abs(data.offsetY) * 0.001),
);
data.rootNode.transform.baseVal.clear();
data.rootNode.transform.baseVal.appendItem(transform);
} else {
// Fallback for HTML elements
data.rootNode.style.transform = `translate(${data.offsetX}px, ${data.offsetY}px)`;
}
};
Performance Considerations
GPU Acceleration
The default transform uses modern CSS properties for hardware acceleration:
// Modern (default) - GPU accelerated
element.style.translate = '100px 50px';
// Legacy - may be slower
element.style.transform = 'translate(100px, 50px)';
Efficient Updates
Transform uses ctx.effect.paint()
for optimized rendering:
// Automatically batched with requestAnimationFrame
ctx.effect.paint(() => {
element.style.translate = `${offsetX}px ${offsetY}px`;
});
Custom Optimization
For high-frequency updates, consider throttling:
let isScheduled = false;
const optimizedTransform = (data) => {
if (!isScheduled) {
isScheduled = true;
requestAnimationFrame(() => {
data.rootNode.style.transform = `translate(${data.offsetX}px, ${data.offsetY}px)`;
isScheduled = false;
});
}
};
Plugin Priority
Transform has the lowest priority (-1000) ensuring it runs after all position calculations:
// Plugin execution order:
// 1. High priority plugins (position, etc.)
// 2. Medium priority plugins (grid, bounds, etc.)
// 3. Transform (priority: -1000) - applies final visual changes
How It Works
The transform plugin:
-
Setup phase:
- Detects HTML vs SVG elements
- Applies initial position if element has existing offset
-
Drag phase:
- Runs with lowest priority (after all other plugins)
- Uses
ctx.effect.paint()
for optimal rendering - Applies either custom function or default movement
-
Default behavior:
- HTML:
element.style.translate = "Xpx Ypx"
- SVG: Creates and applies SVG transform matrix
- HTML:
-
Custom functions:
- Receive current offset and root node
- Can apply any CSS transform or SVG transformation
- Run on every drag update
API Reference
function transform(
customFn?: (data: {
offsetX: number;
offsetY: number;
rootNode: HTMLElement | SVGElement;
}) => void,
): Plugin;
Parameters:
customFn
- Optional custom transform functionoffsetX
- Current X position relative to drag startoffsetY
- Current Y position relative to drag startrootNode
- The element being transformed
Behavior:
- Lowest priority (-1000) - runs after other plugins
- Non-cancelable - always applies transformations
- Supports live updates during drag
- Automatic HTML vs SVG handling
- Uses
effect.paint()
for optimized rendering
Returns: A plugin object for use with draggable.