HTML #2 SKCT 공부용 메모장+계산기 만들기

HTML #2 SKCT 공부용 메모장+계산기 만들기 #

#2025-07-31


1. 문제 #

SKCT는 응시화면이 아래와같이 돼잇는데

image

연습하기 불편한거같애서 html로 만들어봣다

#

2. SKCT 공부용 메모장+계산기 #

#구조

/skct
├── index.html
└── script.js

#링크

https://github.com/yshghid/skct-tools/tree/main

#활용

image

요렇게 문제옆에 띄워놓고 쓰면됨 ㅎㅎㅎ

#

3. 수정사항 #

#메모장

  1. 메모장 ↔ 그림판 전환 버튼
  • 메모장일때는 ‘🎨 그림판’, 그림판일때는 ‘📝 메모장’이 뜨게 수정

#

#그림판

  1. 선 굵기 조절하는 슬라이더 넣기
  • html: 슬라이더 UI 추가
  • javascript: 초기 선 굵기 1로 설정 / 그림판 상태일때만 보기로 설정
<!-- 선 굵기 조절 슬라이더 -->
<div style="padding: 10px 20px; background: #f0f0f0; display: none;" id="strokeControls">
  <label for="strokeWidth">선 굵기:</label>
  <input type="range" id="strokeWidth" min="0.5" max="10" step="0.5" value="1">
  <span id="strokeValue">1</span>px
</div>
// 선 굵기 조절 슬라이더 요소
const strokeControls = document.getElementById('strokeControls');
const strokeWidth = document.getElementById('strokeWidth');
const strokeValue = document.getElementById('strokeValue');

// 초기 선 굵기 설정
ctx.lineWidth = parseFloat(strokeWidth.value);
ctx.lineCap = 'round';  // 더 부드럽게

// 슬라이더 변경 시 선 굵기 업데이트
strokeWidth.addEventListener('input', () => {
  ctx.lineWidth = parseFloat(strokeWidth.value);
  strokeValue.textContent = strokeWidth.value;
});

// 그림판 상태일때만 보기
function toggleMode() {
  const isTextVisible = textArea.style.display !== 'none';
  if (isTextVisible) {
    textArea.style.display = 'none';
    canvas.style.display = 'block';
    strokeControls.style.display = 'block'; // 슬라이더 표시
    resizeCanvas();
    modeToggleBtn.textContent = '📝 메모장';
  } else {
    canvas.style.display = 'none';
    strokeControls.style.display = 'none'; // 슬라이더 숨김
    textArea.style.display = 'block';
    modeToggleBtn.textContent = '🎨 그림판';
  }
}
  1. 선 픽셀이 뭔가 깨져보임
  • 디바이스 해상도(DPR: devicePixelRatio) 반영하여 캔버스 확장
    • 원래 코드: 디스플레이 해상도를 고려하지 않고 canvas.width / canvas.height 를 설정
    • 수정된 코드: dpr을 고려해서 width, height를 수정
