UNPKG

@orca-fe/transformer

Version:
726 lines (695 loc) 24.1 kB
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } import { mat3, vec2 } from 'gl-matrix'; import pc from 'prefix-classnames'; import { roundBy } from '@orca-fe/tools'; var roundBy5 = roundBy(5); var roundBy45 = roundBy(45); var PREFIX = 'orca-transformer-box'; var px = pc(PREFIX); var minSize = 10; export function getResizeMode(classList) { if (classList.includes(px('root'))) { return 'move'; } else if (classList.includes(px('scale-handle-top'))) { return 'top'; } else if (classList.includes(px('scale-handle-bottom'))) { return 'bottom'; } else if (classList.includes(px('scale-handle-right'))) { return 'right'; } else if (classList.includes(px('scale-handle-left'))) { return 'left'; } else if (classList.includes(px('scale-handle-top-left'))) { return 'topLeft'; } else if (classList.includes(px('scale-handle-top-right'))) { return 'topRight'; } else if (classList.includes(px('scale-handle-bottom-left'))) { return 'bottomLeft'; } else if (classList.includes(px('scale-handle-bottom-right'))) { return 'bottomRight'; } else if (classList.includes(px('rotate-handle'))) { return 'rotate'; } return 'move'; } export function rad(degrees) { return degrees * Math.PI / 180; } export function deg(radians) { return radians * 180 / Math.PI; } var round = roundBy(0.001); /** * 获取变换信息(位置和旋转) * @param matrix 变换矩阵 * @returns { x: number; y: number; rotate: number } 变换信息 */ export function getTransformInfo(matrix) { var x = matrix[6]; var y = matrix[7]; // const width = round(Math.sqrt(matrix[0] ** 2 + matrix[1] ** 2)); // const height = round(Math.sqrt(matrix[3] ** 2 + matrix[4] ** 2)); var rotate = round(deg(Math.atan2(matrix[1], matrix[0]))); return { x, y, rotate }; } /** * 旋转矩阵 matrix 绕点 point 旋转 angle 角度 * @param matrix 变换矩阵 * @param point 旋转点 * @param angle 旋转角度 * @returns 旋转后的矩阵 */ function rotateMatrixAroundPoint(matrix, point, angle) { var translationMatrix = mat3.fromTranslation(mat3.create(), [point.x, point.y]); var rotationMatrix = mat3.fromRotation(mat3.create(), angle); var inverseTranslationMatrix = mat3.fromTranslation(mat3.create(), [-point.x, -point.y]); var resultMatrix = mat3.create(); mat3.multiply(resultMatrix, translationMatrix, rotationMatrix); mat3.multiply(resultMatrix, resultMatrix, inverseTranslationMatrix); mat3.multiply(resultMatrix, resultMatrix, matrix); return resultMatrix; } /** * 将点投影到矩阵上 * @param point 点 * @param matrix 矩阵 * @returns 投影后的点 */ function project(point, matrix) { var _vec2$transformMat = vec2.transformMat3(vec2.create(), [point.x, point.y], matrix), _vec2$transformMat2 = _slicedToArray(_vec2$transformMat, 2), x = _vec2$transformMat2[0], y = _vec2$transformMat2[1]; return { x, y }; } /** * 将点反投影到矩阵上 * @param point 点 * @param matrix 矩阵 * @returns 反投影后的点 */ function unproject(point, matrix) { var _vec2$transformMat3 = vec2.transformMat3(vec2.create(), [point.x, point.y], mat3.invert(mat3.create(), matrix)), _vec2$transformMat4 = _slicedToArray(_vec2$transformMat3, 2), x = _vec2$transformMat4[0], y = _vec2$transformMat4[1]; return { x, y }; } /** * 计算 bounds 的变化 * @param startBounds 原始 bounds * @param pointOffset 鼠标偏移 * @param options */ export function calcBoundsChange(startBounds, pointOffset, options = {}) { var top = startBounds.top, left = startBounds.left, width = startBounds.width, height = startBounds.height, _startBounds$rotate = startBounds.rotate, rotate = _startBounds$rotate === void 0 ? 0 : _startBounds$rotate; var _options$resizeType = options.resizeType, resizeType = _options$resizeType === void 0 ? 'move' : _options$resizeType, _options$eqRatio = options.eqRatio, eqRatio = _options$eqRatio === void 0 ? false : _options$eqRatio, _options$symmetrical = options.symmetrical, symmetrical = _options$symmetrical === void 0 ? false : _options$symmetrical; var x = pointOffset.x, y = pointOffset.y; if (resizeType === 'move') { /* 目标是框本身,平移 */ return { left: left + x, top: top + y }; } var mat = mat3.create(); var translateMat = mat3.create(); mat3.translate(translateMat, translateMat, vec2.fromValues(left, top)); var rotateMat = mat3.create(); mat3.rotate(rotateMat, rotateMat, rad(rotate)); mat3.multiply(mat, mat, translateMat); mat3.multiply(mat, mat, rotateMat); // 取得中心位置 var centerPoint = project({ x: 0.5 * width, y: 0.5 * height }, mat); // 如果是旋转操作 if (resizeType === 'rotate') { var rotateHandlePoint = project({ x: 0.5 * width, y: -30 }, mat); var cx = rotateHandlePoint.x + x - centerPoint.x; var cy = rotateHandlePoint.y + y - centerPoint.y; var newRotate = Math.round(deg(Math.atan2(cx, -cy))); if (eqRatio) { newRotate = roundBy45(newRotate); } else if (roundBy5(newRotate) % 90 === 0) { newRotate = roundBy5(newRotate); } // 绕中心点旋转 var matAfterRotate = rotateMatrixAroundPoint(mat, centerPoint, rad(newRotate - rotate)); var changedBounds = getTransformInfo(matAfterRotate); return { left: changedBounds.x, top: changedBounds.y, rotate: changedBounds.rotate }; } // 剩下的操作都是调整大小了 // 根据 resizeType 算出是哪些方位受影响 var _resizeType = resizeType.toLowerCase(); var affectTop = _resizeType.includes('top'); var affectLeft = _resizeType.includes('left'); var affectRight = _resizeType.includes('right'); var affectBottom = _resizeType.includes('bottom'); var affectVertical = affectTop || affectBottom; var affectHorizontal = affectLeft || affectRight; // 基准点,缩放的基准 var basePoint = { x: 0, y: 0 }; // 手柄点位,记录是通过哪个点开始拖拽的 var handlePoint = { x: 0, y: 0 }; // 默认情况下,手柄点位刚好和基准点中心对称 if (affectTop) { basePoint.y = startBounds.height; handlePoint.y = 0; } if (affectBottom) { basePoint.y = 0; handlePoint.y = startBounds.height; } if (affectLeft) { basePoint.x = startBounds.width; handlePoint.x = 0; } if (affectRight) { basePoint.x = 0; handlePoint.x = startBounds.width; } if (symmetrical) { basePoint.x = 0.5 * width; basePoint.y = 0.5 * height; } // 算出当前鼠标位置的原始位置(即相对未变换时的原点的坐标) var p = project(handlePoint, mat); var currentPoint = unproject({ x: p.x + x, y: p.y + y }, mat); var diffX = currentPoint.x - handlePoint.x; var diffY = currentPoint.y - handlePoint.y; // 根据鼠标偏移,在不影响最小值的情况下,计算出上下左右的变化范围 var diffRight = Math.min(width - minSize, diffX); var diffLeft = Math.max(minSize - width, diffX); var diffBottom = Math.max(minSize - height, diffY); var diffTop = Math.min(height - minSize, diffY); // 默认情况下新的位置变化(复用于等比缩放) var newTop = { top: diffTop, height: height - diffTop }; var newBottom = { top: 0, height: height + diffBottom }; var newRight = { left: 0, width: width + diffLeft }; var newLeft = { left: diffRight, width: width - diffRight }; // 计算得出需要缩放的比例 var xRatio = (symmetrical ? 2 : 1) * Math.max(minSize, Math.abs(currentPoint.x - basePoint.x)) / startBounds.width; var yRatio = (symmetrical ? 2 : 1) * Math.max(minSize, Math.abs(currentPoint.y - basePoint.y)) / startBounds.height; if (eqRatio) { // 等比 // eslint-disable-next-line no-nested-ternary var ratio = Math.max(xRatio, yRatio); if (affectVertical && !affectHorizontal) { ratio = yRatio; } if (affectHorizontal && !affectVertical) { ratio = xRatio; } // 根据比例计算出新组件的宽高 var hw = Math.round(ratio * startBounds.width); var hh = Math.round(ratio * startBounds.height); if (symmetrical) { // 中心等比缩放 newTop = { top: Math.round(basePoint.y - 0.5 * hh), height: hh }; newBottom = { top: Math.round(basePoint.y - 0.5 * hh), height: hh }; newLeft = { left: Math.round(basePoint.x - 0.5 * hw), width: hw }; newRight = { left: Math.round(basePoint.x - 0.5 * hw), width: hw }; } else { // 等比缩放 // eslint-disable-next-line no-lonely-if if (affectVertical !== affectHorizontal) { newTop = { top: height - hh, height: hh, left: 0.5 * (width - hw), width: hw }; newBottom = { height: hh, left: 0.5 * (width - hw), width: hw }; newLeft = { left: width - hw, width: hw, top: 0.5 * (height - hh), height: hh }; newRight = { width: hw, top: 0.5 * (height - hh), height: hh }; } else { newTop = { top: height - hh, height: hh }; newBottom = { height: hh }; newLeft = { left: width - hw, width: hw }; newRight = { width: hw }; } } } else if (symmetrical) { // 中心缩放 // 按照各自比例缩放 var _hw = Math.round(xRatio * startBounds.width); var _hh = Math.round(yRatio * startBounds.height); newTop = { top: Math.round(basePoint.y - 0.5 * _hh), height: _hh }; newBottom = { top: Math.round(basePoint.y - 0.5 * _hh), height: _hh }; newLeft = { left: Math.round(basePoint.x - 0.5 * _hw), width: _hw }; newRight = { left: Math.round(basePoint.x - 0.5 * _hw), width: _hw }; } var newBounds = _objectSpread(_objectSpread({}, startBounds), {}, { left: 0, top: 0 }); // 普通缩放 switch (resizeType) { case 'top': /* 上↑ */ newBounds = _objectSpread(_objectSpread({}, newBounds), newTop); break; case 'bottom': /* 下↓ */ newBounds = _objectSpread(_objectSpread({}, newBounds), newBottom); break; case 'right': /* 右→ */ newBounds = _objectSpread(_objectSpread({}, newBounds), newRight); break; case 'left': /* 左← */ newBounds = _objectSpread(_objectSpread({}, newBounds), newLeft); break; case 'topLeft': /* 左上↖ */ newBounds = _objectSpread(_objectSpread(_objectSpread({}, newBounds), newLeft), newTop); break; case 'topRight': /* 右上↗ */ newBounds = _objectSpread(_objectSpread(_objectSpread({}, newBounds), newRight), newTop); break; case 'bottomLeft': /* 左下↙ */ newBounds = _objectSpread(_objectSpread(_objectSpread({}, newBounds), newLeft), newBottom); break; case 'bottomRight': /* 右下↘ */ newBounds = _objectSpread(_objectSpread(_objectSpread({}, newBounds), newRight), newBottom); break; default: } // 转换起点坐标 var projectLT = project({ x: newBounds.left, y: newBounds.top }, mat); return _objectSpread(_objectSpread({}, newBounds), {}, { left: projectLT.x, top: projectLT.y }); } /** * 判断 bounds 的中心点是否在 limit 内,如果不是,则生成符合限制的新 bounds * @param bounds * @param limit */ export function calcLimitBounds(bounds, limit) { var top = bounds.top, left = bounds.left, width = bounds.width, height = bounds.height, _bounds$rotate = bounds.rotate, rotate = _bounds$rotate === void 0 ? 0 : _bounds$rotate; var _ref = limit || {}, _ref$top = _ref.top, limitTop = _ref$top === void 0 ? -Infinity : _ref$top, _ref$left = _ref.left, limitLeft = _ref$left === void 0 ? -Infinity : _ref$left, _ref$width = _ref.width, limitWidth = _ref$width === void 0 ? Infinity : _ref$width, _ref$height = _ref.height, limitHeight = _ref$height === void 0 ? Infinity : _ref$height; var mat = mat3.create(); var translateMat = mat3.create(); mat3.translate(translateMat, translateMat, vec2.fromValues(left, top)); var rotateMat = mat3.create(); mat3.rotate(rotateMat, rotateMat, rad(rotate)); mat3.multiply(mat, mat, translateMat); mat3.multiply(mat, mat, rotateMat); // 取得中心位置 var centerPoint = project({ x: 0.5 * width, y: 0.5 * height }, mat); var newBounds = _objectSpread({}, bounds); var changed = false; if (centerPoint.x < limitLeft) { newBounds.left += limitLeft - centerPoint.x; changed = true; } if (centerPoint.x > limitLeft + limitWidth) { newBounds.left += limitLeft + limitWidth - centerPoint.x; changed = true; } if (centerPoint.y < limitTop) { newBounds.top += limitTop - centerPoint.y; changed = true; } if (centerPoint.y > limitTop + limitHeight) { newBounds.top += limitTop + limitHeight - centerPoint.y; changed = true; } return changed ? newBounds : bounds; } /** * 计算 bounds 的变化 * @param startBounds 原始 bounds * @param pointOffset 鼠标偏移 * @param options */ export var calcBoundsChangeBack = (startBounds, _currentPoint, pointOffset, options = {}) => { var _options$resizeType2 = options.resizeType, resizeType = _options$resizeType2 === void 0 ? 'move' : _options$resizeType2, _options$eqRatio2 = options.eqRatio, eqRatio = _options$eqRatio2 === void 0 ? false : _options$eqRatio2, _options$symmetrical2 = options.symmetrical, symmetrical = _options$symmetrical2 === void 0 ? false : _options$symmetrical2; var x = pointOffset.x, y = pointOffset.y; var top = startBounds.top, left = startBounds.left, width = startBounds.width, height = startBounds.height; if (resizeType === 'move') { /* 目标是框本身,平移 */ return { left: left + x, top: top + y }; } // 基准点 var centerPoint = { x: startBounds.left + 0.5 * startBounds.width, y: startBounds.top + 0.5 * startBounds.height }; // 当前位置(需要基于 pointOffset 算出来 var currentPoint = _objectSpread({}, centerPoint); // 基准点,缩放的基准 var basePoint = _objectSpread({}, centerPoint); // 如果是旋转操作 if (resizeType === 'rotate') { currentPoint.x = centerPoint.x + pointOffset.x; currentPoint.y = startBounds.top - 32 + pointOffset.y; var _x = currentPoint.x - centerPoint.x; var _y = currentPoint.y - centerPoint.y; var rotate = Math.atan2(_x, -_y) / Math.PI * 180; return _objectSpread(_objectSpread({}, startBounds), {}, { rotate }); } // 根据鼠标偏移,在不影响最小值的情况下,计算出上下左右的变化范围 var diffRight = Math.min(width - minSize, x); var diffLeft = Math.max(minSize - width, x); var diffBottom = Math.max(minSize - height, y); var diffTop = Math.min(height - minSize, y); // 默认情况下新的位置变化(复用于等比缩放) var newTop = { top: top + diffTop, height: height - diffTop }; var newBottom = { height: height + diffBottom }; var newRight = { width: width + diffLeft }; var newLeft = { left: left + diffRight, width: width - diffRight }; var _resizeType = resizeType.toLowerCase(); var affectTop = _resizeType.includes('top'); var affectLeft = _resizeType.includes('left'); var affectRight = _resizeType.includes('right'); var affectBottom = _resizeType.includes('bottom'); var affectVertical = affectTop || affectBottom; var affectHorizontal = affectLeft || affectRight; // 计算 basePoint 位置 // currentPoint 刚好和 basePoint 取反 if (affectTop) { basePoint.y = startBounds.top + startBounds.height; currentPoint.y = startBounds.top; } if (affectBottom) { basePoint.y = startBounds.top; currentPoint.y = startBounds.top + startBounds.height; } if (affectLeft) { basePoint.x = startBounds.left + startBounds.width; currentPoint.x = startBounds.left; } if (affectRight) { basePoint.x = startBounds.left; currentPoint.x = startBounds.left + startBounds.width; } if (symmetrical) { // 如果是以中心为基点变化,则 basePoint 就是 centerPoint basePoint.x = centerPoint.x; basePoint.y = centerPoint.y; } // currentPoint 偏移 currentPoint.x += pointOffset.x; currentPoint.y += pointOffset.y; // 计算得出需要缩放的比例 var xRatio = (symmetrical ? 2 : 1) * Math.max(minSize, Math.abs(currentPoint.x - basePoint.x)) / startBounds.width; var yRatio = (symmetrical ? 2 : 1) * Math.max(minSize, Math.abs(currentPoint.y - basePoint.y)) / startBounds.height; if (eqRatio) { // 等比 // eslint-disable-next-line no-nested-ternary var ratio = Math.max(xRatio, yRatio); if (affectVertical && !affectHorizontal) { ratio = yRatio; } if (affectHorizontal && !affectVertical) { ratio = xRatio; } // 根据比例计算出新组件的宽高 var hw = Math.round(ratio * startBounds.width); var hh = Math.round(ratio * startBounds.height); if (symmetrical) { // 中心等比缩放 newTop = { top: Math.round(basePoint.y - 0.5 * hh), height: hh }; newBottom = { top: Math.round(basePoint.y - 0.5 * hh), height: hh }; newLeft = { left: Math.round(basePoint.x - 0.5 * hw), width: hw }; newRight = { left: Math.round(basePoint.x - 0.5 * hw), width: hw }; } else { // 等比缩放 // eslint-disable-next-line no-lonely-if if (affectVertical !== affectHorizontal) { newTop = { top: top + height - hh, height: hh, left: left + 0.5 * (width - hw), width: hw }; newBottom = { height: hh, left: left + 0.5 * (width - hw), width: hw }; newLeft = { left: left + width - hw, width: hw, top: top + 0.5 * (height - hh), height: hh }; newRight = { width: hw, top: top + 0.5 * (height - hh), height: hh }; } else { newTop = { top: top + height - hh, height: hh }; newBottom = { height: hh }; newLeft = { left: left + width - hw, width: hw }; newRight = { width: hw }; } } } else if (symmetrical) { // 中心缩放 // 按照各自比例缩放 var _hw2 = Math.round(xRatio * startBounds.width); var _hh2 = Math.round(yRatio * startBounds.height); newTop = { top: Math.round(basePoint.y - 0.5 * _hh2), height: _hh2 }; newBottom = { top: Math.round(basePoint.y - 0.5 * _hh2), height: _hh2 }; newLeft = { left: Math.round(basePoint.x - 0.5 * _hw2), width: _hw2 }; newRight = { left: Math.round(basePoint.x - 0.5 * _hw2), width: _hw2 }; } // 普通缩放 switch (resizeType) { case 'top': /* 上↑ */ return _objectSpread(_objectSpread({}, startBounds), newTop); case 'bottom': /* 下↓ */ return _objectSpread(_objectSpread({}, startBounds), newBottom); case 'right': /* 右→ */ return _objectSpread(_objectSpread({}, startBounds), newRight); case 'left': /* 左← */ return _objectSpread(_objectSpread({}, startBounds), newLeft); case 'topLeft': /* 左上↖ */ return _objectSpread(_objectSpread(_objectSpread({}, startBounds), newLeft), newTop); case 'topRight': /* 右上↗ */ return _objectSpread(_objectSpread(_objectSpread({}, startBounds), newRight), newTop); case 'bottomLeft': /* 左下↙ */ return _objectSpread(_objectSpread(_objectSpread({}, startBounds), newLeft), newBottom); case 'bottomRight': /* 右下↘ */ return _objectSpread(_objectSpread(_objectSpread({}, startBounds), newRight), newBottom); } return {}; }; export var getPointByEvent = event => { if (event instanceof TouchEvent) { var _event$touches, _event$touches2; return { x: (_event$touches = event.touches) === null || _event$touches === void 0 || (_event$touches = _event$touches[0]) === null || _event$touches === void 0 ? void 0 : _event$touches.clientX, y: (_event$touches2 = event.touches) === null || _event$touches2 === void 0 || (_event$touches2 = _event$touches2[0]) === null || _event$touches2 === void 0 ? void 0 : _event$touches2.clientY }; } return { x: event.pageX, y: event.pageY }; }; export var getPointOffset = (p1, p2) => ({ x: p2.x - p1.x, y: p2.y - p1.y });