Free Transform Tool Core

A set of functions to perform shape transformations, including, Scale
, Rotate
, Translate
& Warping
Installation
npm install @free-transform/core
Usage
Scale
scale types (Handles)
Scale handles can be specified origin points
[0, 0]
left-top
[0, 1]
left-bottom
[1, 0]
right-top
[1, 1]
right-bottom
[0, .5]
left-middle
[1, .5]
right-middle
[0.5, 0]
middle-top
[0.5, 1]
middle-bottom
[number, number]
any numeric value can be used to specify scale handle but it could introduce undesired behaviour
import {scale, createMatrixFromParams} from '@free-transform/core'
let element = {
x:0,
y:0,
width:100,
height:100,
matrix: createMatrixFromParams()
}
const onScaleHandleMouseDown = (event) => {
event.stopPropagation();
event.preventDefault();
const drag = scale([0,0 ], {
start: [event.pageX, event.clientY],
fromCenter: (event => event.altKey),
aspectRatio: (event) => event.shiftKey,
...element,
}, (payload) => {
element = { ...element, ...payload }
});
const up = () => {
document.removeEventListener('pointermove', drag);
document.removeEventListener('pointerup', up);
};
document.addEventListener("pointermove", drag)
document.addEventListener("pointerup", up)
}
Rotation
import {rotate, createMatrixFromParams, toRadians} from '@free-transform/core'
let element = {
x:0,
y:0,
width:100,
height:100,
matrix: createMatrixFromParams({
angle: toRadians(45)
})
}
const onRotateHandleMouseDown = (event) => {
event.stopPropagation();
event.preventDefault();
const drag = rotate({
start: [event.pageX,event.clientY]
offset: [0, 0],
...element,
snap: (event) => event.altKey,
snapDegree: 15,
}, (payload) => {
element = { ...element, ...payload }
});
const up = () => {
document.removeEventListener('pointermove', drag);
document.removeEventListener('pointerup', up);
};
document.addEventListener("pointermove", drag)
document.addEventListener("pointerup", up)
}
Translation (Dragging)
import {translate} from '@free-transform/core'
let element = {
x:0,
y:0,
}
const onElementMouseDown = (event) => {
event.stopPropagation();
const drag = translate({
x: element.x,
y: element.y,
start: [event.pageX,event.clientY]
}, (payload) => {
element = { ...element, ...payload }
});
const up = () => {
document.removeEventListener('pointermove', drag);
document.removeEventListener('pointerup', up);
};
document.addEventListener('pointermove', drag);
document.addEventListener('pointerup', up);
}
Warp (Perspective transofrmation)
import {warp, createMatrixFromParams, makeWarpPoints} from '@free-transform/core'
let element = {
x:0,
y:0,
width:100,
height:100,
matrix: createMatrixFromParams()
}
element.warp = makeWarpPoints(element.width, element.height)
const onElementMouseDown = (event) => {
event.stopPropagation();
const drag = warp([0, 0], {
start: [event.pageX,event.clientY],
warp: element.warp,
matrix: element.matrix,
}, (payload) => {
element = { ...element, ...payload }
});
const up = () => {
document.removeEventListener('pointermove', drag);
document.removeEventListener('pointerup', up);
};
document.addEventListener('pointermove', drag);
document.addEventListener('pointerup', up);
}
All together
in order to make all transformations works together, we need to generate a new Perspective matrix to work on homogeneous coordinates
import {
rotate,
warp,
scale,
createMatrixFromParams,
makeWarpPoints,
makePerspectiveMatrix,
multiply
} from '@free-transform/core'
let element = {
x:0,
y:0,
width:100,
height:100,
matrix: createMatrixFromParams()
}
element.warp = makeWarpPoints(element.width, element.height)
const onScaleHandleMouseDown = (event) => {
const drag = scale([0,0 ], {
start: [event.pageX, event.clientY],
fromCenter: (event => event.altKey),
aspectRatio: (event) => event.shiftKey,
...element,
perspectiveMatrix,
affineMatrix: element.matrix,
matrix: multiply(element.matrix, perspectiveMatrix)
}, (payload) => {
element = { ...element, ...payload }
});
}
const onRotateHandleMouseDown = (event) => {
const perspectiveMatrix = makePerspectiveMatrix(
makeWarpPoints(element.width, element.height),
element.warp
)
const drag = rotate({
start: [event.pageX,event.clientY]
offset: [0, 0],
...element,
snap: (event) => event.altKey,
snapDegree: 15,
affineMatrix: element.matrix,
matrix: multiply(element.matrix, perspectiveMatrix)
}, (payload) => {
element = { ...element, ...payload }
});
}
Output to css matrix3d
import {
createMatrixFromParams,
makeWarpPoints,
makePerspectiveMatrix,
multiply,
transpose,
matrixTranslate
} from '@free-transform/core'
let element = {
x:0,
y:0,
width:100,
height:100,
matrix: createMatrixFromParams()
}
element.warp = makeWarpPoints(element.width, element.height)
const perspectiveMatrix = makePerspectiveMatrix(
makeWarpPoints(element.width, element.height),
element.warp
)
const outputMatrix = multiply(
element.matrix,
perspectiveMatrix,
matrixTranslate(element.x, element.y)
) ;
const string = transpose(outputMatrix).map((v) =>
v.map((v) => Number(v.toFixed(10)))
);
const tansform = `matrix3d(${string})`;