From 066cd466143fa56eff56de0baeece797a9b2f303 Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Sat, 11 May 2024 18:22:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(zhst/meta-cropperImage):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meta/src/cropperImage/CropperImage.tsx | 163 ++++++++++++++---- packages/meta/src/cropperImage/demo/basic.tsx | 12 +- 2 files changed, 139 insertions(+), 36 deletions(-) diff --git a/packages/meta/src/cropperImage/CropperImage.tsx b/packages/meta/src/cropperImage/CropperImage.tsx index c83342f..ea8acba 100644 --- a/packages/meta/src/cropperImage/CropperImage.tsx +++ b/packages/meta/src/cropperImage/CropperImage.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useEffect, forwardRef, useImperativeHandle, useContext, useState, ReactNode } from 'react' +import React, { useRef, useEffect, forwardRef, useImperativeHandle, useContext, useState, ReactNode, useMemo } from 'react' import classNames from 'classnames' import { fabric } from 'fabric' import { addEventListenerWrapper, getTransforms, pick } from '@zhst/func' @@ -12,7 +12,8 @@ import Align from 'rc-align'; import { getTransformRect } from '../BigImagePreview/bigImagePreviewHelper'; interface RectPro extends Rect { - imageRect: string + imageRect?: string; + type?: 'line' | 'rect'; // line:线,rect:矩形 } export interface CropperImageProps { @@ -20,7 +21,7 @@ export interface CropperImageProps { url?: string; width?: number; height?: number; - odList?: Rect[] // od框 + odList?: RectPro[] // od框 lineConfig?: fabric.Line; // 线条配置 editAble?: boolean; // 是否可编辑 selectedItem?: RectPro @@ -93,7 +94,7 @@ const CropperImage = forwardRef((props, } = props; const { getPrefixCls } = useContext(ConfigContext); const componentName = getPrefixCls('cropper-view', customizePrefixCls); - const [isMove, setIsMove] = useState(false) // 矩形是否在移动 + const [isDrawing, setIsDrawing] = useState(false) // 矩形是否在移动 const canvasRef = useRef(null); const currentShapeRef = useRef(null) @@ -103,6 +104,7 @@ const CropperImage = forwardRef((props, // 自定义弹框 const alginContainerRef: any = useRef(null); const alignRef: any = useRef(null); + const imageRectRef: any = useRef(null) // 初始化 - 图片 useEffect(() => { @@ -114,23 +116,37 @@ const CropperImage = forwardRef((props, }); // 监听形状选择事件 - addEventListenerWrapper(imageRef.current, EVENT_SHAPE_SELECT, (e: { detail: any; }) => { + imageRectRef.current = addEventListenerWrapper(imageRef.current, EVENT_SHAPE_SELECT, (e: { detail: any; }) => { // 选中的od const id = e.detail; if (id) { - const selectRectData = odList!.filter(_od => _od.id === id)?.[0] + // 获取选中的形状 + // const shapes = imgIns.getSelectShape(); + + const selectRectData = odList!.filter(_od => _od.id === id)?.[0] || {} const _data = percentToLength(selectRectData, viewerRef.current.canvas) const imageRect = getImageDataByPosition( { x: _data.x, y: _data.y, w: _data.w, h: _data.h }, { canvas: viewerRef.current.canvas } ) + // TODO: 换算成屏幕坐标 + // const axisRect = imgIns.imgRectAxisToCanvasAxisRect(selectShape); + // const rect = { + // x: axisRect.x2 > axisRect.x ? axisRect.x : axisRect.x2, + // y: axisRect.y2 > axisRect.y ? axisRect.y : axisRect.y2, + // w: Math.abs(axisRect.x2 - axisRect.x), + // h: Math.abs(axisRect.y2 - axisRect.y), + // }; + // getTransformRect(imageRectRef.current.image, ) id && onShapeSelected?.(id, { ..._data, imageRect, originData: selectRectData }) } }) return () => { + // 再次加载,销毁原来的实例 viewerRef?.current?.destroy?.(); viewerRef.current = null; + imageRectRef.current?.remove(); viewerRef.current?.clearShape?.(); } }, [url]) @@ -149,26 +165,22 @@ const CropperImage = forwardRef((props, }) return } else { + // 编辑模式 _viewer?.clearShape?.(); - const { targetTransform = {} } = _viewer if (type === 'rect') { + // 编辑模式 - 矩形绘制 currentShapeRef.current = initRect() - // 矩形 - 开始绘制实践 - cropStartRef.current = addEventListenerWrapper(imageRef.current, EVENT_CROP_START, () => { - setIsMove(true) - onCropStart?.() - }); - // 矩形 - 结束绘制实践 - cropEndRef.current = addEventListenerWrapper(imageRef.current, EVENT_CROP_END, (event: { detail: any; }) => { - const data = event.detail; - const targetPosition = { x: data.left, y: data.top, w: data.width, h: data.height } - const imageRect = getImageDataByPosition(targetPosition, { canvas: viewerRef.current.canvas }) - const targetData = getTransformRect(_viewer.image, targetTransform, targetPosition) - onCropEnd?.({ ...data , imageRect, targetTransform, targetData }) - setIsMove(false) - }) + // cropEndRef.current = addEventListenerWrapper(imageRef.current, EVENT_CROP_END, (event: { detail: any; }) => { + // const data = event.detail; + // const targetPosition = { x: data.left, y: data.top, w: data.width, h: data.height } + // const imageRect = getImageDataByPosition(targetPosition, { canvas: viewerRef.current.canvas }) + // const targetData = getTransformRect(_viewer.image, targetTransform, targetPosition) + // onCropEnd?.({ ...data , imageRect, targetTransform, targetData }) + // setIsMove(false) + // }) } else { + // 编辑模式 - 线绘制 currentShapeRef.current = initLine() } } @@ -179,17 +191,110 @@ const CropperImage = forwardRef((props, currentShapeRef.current?.destroy?.() currentShapeRef.current?.dispose?.() } - },[type, editAble, odList]) + },[type, editAble]) // 初始化 - 矩形圈选工具 const initRect = () => { - const currentCropper = new Cropper(imageRef.current, { - showMask: true, - scaleAble: false, - viewer: viewerRef.current, + const viewer = viewerRef?.current || {} + const { containerData = {}, targetTransform = {} } = viewer + let currentFabric: CanvasPro = new fabric.Canvas( + canvasRef.current, + { + backgroundColor: 'transparent', + width: containerData.width, + height: containerData.height, + selection: false, + } + ) + let rect: fabric.Rect + let origX: number, origY: number + + function addOrReplaceRect(newRect) { + // 移除画布上所有的矩形对象 + const objects = currentFabric.getObjects(); + for (let i = objects.length - 1; i >= 0; i--) { + if (objects[i].type === 'rect') { + currentFabric.remove(objects[i]); + } + } + // 添加新的矩形对象 + currentFabric.add(newRect); + } + + const checkPointInRect = (o: fabric.IEvent) => { + var pointer = currentFabric.getPointer(o.e); + var point = new fabric.Point(pointer.x, pointer.y); + let inRect = false + currentFabric.forEachObject(function(obj) { + if (obj.containsPoint(point) || obj._findTargetCorner(pointer)) { + inRect = true + } else { + inRect = false + } + }); + return inRect + } + + // 鼠标按下事件 + currentFabric.on('mouse:down', function(o) { + currentFabric.startDraw = true + var pointer = currentFabric.getPointer(o.e); + origX = pointer.x; + origY = pointer.y; + + // 创建一个矩形对象 + rect = new fabric.Rect({ + left: origX, + top: origY, + originX: 'left', + originY: 'top', + borderColor: '#09f', + cornerColor: '#09f', + cornerSize: 6, + width: pointer.x - origX, + height: pointer.y - origY, + angle: 0, + fill: 'transparent', + hasControls: true, + hasBorders: true, + lockRotation: true, // 锁定旋转 + hasRotatingPoint: false // 隐藏旋转控制点 + }); + + // 判断存在实例,并且鼠标点击在实例上 + if (checkPointInRect(o) && rect) { + } else { + addOrReplaceRect(rect) + // 监听移动 + rect.on('moving', o => console.log('o', o)) + rect.on('resizing', o => console.log('o', o)) + // 监听缩放 + rect.on('scaling', o => console.log('o', o)) + } + currentFabric.setActiveObject(currentFabric.item(0)); + }); + // 鼠标移动事件 + currentFabric.on('mouse:move', function(o) { + if (!currentFabric.startDraw) return; + var pointer = currentFabric.getPointer(o.e); + + if(origX > pointer.x){ + rect.set({ left: Math.abs(pointer.x) }); + } + + if(origY > pointer.y){ + rect.set({ top: Math.abs(pointer.y) }); + } + rect.set({ width: Math.abs(origX - pointer.x) }); + rect.set({ height: Math.abs(origY - pointer.y) }); + currentFabric.renderAll(); + }); + // 鼠标松开事件 + currentFabric.on('mouse:up', function(o) { + currentFabric.startDraw = false }); - return currentCropper + return currentFabric } // 初始化线条 @@ -316,7 +421,7 @@ const CropperImage = forwardRef((props, ref={imageRef} className={classNames(`${componentName}_img`)} /> - {showToast && selectedItem && !isMove && (<> + {showToast && selectedItem && !isDrawing && (<>
((props, })} )} - + {children}
); diff --git a/packages/meta/src/cropperImage/demo/basic.tsx b/packages/meta/src/cropperImage/demo/basic.tsx index 25ba03f..292958e 100644 --- a/packages/meta/src/cropperImage/demo/basic.tsx +++ b/packages/meta/src/cropperImage/demo/basic.tsx @@ -30,11 +30,10 @@ export default () => { odList={[ { "id": "123", - "x": 0.5519352, - "y": 0.2965385, - "w": 0.05185461, - "h": 0.24698898, - selectAble: false, + h: 0.30666666666666664, + w: 0.27170650730411683, + x: 0.19064741035856572, + y: 0.09703124999999999, }, { "id": "456", @@ -53,9 +52,8 @@ export default () => { }} onCropStart={() => console.log('矩形开始绘制')} onCropEnd={(data) => { - console.log('矩形结束绘制', data) setSelectedItem({ x: data.left, y: data.top, h: data.height, w: data.width }) - setImgUrl(data.imageRect as string) + setImgUrl(data?.imageRect as string) }} selectedItem={selectedItem} showToast={editAble}