// 원래 코드
function resizeCanvas() {
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
// 수정된 코드
function resizeCanvas() {
  const dpr = window.devicePixelRatio || 1;
  canvas.width = canvas.clientWidth * dpr;
  canvas.height = canvas.clientHeight * dpr;
  canvas.style.width = canvas.clientWidth + 'px';
  canvas.style.height = canvas.clientHeight + 'px';
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // scale canvas context
}
  1. 선색상, 지우개, 실행취소 기능 넣기
  • “🧽 지우개” “✏️ 펜” 이렇게 2개 버튼 넣지말고 버튼 하나만 남겨서 펜 상태이면 “🧽 지우개”, 지우개 상태면 “✏️ 펜"으로 변경
<!-- 도구 패널 -->

<!-- 원래코드 -->
<div id="strokeControls">
  <label>선 굵기:
    <input type="range" id="strokeWidth" min="0.5" max="10" step="0.5" value="1">
    <span id="strokeValue">1</span>px
  </label>

  <label>선 색상:
    <input type="color" id="strokeColor" value="#000000">
  </label>

  <button onclick="setEraser(true)">🧽 지우개</button>
  <button onclick="setEraser(false)">✏️ 펜</button>
  <button onclick="undo()">↩️ 실행취소</button>
</div>

<!-- 수정된 코드 -->
<div id="strokeControls">
  <label>선 굵기:
    <input type="range" id="strokeWidth" min="0.5" max="10" step="0.5" value="1">
    <span id="strokeValue">1</span>px
  </label>

  <label>선 색상:
    <input type="color" id="strokeColor" value="#000000">
  </label>

  <button id="toggleToolBtn" onclick="toggleTool()">🧽 지우개</button>
  <button onclick="undo()">↩️ 실행취소</button>
</div>
// 버튼 1개만 남김
function toggleTool() {
  erasing = !erasing;
  ctx.strokeStyle = erasing ? '#ffffff' : strokeColor.value;
  toggleToolBtn.textContent = erasing ? '✏️ 펜' : '🧽 지우개';
}

// 색상 선택 변경 시 현재 상태 반영
strokeColor.addEventListener('input', () => {
  if (!erasing) {
    ctx.strokeStyle = strokeColor.value;
  }
});
  1. 지우개/펜 기본 두께 설정 / 지우개 색깔 수정
  • 지우개 상태일때는 기본값이 9.5 / 펜 상태일때는 기본값이 3.0으로
  • 지우개랑 배경색 통일시키기
  • ‘🧽 지우개’ 버튼을 클릭했을때 선 굵기가 9.5로 바뀌고나서 다시 “✏️ 펜” 버튼을 누르면 슬라이더가 원래 두께인 3.0로 돌아오지않고 9.5로 남는데 3.0로 따라오게.
let previousPenWidth = 3; // 기본 펜 굵기
ctx.lineWidth = 3;

function toggleTool() {
  erasing = !erasing;

  if (erasing) {
    previousPenWidth = parseFloat(strokeWidth.value); // 현재 펜 굵기 저장
    ctx.strokeStyle = '#f9f9f9';  // 배경색과 일치
    ctx.lineWidth = 9.5;
    strokeWidth.value = 9.5;
    strokeValue.textContent = '9.5';
    toggleToolBtn.textContent = '✏️ 펜';
  } else {
    ctx.strokeStyle = strokeColor.value;
    ctx.lineWidth = previousPenWidth; // 복원
    strokeWidth.value = previousPenWidth;
    strokeValue.textContent = previousPenWidth;
    toggleToolBtn.textContent = '🧽 지우개';
  }
}
<input type="range" id="strokeWidth" min="0.5" max="10" step="0.5" value="3">
<span id="strokeValue">3</span>px

#

#계산기

  1. *대신 × 쓰고싶은데 ×가 들어가니까 연산 오류가 남
  • 입력창에는 ×를 보여주고
    • 내부 계산 시에는 ×를 *로 변환하여 처리하는데
      • display.value에는 ×가 포함되게 append()수정

원래 코드 / 변형코드

// 원래
<button onclick="append('*')">*</button>

//변형
<button onclick="append('×')">×</button>

수정된 변형코드

function append(value) {
  // 보이는 기호는 ×로, 실제 계산은 *
  if (value === '*') {
    display.value += '×';
  } else {
    display.value += value;
  }
}

이때 버튼은 × 유지

<button onclick="append('*')">×</button>

#

2차 수정

  1. 그림판에서 기본 선 굵기가 3px라고 표시만 돼잇고 실제로는 1px 처럼보임
  • canvas.getBoundingClientRect() 기반으로 크기를 조정한 후 ctx.setTransform(…)을 호출하면 브라우저의 devicePixelRatio에 따라 얇게 보일 수 있다.
  • resizeCanvas() 함수의 마지막에 ctx.lineWidth 설정을 추가, ctx.lineWidth = 3; 를 초기 설정 블록에서 한 번 더 강제 설정
// 선 굵기 재설정
function resizeCanvas() {
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();
  canvas.width = rect.width * dpr;
  canvas.height = rect.height * dpr;
  canvas.style.width = rect.width + 'px';
  canvas.style.height = rect.height + 'px';
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  redrawHistory();

  ctx.lineWidth = parseFloat(strokeWidth.value);
}

// 초기 설정 블록에서 3px로 한번더설정
ctx.lineCap = 'round';
ctx.strokeStyle = strokeColor.value;
ctx.lineWidth = parseFloat(strokeWidth.value);  // 이 라인 추가
  1. 계산 결과 후 숫자를 누르면 자동 초기화 시키기
  • 계산기에서 3+5하고 =하면 8 > 9를 누르면 8옆에 그냥 9가 적혀서 89가 됨
  • 만약에 = 해서 연산 결과가 나온 상태면, 뒤에 숫자를 누르면 초기화(AC) 버튼을 안눌렀더라도 초기화(AC) 버튼을누르고 숫자를 누른걸로 인식하게 수정: justCalculated 플래그 도입
let justCalculated = false;

function append(value) {
  // 숫자를 누른 경우: 결과가 방금 계산된 상태라면 초기화
  const isNumber = /^[0-9.]$/.test(value);
  const isOperator = /^[+\-*/×÷]$/.test(value);

  if (justCalculated && isNumber) {
    display.value = '';
  }

  if (value === '*') {
    display.value += '×';
  } else {
    display.value += value;
  }

  justCalculated = false;
}

function calculate() {
  try {
    const expression = display.value.replace(/×/g, '*').replace(/÷/g, '/');
    display.value = eval(expression);
    justCalculated = true; // 결과가 방금 계산됨
  } catch (e) {
    display.value = 'Error';
    justCalculated = true;
  }
}

function clearDisplay() {
  display.value = '';
  justCalculated = false;
}
  1. 계산기에서 키보드 입력(숫자 및 연산자 키)도 인식
  • 계산기를 클릭뿐이아니라 키보드입력도 받게.

  • 유의사항

    • 메모창이랑 섞이면 안됨.
      • 메모창을 누르면 키보드 입력이 메모창 내용만 수정해야하고
      • 계산기 입력창을 누르면 키보드 입력이 계산기 입력 내용만 수정하게 분리.
  • 구현

    • display 입력창에 focus되었을 때만 키보드 입력을 계산기에 전달
    • 메모장(textarea)이 focus되면 계산기로 입력이 가지 않도록 제어
    • keydown 이벤트를 window에 추가하고, 계산기 입력창이 focus일 때만 append() 호출
// 키보드 입력 처리
window.addEventListener('keydown', (e) => {
  const isNumber = /^[0-9]$/.test(e.key);
  const isOperator = /^[+\-*/]$/.test(e.key);
  const isEnter = e.key === 'Enter';

  const isCalculatorFocused = document.activeElement === display;
  const isTextAreaFocused = document.activeElement === textArea;

  if (isCalculatorFocused && (isNumber || isOperator)) {
    e.preventDefault();
    append(e.key);
  } else if (isCalculatorFocused && isEnter) {
    e.preventDefault();
    calculate();
  }
});

+) readonly 상태에서도 focus되도록 하려면 tabindex=“0"을 넣어줘야 한다.

