교우관계 설문조사 페이지 구현

1. 설문조사 페이지 구조

교우관계 설문조사 페이지는 크게 두 부분으로 구성됩니다:

  1. Canvas를 활용한 인터랙티브 관계도
  2. 추가 설문 문항 폼

1.1 HTML 구조

<div id="name-input-container">
  자신의 이름을 입력하세요:
  <input type="text" id="name-input" />
  <button id="save-name">저장</button>
</div>

<div id="canvas-container">
  <canvas id="relationship-canvas"></canvas>
</div>

<div id="survey-container">
  <div class="survey-item">
    <label>칭찬하고 싶은 친구와 이유:</label>
    <input type="text" id="praise-name" />
    <textarea id="praise-reason"></textarea>
  </div>
  <!-- 추가 설문 항목들 -->
</div>

2. Canvas 관계도 구현

2.1 캔버스 초기화

const canvas = document.getElementById("relationship-canvas");
const ctx = canvas.getContext("2d");

// 캔버스 크기 설정
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;

const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 150; // 학생들이 배치될 원의 반지름

2.2 학생 위치 계산

function calculateStudentPositions() {
  studentPositions = [];
  const angleStep = (2 * Math.PI) / students.length;

  students.forEach((student, index) => {
    const angle = index * angleStep;
    const x = centerX + radius * Math.cos(angle);
    const y = centerY + radius * Math.sin(angle);
    studentPositions.push({ x, y });
  });
}

2.3 관계도 그리기

function drawStudents() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 중심 원(자신) 그리기
  ctx.beginPath();
  ctx.arc(centerX, centerY, 25, 0, 2 * Math.PI);
  ctx.fillStyle = "#4CAF50";
  ctx.fill();

  // 자신의 이름 표시
  ctx.fillStyle = "black";
  ctx.font = "16px Arial";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(userName, centerX, centerY);

  // 다른 학생들 그리기
  students.forEach((student, index) => {
    const pos = studentPositions[index];

    ctx.beginPath();
    ctx.arc(pos.x, pos.y, 10, 0, 2 * Math.PI);
    ctx.fillStyle = "blue";
    ctx.fill();

    ctx.fillStyle = "black";
    ctx.font = "12px Arial";
    ctx.textAlign = "left";
    ctx.fillText(student, pos.x + 15, pos.y);
  });
}

3. 드래그 앤 드롭 구현

3.1 마우스 이벤트 처리

let dragging = false;
let dragIndex = -1;

canvas.addEventListener("mousedown", (evt) => {
  const mousePos = getMousePos(canvas, evt);
  studentPositions.forEach((pos, index) => {
    if (isInsideCircle(mousePos.x, mousePos.y, pos.x, pos.y, 10)) {
      dragging = true;
      dragIndex = index;
    }
  });
});

canvas.addEventListener("mousemove", (evt) => {
  if (dragging && dragIndex !== -1) {
    const mousePos = getMousePos(canvas, evt);
    studentPositions[dragIndex] = mousePos;
    drawStudents();
  }
});

canvas.addEventListener("mouseup", () => {
  dragging = false;
  dragIndex = -1;
});

function getMousePos(canvas, evt) {
  const rect = canvas.getBoundingClientRect();
  return {
    x: evt.clientX - rect.left,
    y: evt.clientY - rect.top,
  };
}

function isInsideCircle(x, y, circleX, circleY, radius) {
  const dx = x - circleX;
  const dy = y - circleY;
  return dx * dx + dy * dy <= radius * radius;
}

3.2 터치 이벤트 처리 (모바일 지원)

canvas.addEventListener("touchstart", handleTouchStart);
canvas.addEventListener("touchmove", handleTouchMove);
canvas.addEventListener("touchend", handleTouchEnd);

function handleTouchStart(evt) {
  evt.preventDefault();
  const touch = evt.touches[0];
  const mousePos = getMousePos(canvas, touch);

  studentPositions.forEach((pos, index) => {
    if (isInsideCircle(mousePos.x, mousePos.y, pos.x, pos.y, 10)) {
      dragging = true;
      dragIndex = index;
    }
  });
}

function handleTouchMove(evt) {
  evt.preventDefault();
  if (dragging && dragIndex !== -1) {
    const touch = evt.touches[0];
    const mousePos = getMousePos(canvas, touch);
    studentPositions[dragIndex] = mousePos;
    drawStudents();
  }
}

function handleTouchEnd(evt) {
  evt.preventDefault();
  dragging = false;
  dragIndex = -1;
}

4. 데이터 저장 구현

4.1 설문 데이터 구조화

document.getElementById("submitSurvey").addEventListener("click", async () => {
  const surveyResults = {
    userName: userName,
    studentPositions: students.map((student, index) => ({
      name: student,
      position: {
        x: studentPositions[index].x,
        y: studentPositions[index].y,
      },
    })),
    praise: {
      name: document.getElementById("praise-name").value,
      reason: document.getElementById("praise-reason").value,
    },
    difficult: {
      name: document.getElementById("difficult-name").value,
      reason: document.getElementById("difficult-reason").value,
    },
    otherclass: {
      friendlyName: document.getElementById("otherclass-friendly-name").value,
      friendlyReason: document.getElementById("good-friend-reason").value,
      badName: document.getElementById("otherclass-bad-name").value,
      badReason: document.getElementById("bad-friend-reason").value,
    },
    concern: document.getElementById("concern-reason").value,
    teacherMessage: document.getElementById("teacher-message").value,
  };

  try {
    await submitSurveyData(surveyResults);
    showCompletionPage();
  } catch (error) {
    console.error("제출 중 오류 발생:", error);
    alert("설문 제출 중 오류가 발생했습니다. 다시 시도해주세요.");
  }
});

5. 사용자 경험 개선

5.1 단계별 진행

const prevButton = document.getElementById("prevButton");
const nextButton = document.getElementById("nextButton");

nextButton.addEventListener("click", () => {
  // 현재 학생 위치 저장
  const currentPositions = studentPositions.map((pos) => ({
    x: pos.x,
    y: pos.y,
  }));

  // 설문조사 폼 표시
  surveyContainer.style.display = "block";
  nextButton.disabled = true;
});

5.2 완료 페이지 표시

function showCompletionPage() {
  const completePage = document.createElement("div");
  completePage.innerHTML = `
    <h1>설문이 제출되었습니다.</h1>
    <p>제출해 주셔서 감사합니다.</p>
  `;

  document.body.innerHTML = "";
  document.body.appendChild(completePage);
}

6. 에러 처리

6.1 입력 검증

function validateSurveyData(data) {
  if (!data.userName) {
    throw new Error("이름을 입력해주세요.");
  }

  if (data.studentPositions.length === 0) {
    throw new Error("학생 위치 데이터가 없습니다.");
  }

  // 필수 입력 항목 검증
  if (!data.praise.name || !data.praise.reason) {
    throw new Error("칭찬하고 싶은 친구와 이유를 모두 입력해주세요.");
  }
}

마치며

Canvas API를 활용한 인터랙티브 관계도 구현은 학생들이 직관적으로 자신의 교우관계를 표현할 수 있게 해줍니다. 또한 터치 이벤트 지원을 통해 모바일 환경에서도 원활한 사용이 가능합니다. 추가 설문 항목들과 함께 종합적인 교우관계 데이터를 수집할 수 있는 기반을 마련했습니다.