HTML #2 SKCT 공부용 메모장+계산기 만들기 #
#2025-07-31
1. 문제 #
SKCT는 응시화면이 아래와같이 돼잇는데
연습하기 불편한거같애서 html로 만들어봣다
#
2. SKCT 공부용 메모장+계산기 #
#구조
/skct
├── index.html
└── script.js
#링크
https://github.com/yshghid/skct-tools/tree/main
#활용
요렇게 문제옆에 띄워놓고 쓰면됨 ㅎㅎㅎ
#
3. 수정사항 #
#메모장
- 메모장 ↔ 그림판 전환 버튼
- 메모장일때는 ‘🎨 그림판’, 그림판일때는 ‘📝 메모장’이 뜨게 수정
#
#그림판
- 선 굵기 조절하는 슬라이더 넣기
- 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 = '🎨 그림판';
}
}
- 선 픽셀이 뭔가 깨져보임
- 디바이스 해상도(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
}
- 선색상, 지우개, 실행취소 기능 넣기
- “🧽 지우개” “✏️ 펜” 이렇게 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;
}
});
- 지우개/펜 기본 두께 설정 / 지우개 색깔 수정
- 지우개 상태일때는 기본값이 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
#
#계산기
- *대신 × 쓰고싶은데 ×가 들어가니까 연산 오류가 남
- 입력창에는 ×를 보여주고
- 내부 계산 시에는 ×를 *로 변환하여 처리하는데
- 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차 수정
- 그림판에서 기본 선 굵기가 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); // 이 라인 추가
- 계산 결과 후 숫자를 누르면 자동 초기화 시키기
- 계산기에서 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;
}
- 계산기에서 키보드 입력(숫자 및 연산자 키)도 인식
계산기를 클릭뿐이아니라 키보드입력도 받게.
유의사항
- 메모창이랑 섞이면 안됨.
- 메모창을 누르면 키보드 입력이 메모창 내용만 수정해야하고
- 계산기 입력창을 누르면 키보드 입력이 계산기 입력 내용만 수정하게 분리.
- 메모창이랑 섞이면 안됨.
구현
- 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" />
- 계산기 입력창을 눌렀을 때 display.focus()를 명시적으로 호출해서 누르면 파란색으로 표시되게하기
display.addEventListener('click', () => {
display.focus();
});
- 계산기에서 백스페이스, 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차 수정
- “.“을 2번 이상 입력하려고 하면 무시(1번만 입력되게)
- 0~9와 +-*/의 경우 키보드로 입력 가능하게 되어있는데 “.“도 키보드로 입력 가능하게
- 그림판에서 실행취소, 지우개만 있는데 초기화 버튼도 추가
- .입력 후 /+-*같은 연산자가 입력 안되게
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;
#
진짜 마지막..
- 계산기에서 숫자나 연산기호를 누른상태에서 키보드 입력이 들어가면 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()
를 강제로 호출해서 키보드 입력이 계산기 입력창으로 자동 전달되게.
#
🎉 셋팅끝!!
이제핑계를다잃엇다..진짜공부해야함