From 414fb47c752501d5793b2c3da5d9a6c2ba530fdc Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 21 Jun 2024 10:21:13 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9A=B0=EC=84=A0=20=EC=88=AB=EC=9E=90=20?= =?UTF-8?q?=EB=8D=94=EB=B8=94=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EC=88=AB?= =?UTF-8?q?=EC=9E=90=EB=A7=8C=20=EC=88=98=EC=A0=95=20=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/util/canvas-util.js | 46 ++++---- src/components/Roof.jsx | 213 ++++++++++++++++++++++++------------ src/hooks/useCanvas.js | 9 -- 3 files changed, 170 insertions(+), 98 deletions(-) diff --git a/src/app/util/canvas-util.js b/src/app/util/canvas-util.js index c052986b..1cd367fc 100644 --- a/src/app/util/canvas-util.js +++ b/src/app/util/canvas-util.js @@ -77,23 +77,31 @@ export function anchorWrapper(anchorIndex, fn) { export const getDistance = (x1, y1, x2, y2) => { return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) } -// 선의 길이를 계산하는 함수 -export const calculateLineLength = (x1, y1, x2, y2) => { - return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) -} - -// 선과 텍스트를 그룹으로 묶는 함수 -export const createGroupWithLineAndText = (line, text) => { - return new fabric.Group([line, text]) -} - -export const calculateShapeLength = (shape) => { - // 도형의 원래 길이를 가져옵니다. - const originalLength = shape.width - - // 도형의 scaleX 값을 가져옵니다. - const scaleX = shape.scaleX - - // 도형의 현재 길이를 계산합니다. - return originalLength * scaleX + +// polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수 +export function addDistanceTextToPolygon(polygon) { + const points = polygon.get('points') + const texts = [] + + for (let i = 0; i < points.length; i++) { + const start = points[i] + const end = points[(i + 1) % points.length] // 다음 점 (마지막 점의 경우 첫번째 점으로) + const distance = Math.sqrt( + Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2), + ) // 두 점 사이의 거리 계산 + + const text = new fabric.Textbox(distance.toFixed(2), { + // 소수 둘째자리까지 표시 + left: (start.x + end.x) / 2, // 텍스트의 위치는 두 점의 중간 + top: (start.y + end.y) / 2, + fontSize: 20, + }) + + texts.push(text) + } + + return new fabric.Group([polygon, ...texts], { + // polygon과 텍스트들을 그룹화 + selectable: true, + }) } diff --git a/src/components/Roof.jsx b/src/components/Roof.jsx index 4da324c1..3a90b960 100644 --- a/src/components/Roof.jsx +++ b/src/components/Roof.jsx @@ -1,4 +1,4 @@ -import { createGroupWithLineAndText, getDistance } from '@/app/util/canvas-util' +import { addDistanceTextToPolygon, getDistance } from '@/app/util/canvas-util' import { useCanvas } from '@/hooks/useCanvas' import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' @@ -19,20 +19,86 @@ export default function Roof() { attachCustomControlOnPolygon, saveImage, handleFlip, - updateTextOnLineChange, } = useCanvas('canvas') useEffect(() => { - // IText 추가 - const text = new fabric.IText('Hello', { + let circle = new fabric.Circle({ + radius: 40, + fill: 'rgba(200, 0, 0, 0.3)', + originX: 'center', + originY: 'center', + }) + + let text = new fabric.Textbox('AJLoveChina', { + originX: 'center', + originY: 'center', + textAlign: 'center', + fontSize: 12, + }) + + let group = new fabric.Group([circle, text], { left: 100, top: 100, - fill: 'red', + originX: 'center', + originY: 'center', }) - text.on('editing:entered', () => { - console.log('editing:entered') + + group.on('mousedblclick', () => { + // textForEditing is temporary obj, + // and will be removed after editing + console.log(text.type) + let textForEditing = new fabric.Textbox(text.text, { + originX: 'center', + originY: 'center', + textAlign: text.textAlign, + fontSize: text.fontSize, + + left: group.left, + top: group.top, + }) + + // hide group inside text + text.visible = false + // note important, text cannot be hidden without this + group.addWithUpdate() + + textForEditing.visible = true + // do not give controls, do not allow move/resize/rotation on this + textForEditing.hasConstrols = false + + // now add this temporary obj to canvas + canvas.add(textForEditing) + canvas.setActiveObject(textForEditing) + // make the cursor showing + textForEditing.enterEditing() + textForEditing.selectAll() + + // editing:exited means you click outside of the textForEditing + textForEditing.on('editing:exited', () => { + let newVal = textForEditing.text + let oldVal = text.text + + // then we check if text is changed + if (newVal !== oldVal) { + text.set({ + text: newVal, + visible: true, + }) + + // comment before, you must call this + group.addWithUpdate() + + // we do not need textForEditing anymore + textForEditing.visible = false + canvas?.remove(textForEditing) + + // optional, buf for better user experience + canvas?.setActiveObject(group) + } + }) }) - canvas?.add(text) + + canvas?.add(group) }, [canvas]) const addRect = () => { @@ -107,7 +173,7 @@ export default function Roof() { [ { x: 100, y: 100 }, // 좌상단 { x: 500, y: 100 }, // 우상단 - { x: 750, y: 400 }, // 우하단 + { x: 750, y: 700 }, // 우하단 { x: 250, y: 400 }, // 좌하단 ], { @@ -119,86 +185,93 @@ export default function Roof() { objectCaching: false, }, ) - // attachCustomControlOnPolygon(trapezoid) - + attachCustomControlOnPolygon(trapezoid) const group = addDistanceTextToPolygon(trapezoid) addGroupClickEvent(group) - group.getObjects().forEach(function (object, index) { - if (object.type === 'i-text') { - addTextModifiedEvent(object, trapezoid, index) - } - }) canvas?.add(group) canvas?.renderAll() } // group에 클릭 이벤트를 추가하여 클릭 시 group을 제거하고 object들만 남기는 함수 function addGroupClickEvent(group) { - group.on('mousedown', function () { - const objects = group.getObjects() - canvas?.remove(group) - objects.forEach(function (object) { - canvas?.add(object) - }) - canvas?.renderAll() + group.on('selected', (e) => { + console.log(e) }) - } + group.on('mousedblclick', (e) => { + // textForEditing is temporary obj, + // and will be removed after editing + const pointer = canvas?.getPointer(e.e) // 마우스 클릭 위치 가져오기 + let minDistance = Infinity + let closestTextbox = null + const groupPoint = group.getCenterPoint() + group.getObjects().forEach(function (object) { + if (object.type === 'textbox') { + // 객체가 TextBox인지 확인 - // polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수 - function addDistanceTextToPolygon(polygon) { - const points = polygon.get('points') - const texts = [] + const objectCenter = object.getCenterPoint() // TextBox 객체의 중심점 가져오기 + const dx = objectCenter.x + groupPoint.x - pointer.x + const dy = objectCenter.y + groupPoint.y - pointer.y + const distance = Math.sqrt(dx * dx + dy * dy) // 마우스 클릭 위치와 TextBox 객체 사이의 거리 계산 - for (let i = 0; i < points.length; i++) { - const start = points[i] - const end = points[(i + 1) % points.length] // 다음 점 (마지막 점의 경우 첫번째 점으로) - const distance = Math.sqrt( - Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2), - ) // 두 점 사이의 거리 계산 - - const text = new fabric.IText(distance.toFixed(2), { - // 소수 둘째자리까지 표시 - left: (start.x + end.x) / 2, // 텍스트의 위치는 두 점의 중간 - top: (start.y + end.y) / 2, - fontSize: 10, - editable: true, + if (distance < minDistance) { + // 가장 짧은 거리를 가진 TextBox 객체 찾기 + minDistance = distance + closestTextbox = object + } + } }) - texts.push(text) - } + let textForEditing = new fabric.Textbox(closestTextbox.text, { + originX: 'center', + originY: 'center', + textAlign: closestTextbox.textAlign, + fontSize: closestTextbox.fontSize, + left: closestTextbox.left + groupPoint.x, + top: closestTextbox.top + groupPoint.y, + }) - return new fabric.Group([polygon, ...texts], { - // polygon과 텍스트들을 그룹화 - selectable: true, + // hide group inside text + closestTextbox.visible = false + // note important, text cannot be hidden without this + group.addWithUpdate() + + textForEditing.visible = true + // do not give controls, do not allow move/resize/rotation on this + textForEditing.hasConstrols = false + + // now add this temporary obj to canvas + canvas?.add(textForEditing) + canvas?.setActiveObject(textForEditing) + // make the cursor showing + textForEditing?.enterEditing() + textForEditing?.selectAll() + + // editing:exited means you click outside of the textForEditing + textForEditing?.on('editing:exited', () => { + let newVal = textForEditing.text + + // then we check if text is changed + closestTextbox.set({ + text: newVal, + visible: true, + }) + + // comment before, you must call this + group.addWithUpdate() + + // we do not need textForEditing anymore + textForEditing.visible = false + canvas?.remove(textForEditing) + + // optional, buf for better user experience + canvas?.setActiveObject(group) + }) }) } // IText를 수정할 때 해당 값을 길이로 갖는 다른 polygon을 생성하고 다시 그룹화하는 함수 function addTextModifiedEvent(text, polygon, index) { - text.on('editing:entered', function () { - console.log(123) - const newLength = parseFloat(text.text) - const points = polygon.get('points') - const start = points[index] - const end = points[(index + 1) % points.length] - const vector = { x: end.x - start.x, y: end.y - start.y } // start에서 end로의 벡터 - const length = Math.sqrt(vector.x * vector.x + vector.y * vector.y) // 벡터의 길이 (현재 거리) - const normalizedVector = { x: vector.x / length, y: vector.y / length } // 벡터를 정규화 (길이를 1로) - const scaledVector = { - x: normalizedVector.x * newLength, - y: normalizedVector.y * newLength, - } // 정규화된 벡터를 새로운 길이로 스케일링 - - // end 점을 새로운 위치로 이동 - end.x = start.x + scaledVector.x - end.y = start.y + scaledVector.y - - // polygon을 다시 그룹화 - const newGroup = addDistanceTextToPolygon(polygon) - addGroupClickEvent(newGroup) - canvas.add(newGroup) - canvas.renderAll() - }) + text.on('editing:exited', function () {}) } const randomColor = () => { diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js index 6d9559c1..9a188e58 100644 --- a/src/hooks/useCanvas.js +++ b/src/hooks/useCanvas.js @@ -3,7 +3,6 @@ import { fabric } from 'fabric' import { actionHandler, anchorWrapper, - calculateShapeLength, polygonPositionHandler, } from '@/app/util/canvas-util' @@ -495,13 +494,6 @@ export function useCanvas(id) { canvas?.renderAll() } - // 선의 길이가 변경될 때마다 텍스트를 업데이트하는 함수 - const updateTextOnLineChange = (group, text) => { - const length = calculateShapeLength(group) - text.set({ text: length.toString() }) - canvas?.renderAll() - } - return { canvas, addShape, @@ -516,6 +508,5 @@ export function useCanvas(id) { attachCustomControlOnPolygon, saveImage, handleFlip, - updateTextOnLineChange, } }