<input type="text" id="display" readonly tabindex="0" />
  1. 계산기 입력창을 눌렀을 때 display.focus()를 명시적으로 호출해서 누르면 파란색으로 표시되게하기
display.addEventListener('click', () => {
  display.focus();
});
  1. 계산기에서 백스페이스, enter 누르면 = 처럼 쓰기, esc 누르면 AC 누른거랑 동일하게 쓰기
// 계산기 입력창 클릭 시 focus
display.addEventListener('click', () => {
  display.focus();
});

// 키보드 입력 처리
window.addEventListener('keydown', (e) => {
  const isNumber = /^[0-9]$/.test(e.key);
  const isOperator = /^[+\-*/]$/.test(e.key);
  const isEnter = e.key === 'Enter';
  const isBackspace = e.key === 'Backspace';
  const isEscape = e.key === 'Escape';

  const isCalculatorFocused = document.activeElement === display;

  if (!isCalculatorFocused) return; // 계산기 입력창에 focus된 경우만 작동

  // 숫자 및 연산자 입력
  if (isNumber || isOperator) {
    e.preventDefault();
    append(e.key);
  }

  // Enter 키: 계산 수행
  else if (isEnter) {
    e.preventDefault();
    calculate();
  }

  // Backspace 키: 마지막 문자 삭제
  else if (isBackspace) {
    e.preventDefault();
    if (display.value.length > 0) {
      display.value = display.value.slice(0, -1);
    }
  }

  // ESC 키: 전체 초기화
  else if (isEscape) {
    e.preventDefault();
    clearDisplay();
  }
});

