캔버스 요소들이 따라가게 하는 방법은 여러 방법을 생각했었다.
import { Node, NodeData } from "@/konva_mindmap/types/Node";
const maxDistance = 400;
export function checkFollowing(data: NodeData, root: Node, updateNode) {
if (!root.children) return;
root.children.forEach((childId) => {
const childNode = data[childId];
if (!childNode) return;
adjustNodePositions(root, childNode, updateNode);
// 재귀적으로 자식 노드들도 확인
checkFollowing(data, childNode, updateNode);
});
}
export function adjustNodePositions(parentNode: Node, targetNode: Node, updateNode) {
// 현재 거리 계산
const dx = targetNode.location.x - parentNode.location.x;
const dy = targetNode.location.y - parentNode.location.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > maxDistance) {
// 단위 방향 벡터 계산
const directionX = dx / distance;
const directionY = dy / distance;
// 새로운 위치 계산
// maxDistance를 유지하도록 위치 조정
const newX = parentNode.location.x + directionX * maxDistance;
const newY = parentNode.location.y + directionY * maxDistance;
// 노드 위치 업데이트
updateNode(targetNode.id, {
location: {
x: newX,
y: newY,
},
});
}
}
처음 로직의 경우 생각하기가 그렇게 어렵진 않았다.
부모 요소와 자식 요소간의 선의 길이, 즉 중점 간의 길이를 계산하여 내가설정해놓은 일정 길이를 넘게 된다면, 넘어간 만큼 부모노드의 이동 각도를 계산하여 자식 노드들도 이동시키면 된다.
화면 기록 2024-11-17 오전 1.36.16.mov
하지만 여기에서 자식 노드들이 일정하게 부모의 이동 각도에 의존하다 보니, 한 곳으로 뭉치는 현상이 있었다. 여기서 문제는 한 곳으로 뭉치는 과정에서 상태가 계속해서 바뀌고 있는 상태이기 때문에 data를 의존성 배열로 넣어놓은 useEffect
내의 충돌 방지가 실행된다는 점이다. 이 때문에 노드들은 움직일 때마다 조금씩 서로를 충돌시키며 이동하기 때문에 보다 부자연스럽게 움직이는 듯한 애니메이션을 보여준다.
import { Location } from "@/konva_mindmap/types/location";
import { Node, NodeData } from "@/konva_mindmap/types/Node";
// 이전 위치를 저장할 Map
const previousLocations = new Map<number, Location>();
export function checkFollowing(data: NodeData, root: Node, updateNode: (id: number, updates: Partial<Node>) => void) {
if (!root.children) return;
// 이전 위치가 없다면 현재 위치를 저장하고 종료
if (!previousLocations.has(root.id)) {
previousLocations.set(root.id, { ...root.location });
return;
}
// 부모 노드의 이동 거리 계산
const previousLocation = previousLocations.get(root.id)!;
const deltaX = root.location.x - previousLocation.x;
const deltaY = root.location.y - previousLocation.y;
// 부모가 실제로 이동했을 때만 자식들을 이동
if (deltaX !== 0 || deltaY !== 0) {
// 모든 자식 노드 업데이트
root.children.forEach((childId) => {
const childNode = data[childId];
if (!childNode) return;
// 부모의 이동량만큼 자식 노드도 이동
const newLocation = {
x: childNode.location.x + deltaX,
y: childNode.location.y + deltaY,
};
updateNode(childId, {
location: newLocation,
});
// 재귀적으로 하위 노드 업데이트
checkFollowing(data, childNode, updateNode);
});
}
// 현재 위치를 이전 위치로 저장
previousLocations.set(root.id, { ...root.location });
}
상대적인 위치를 그대로 유지하면서 움직이게 하는 방식은 마치 부모와 자식 노드를 한 그룹으로 보고 움직이게 하는 방식이다.
이 방식의 경우, 우리의 node 정보에는 부모와 자식 간의 offset정보를 따로 두고 있지 않기 때문에 offset을 기반으로 노드를 배치시키는 것은 보다 많은 연산 비용을 추가적으로 요구할 것이라고 생각했다.
따라서 생각한 방식은 부모가 이동한 양만큼 자식 노드도 이동시키는 방식이다. 이는 부모가 이전에 가지고 있던 위치의 정보만 가지고 이를 비교하여 자식 노드들도 똑같이 움직이게 하면 되니, 가지고 있어야 하는 것은 부모의 이전 위치정보밖에 없다.
화면 기록 2024-11-17 오후 10.44.58.mov
천천히 노드들에 대해 움직였을 때는 상대적인 위치를 잘 기록하면서 가는 듯한 모습을 보여준다.
하지만 이는 문제가 있었던 것이, 그룹을 빠르게 움직였다간 이전 노드의 정보 갱신보다 dragmove이벤트로 하위 노드들의 위치를 조정하는 로직이 더 빠르게 일어나다보니 제대로 갱신이 되지 않는 듯한 모습을 보여주었다.
결국 맨 처음 드래그가 시작했을 때, 상대적인 하위 자식 노드들에 대한 offset을 기록해놔야 하는 것은 필요할 것이라 판단했다.