UNPKG

react-moveable

Version:

A React Component that create Moveable, Draggable, Resizable, Scalable, Rotatable, Warpable, Pinchable, Groupable.

374 lines (333 loc) 13.1 kB
import { throttle, getDirection, triggerEvent, multiply2, getAbsolutePosesByState, fillParams, getKeepRatioHeight, getKeepRatioWidth, } from "../utils"; import { MIN_SCALE } from "../consts"; import { setDragStart, getDragDist, getScaleDist, getPosByReverseDirection } from "../DraggerUtils"; import MoveableManager from "../MoveableManager"; import { renderAllDirections, renderDiagonalDirections } from "../renderDirection"; import { ScalableProps, ResizableProps, OnScaleGroup, OnScaleGroupEnd, Renderer, OnScaleGroupStart, DraggableProps, OnDragStart, OnDrag, SnappableState, GroupableProps, OnScaleStart, OnScale, OnScaleEnd, } from "../types"; import { directionCondition, triggerChildAble, } from "../groupUtils"; import MoveableGroup from "../MoveableGroup"; import Draggable from "./Draggable"; import { getRad, caculate, createRotateMatrix, plus } from "@moveable/matrix"; import CustomDragger, { setCustomDrag } from "../CustomDragger"; import { checkSnapScale } from "./Snappable"; import { isArray } from "@daybrush/utils"; export default { name: "scalable", ableGroup: "size", canPinch: true, render(moveable: MoveableManager<Partial<ResizableProps & ScalableProps>>, React: Renderer): any[] | undefined { const { resizable, scalable, edge } = moveable.props; if (!resizable && scalable) { if (edge) { return renderDiagonalDirections(moveable, React); } return renderAllDirections(moveable, React); } }, dragControlCondition: directionCondition, dragControlStart( moveable: MoveableManager<ScalableProps & DraggableProps, SnappableState>, e: any) { const { datas, pinchFlag, inputEvent } = e; const { target: inputTarget } = inputEvent; const direction = pinchFlag ? [1, 1] : getDirection(inputTarget); const { width, height, targetTransform, target, } = moveable.state; if (!direction || !target) { return false; } if (!pinchFlag) { setDragStart(moveable, { datas }); } datas.datas = {}; datas.transform = targetTransform; datas.prevDist = [1, 1]; datas.direction = direction; datas.width = width; datas.height = height; datas.startScale = [1, 1]; const params = fillParams<OnScaleStart>(moveable, e, { direction, set: (scale: number[]) => { datas.startScale = scale; }, dragStart: Draggable.dragStart( moveable, new CustomDragger().dragStart([0, 0], inputEvent), ) as OnDragStart, }); const result = triggerEvent(moveable, "onScaleStart", params); if (result !== false) { datas.isScale = true; moveable.state.snapDirection = direction; } return datas.isScale ? params : false; }, dragControl( moveable: MoveableManager<ScalableProps & DraggableProps & GroupableProps, SnappableState>, e: any) { const { datas, distX, distY, parentScale, parentDistance, parentFlag, pinchFlag, inputEvent, dragClient, } = e; const { prevDist, direction, width, height, transform, isScale, startScale, } = datas; if (!isScale) { return false; } const { throttleScale, parentMoveable, } = moveable.props; const keepRatio = moveable.props.keepRatio || parentScale; const state = moveable.state; const isWidth = direction[0] || !direction[1]; let scaleX: number = 1; let scaleY: number = 1; const startWidth = width * startScale[0]; const startHeight = height * startScale[1]; const ratio = isWidth ? startHeight / startWidth : startWidth / startHeight; if (parentScale) { scaleX = parentScale[0]; scaleY = parentScale[1]; } else if (pinchFlag) { if (parentDistance) { scaleX = (width + parentDistance) / width; scaleY = (height + parentDistance * height / width) / height; } } else { const dist = getDragDist({ datas, distX, distY }); let distWidth = direction[0] * dist[0]; let distHeight = direction[1] * dist[1]; if (keepRatio && width && height) { const rad = getRad([0, 0], dist); const standardRad = getRad([0, 0], direction); const ratioRad = getRad([0, 0], [startWidth, startHeight]); const size = Math.sqrt(distWidth * distWidth + distHeight * distHeight); const signSize = Math.cos(rad - standardRad) * size; if (!direction[0]) { // top, bottom distHeight = signSize; distWidth = getKeepRatioWidth(distHeight, isWidth, ratio); } else if (!direction[1]) { // left, right distWidth = signSize; distHeight = getKeepRatioHeight(distWidth, isWidth, ratio); } else { // two-way distWidth = Math.cos(ratioRad) * signSize; distHeight = Math.sin(ratioRad) * signSize; } } scaleX = (width + distWidth) / width; scaleY = (height + distHeight) / height; } scaleX = direction[0] ? scaleX * startScale[0] : startScale[0]; scaleY = direction[1] ? scaleY * startScale[1] : startScale[1]; if (scaleX === 0) { scaleX = (prevDist[0] > 0 ? 1 : -1) * MIN_SCALE; } if (scaleY === 0) { scaleY = (prevDist[1] > 0 ? 1 : -1) * MIN_SCALE; } const nowDist = [scaleX / startScale[0], scaleY / startScale[1]]; let scale = [scaleX, scaleY]; let snapDirection = direction; if (moveable.props.groupable) { snapDirection = [ (nowDist[0] >= 0 ? 1 : -1) * direction[0], (nowDist[1] >= 0 ? 1 : -1) * direction[1], ]; const stateDirection = state.snapDirection; if (isArray(stateDirection) && (stateDirection[0] || stateDirection[1])) { state.snapDirection = snapDirection; } } let snapDist = [0, 0]; if (!pinchFlag) { snapDist = checkSnapScale(moveable, nowDist, direction, snapDirection, datas); } if (keepRatio) { if (direction[0] && direction[1] && snapDist[0] && snapDist[1]) { if (Math.abs(snapDist[0]) > Math.abs(snapDist[1])) { snapDist[1] = 0; } else { snapDist[0] = 0; } } const isNoSnap = !snapDist[0] && !snapDist[1]; if (isNoSnap) { if (isWidth) { nowDist[0] = throttle(nowDist[0] * startScale[0], throttleScale!) / startScale[0]; } else { nowDist[1] = throttle(nowDist[1] * startScale[1], throttleScale!) / startScale[1]; } } if ( (direction[0] && !direction[1]) || (snapDist[0] && !snapDist[1]) || (isNoSnap && isWidth) ) { nowDist[0] += snapDist[0]; const snapHeight = getKeepRatioHeight(width * nowDist[0] * startScale[0], isWidth, ratio); nowDist[1] = snapHeight / height / startScale[1]; } else if ( (!direction[0] && direction[1]) || (!snapDist[0] && snapDist[1]) || (isNoSnap && !isWidth) ) { nowDist[1] += snapDist[1]; const snapWidth = getKeepRatioWidth(height * nowDist[1] * startScale[1], isWidth, ratio); nowDist[0] = snapWidth / width / startScale[0]; } } else { if (!snapDist[0]) { nowDist[0] = throttle(nowDist[0] * startScale[0], throttleScale!) / startScale[0]; } if (!snapDist[1]) { nowDist[1] = throttle(nowDist[1] * startScale[1], throttleScale!) / startScale[1]; } } const delta = [nowDist[0] / prevDist[0], nowDist[1] / prevDist[1]]; scale = multiply2(nowDist, startScale); datas.prevDist = nowDist; if (scaleX === prevDist[0] && scaleY === prevDist[1] && !parentMoveable) { return false; } const inverseDelta = !parentFlag && pinchFlag ? [0, 0] : getScaleDist(moveable, delta, direction, dragClient); const params = fillParams<OnScale>(moveable, e, { scale, direction, dist: nowDist, delta, transform: `${transform} scale(${scaleX}, ${scaleY})`, isPinch: !!pinchFlag, drag: Draggable.drag( moveable, setCustomDrag(moveable.state, inverseDelta, inputEvent), ) as OnDrag, }); triggerEvent(moveable, "onScale", params); return params; }, dragControlEnd(moveable: MoveableManager<ScalableProps>, e: any) { const { datas, isDrag } = e; if (!datas.isScale) { return false; } datas.isScale = false; triggerEvent(moveable, "onScaleEnd", fillParams<OnScaleEnd>(moveable, e, { isDrag, })); return isDrag; }, dragGroupControlCondition: directionCondition, dragGroupControlStart(moveable: MoveableGroup, e: any) { const { datas } = e; const params = this.dragControlStart(moveable, e); if (!params) { return false; } const direction = params.direction; const startPos = getPosByReverseDirection(getAbsolutePosesByState(moveable.state), direction); const events = triggerChildAble( moveable, this, "dragControlStart", datas, (child, childDatas) => { const pos = getPosByReverseDirection(getAbsolutePosesByState(child.state), direction); const [originalX, originalY] = caculate( createRotateMatrix(-moveable.rotation / 180 * Math.PI, 3), [pos[0] - startPos[0], pos[1] - startPos[1], 1], 3, ); childDatas.originalX = originalX; childDatas.originalY = originalY; return e; }, ); const nextParams: OnScaleGroupStart = { ...params, targets: moveable.props.targets!, events, }; const result = triggerEvent(moveable, "onScaleGroupStart", nextParams); datas.isScale = result !== false; return datas.isScale ? nextParams : false; }, dragGroupControl(moveable: MoveableGroup, e: any) { const { datas } = e; if (!datas.isScale) { return; } const params = this.dragControl(moveable, e); if (!params) { return; } const { scale, direction, dist } = params; const prevPos = getPosByReverseDirection(getAbsolutePosesByState(moveable.state), multiply2(direction, dist)); const events = triggerChildAble( moveable, this, "dragControl", datas, (_, childDatas) => { const [clientX, clientY] = caculate( createRotateMatrix(moveable.rotation / 180 * Math.PI, 3), [ childDatas.originalX * scale[0], childDatas.originalY * scale[1], 1, ], 3, ); return { ...e, parentScale: scale, dragClient: plus(prevPos, [clientX, clientY]) }; }, ); const nextParams: OnScaleGroup = { targets: moveable.props.targets!, events, ...params, }; triggerEvent(moveable, "onScaleGroup", nextParams); return nextParams; }, dragGroupControlEnd(moveable: MoveableGroup, e: any) { const { isDrag, datas } = e; if (!datas.isScale) { return; } this.dragControlEnd(moveable, e); triggerChildAble(moveable, this, "dragControlEnd", datas, e); const nextParams = fillParams<OnScaleGroupEnd>(moveable, e, { targets: moveable.props.targets!, isDrag, }); triggerEvent(moveable, "onScaleGroupEnd", nextParams); return isDrag; }, };