#

3차 수정

  1. “.“을 2번 이상 입력하려고 하면 무시(1번만 입력되게)
  2. 0~9와 +-*/의 경우 키보드로 입력 가능하게 되어있는데 “.“도 키보드로 입력 가능하게
  3. 그림판에서 실행취소, 지우개만 있는데 초기화 버튼도 추가
  4. .입력 후 /+-*같은 연산자가 입력 안되게
if (value === '.' && display.value.includes('.') && !/[+\-*/×÷]/.test(lastChar)) {
  return; // 중복 소수점 방지
}
const isDot = e.key === '.';

if (isNumber || isOperator || isDot) {
  e.preventDefault();
  append(e.key);
}
<button onclick="clearCanvas()">🗑️ 초기화</button>
function clearCanvas() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  history = [];
}
if (/[.]/.test(lastChar) && isOperator) return;

#

  1. 진짜 마지막..
  • 계산기에서 숫자나 연산기호를 누른상태에서 키보드 입력이 들어가면 focus가 숫자나 연산기호버튼으로 들어가있어서 입력창으로 키보드입력이 안들어감
    • 계산기에서의 키보드입력은 계산기입력창으로만 들어가면 되니까 다음과같이 수정
      • 키보드 입력은 무조건 계산기 입력창(#display)으로 들어가도록 한다.
      • 단, 메모장(#textArea)에 포커스가 있을 때는 예외로 한다.
window.addEventListener('keydown', (e) => {
  const isNumber = /^[0-9]$/.test(e.key);
  const isOperator = /^[+\-*/]$/.test(e.key);
  const isDot = e.key === '.';
  const isEnter = e.key === 'Enter';
  const isEquals = e.key === '=';
  const isBackspace = e.key === 'Backspace';
  const isEscape = e.key === 'Escape';

  const isInMemo = document.activeElement === textArea;

  if (isInMemo) return; // 메모장에 포커스가 있을 경우 무시

  // 항상 계산기에 포커스 유지
  display.focus();

  if (isNumber || isOperator || isDot) {
    e.preventDefault();
    append(e.key);
  } else if (isEnter || isEquals) {
    e.preventDefault();
    calculate();
  } else if (isBackspace) {
    e.preventDefault();
    display.value = display.value.slice(0, -1);
  } else if (isEscape) {
    e.preventDefault();
    clearDisplay();
  }
});
  • const isInMemo = document.activeElement === textArea; if (isInMemo) return;으로 메모장에 포커스 있으면 계산기 입력 무시
  • display.focus()를 강제로 호출해서 키보드 입력이 계산기 입력창으로 자동 전달되게.

#

🎉 셋팅끝!!

이제핑계를다잃엇다..진짜공부해야함

#