우선 숫자 더블클릭 시 숫자만 수정 가능
This commit is contained in:
parent
86522f67e1
commit
414fb47c75
@ -77,23 +77,31 @@ export function anchorWrapper(anchorIndex, fn) {
|
|||||||
export const getDistance = (x1, y1, x2, y2) => {
|
export const getDistance = (x1, y1, x2, y2) => {
|
||||||
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
|
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
|
||||||
}
|
}
|
||||||
// 선의 길이를 계산하는 함수
|
|
||||||
export const calculateLineLength = (x1, y1, x2, y2) => {
|
// polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수
|
||||||
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
|
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], {
|
||||||
export const createGroupWithLineAndText = (line, text) => {
|
// polygon과 텍스트들을 그룹화
|
||||||
return new fabric.Group([line, text])
|
selectable: true,
|
||||||
}
|
})
|
||||||
|
|
||||||
export const calculateShapeLength = (shape) => {
|
|
||||||
// 도형의 원래 길이를 가져옵니다.
|
|
||||||
const originalLength = shape.width
|
|
||||||
|
|
||||||
// 도형의 scaleX 값을 가져옵니다.
|
|
||||||
const scaleX = shape.scaleX
|
|
||||||
|
|
||||||
// 도형의 현재 길이를 계산합니다.
|
|
||||||
return originalLength * scaleX
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { useCanvas } from '@/hooks/useCanvas'
|
||||||
import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
@ -19,20 +19,86 @@ export default function Roof() {
|
|||||||
attachCustomControlOnPolygon,
|
attachCustomControlOnPolygon,
|
||||||
saveImage,
|
saveImage,
|
||||||
handleFlip,
|
handleFlip,
|
||||||
updateTextOnLineChange,
|
|
||||||
} = useCanvas('canvas')
|
} = useCanvas('canvas')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// IText 추가
|
let circle = new fabric.Circle({
|
||||||
const text = new fabric.IText('Hello', {
|
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,
|
left: 100,
|
||||||
top: 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,
|
||||||
})
|
})
|
||||||
canvas?.add(text)
|
|
||||||
|
// 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(group)
|
||||||
}, [canvas])
|
}, [canvas])
|
||||||
|
|
||||||
const addRect = () => {
|
const addRect = () => {
|
||||||
@ -107,7 +173,7 @@ export default function Roof() {
|
|||||||
[
|
[
|
||||||
{ x: 100, y: 100 }, // 좌상단
|
{ x: 100, y: 100 }, // 좌상단
|
||||||
{ x: 500, y: 100 }, // 우상단
|
{ x: 500, y: 100 }, // 우상단
|
||||||
{ x: 750, y: 400 }, // 우하단
|
{ x: 750, y: 700 }, // 우하단
|
||||||
{ x: 250, y: 400 }, // 좌하단
|
{ x: 250, y: 400 }, // 좌하단
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
@ -119,86 +185,93 @@ export default function Roof() {
|
|||||||
objectCaching: false,
|
objectCaching: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
// attachCustomControlOnPolygon(trapezoid)
|
attachCustomControlOnPolygon(trapezoid)
|
||||||
|
|
||||||
const group = addDistanceTextToPolygon(trapezoid)
|
const group = addDistanceTextToPolygon(trapezoid)
|
||||||
addGroupClickEvent(group)
|
addGroupClickEvent(group)
|
||||||
group.getObjects().forEach(function (object, index) {
|
|
||||||
if (object.type === 'i-text') {
|
|
||||||
addTextModifiedEvent(object, trapezoid, index)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
canvas?.add(group)
|
canvas?.add(group)
|
||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// group에 클릭 이벤트를 추가하여 클릭 시 group을 제거하고 object들만 남기는 함수
|
// group에 클릭 이벤트를 추가하여 클릭 시 group을 제거하고 object들만 남기는 함수
|
||||||
function addGroupClickEvent(group) {
|
function addGroupClickEvent(group) {
|
||||||
group.on('mousedown', function () {
|
group.on('selected', (e) => {
|
||||||
const objects = group.getObjects()
|
console.log(e)
|
||||||
canvas?.remove(group)
|
|
||||||
objects.forEach(function (object) {
|
|
||||||
canvas?.add(object)
|
|
||||||
})
|
|
||||||
canvas?.renderAll()
|
|
||||||
})
|
})
|
||||||
|
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인지 확인
|
||||||
|
|
||||||
|
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 객체 사이의 거리 계산
|
||||||
|
|
||||||
|
if (distance < minDistance) {
|
||||||
|
// 가장 짧은 거리를 가진 TextBox 객체 찾기
|
||||||
|
minDistance = distance
|
||||||
|
closestTextbox = object
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수
|
|
||||||
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.IText(distance.toFixed(2), {
|
|
||||||
// 소수 둘째자리까지 표시
|
|
||||||
left: (start.x + end.x) / 2, // 텍스트의 위치는 두 점의 중간
|
|
||||||
top: (start.y + end.y) / 2,
|
|
||||||
fontSize: 10,
|
|
||||||
editable: true,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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], {
|
// hide group inside text
|
||||||
// polygon과 텍스트들을 그룹화
|
closestTextbox.visible = false
|
||||||
selectable: true,
|
// 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을 생성하고 다시 그룹화하는 함수
|
// IText를 수정할 때 해당 값을 길이로 갖는 다른 polygon을 생성하고 다시 그룹화하는 함수
|
||||||
function addTextModifiedEvent(text, polygon, index) {
|
function addTextModifiedEvent(text, polygon, index) {
|
||||||
text.on('editing:entered', function () {
|
text.on('editing:exited', 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()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomColor = () => {
|
const randomColor = () => {
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { fabric } from 'fabric'
|
|||||||
import {
|
import {
|
||||||
actionHandler,
|
actionHandler,
|
||||||
anchorWrapper,
|
anchorWrapper,
|
||||||
calculateShapeLength,
|
|
||||||
polygonPositionHandler,
|
polygonPositionHandler,
|
||||||
} from '@/app/util/canvas-util'
|
} from '@/app/util/canvas-util'
|
||||||
|
|
||||||
@ -495,13 +494,6 @@ export function useCanvas(id) {
|
|||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 선의 길이가 변경될 때마다 텍스트를 업데이트하는 함수
|
|
||||||
const updateTextOnLineChange = (group, text) => {
|
|
||||||
const length = calculateShapeLength(group)
|
|
||||||
text.set({ text: length.toString() })
|
|
||||||
canvas?.renderAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canvas,
|
canvas,
|
||||||
addShape,
|
addShape,
|
||||||
@ -516,6 +508,5 @@ export function useCanvas(id) {
|
|||||||
attachCustomControlOnPolygon,
|
attachCustomControlOnPolygon,
|
||||||
saveImage,
|
saveImage,
|
||||||
handleFlip,
|
handleFlip,
|
||||||
updateTextOnLineChange,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user