input type=number의 배신 — 금융계산기 입력창을 다시 만든 이유
플랜씨 금융계산기 입력창을 number 타입에서 inputmode+pattern 조합으로 바꾼 이유와 적용 과정을 정리했습니다.
플랜씨(plan-c)는 로그인 없이 쓰는 무료 금융계산기예요. 대출이자, 연봉 실수령, 복리, 저축 네 가지를 다루는데 화면에 있는 건 사실상 숫자 입력창 몇 개가 전부예요. 그래서 입력창이 조금만 이상해도 계산기 전체가 이상해 보여요.
처음엔 당연히 <input type="number">를 썼어요. 숫자 입력이니까 숫�자 타입. 이보다 당연한 선택이 있나 싶었죠.
직접 써 보니 드러난 문제들
모바일에서 대출 원금 5천만 원을 넣어 보다가 이상한 걸 발견했어요. 입력창 위에서 스크롤을 하면 값이 슬금슬금 바뀌어요. type="number"는 마우스 휠과 위아래 화살표로 값이 증감하는 게 기본 동작이라서요. 목록을 훑다가 원금이 49,999,998원이 돼 있으면 계산 결과가 왜 다른지 알 길이 없어요.
두 번째는 입력 자체가 조용히 사라지는 거예요. 크롬에서 type="number" 칸에 1,000처럼 쉼표를 넣거나 한글이 섞이면 브라우저가 그 값을 유효하지 않다고 판단해요. 이때 자바스크립트로 읽는 값은 빈 문자열이에요. 쉼표를 찍은 사용자에겐 화면에 글자가 보이는데 계산기는 0으로 읽는 상황이 생겨요. 이게 제일 무서웠어요. 에러도 안 나고 그냥 틀린 답이 나오거든요.
세 번째는 천 단위 콤마예요. 금융계산기에서 50,000,000을 보여주는 건 기본인데, type="number" 안에는 콤마를 넣을 수 없어요. 숫자만 허용하는 타입이라서요. 결국 콤마를 포기하거나 타입을 바꾸거나 둘 중 하나였어요.
- 휠 스크롤로 값 변경
- 잘못된 입력이 빈 문자열로
- 천 단위 콤마 불가
inputmode + pattern으로 바꾼 구조
바꾼 조합은 이래요.
<input
type="text"
inputMode="numeric"
pattern="[0-9,]*"
value={display}
onChange={(e) => {
const raw = e.target.value.replace(/[^0-9]/g, "");
setAmount(raw ? Number(raw) : 0);
setDisplay(raw ? Number(raw).toLocaleString("ko-KR") : "");
}}
/>type="text"로 두면 브라우저가 값을 검열하지 않아요. 입력된 건 전부 문자열로 들어오고, 무엇을 숫자로 볼지는 내가 정해요. inputMode="numeric"은 모바일 키보드를 숫자 패드로 띄우는 역할만 해요. 값 검증은 안 해요. 그래서 pattern으로 iOS 쪽 키보드 힌트를 한 번 더 주고, 실제 걸러내는 일은 replace(/[^0-9]/g, "") 한 줄이 해요.
이 구조에서 좋았던 점은 화면에 보이는 값(display)과 계산에 쓰는 값(amount)이 분리된다는 거예요. 사용자는 50,000,000을 보고, 계산 함수는 50000000을 받아요. 붙여넣기로 50,000,000원이 들어와도 숫자만 남으니 그대로 동작해요. 원래 type="number"에서는 이 붙여넣기가 통째로 무시됐어요.
적용해 보고 알게 된 것
네 개 계산기 화면을 순서대로 고쳤는데, 실제 수정은 입력 컴포넌트 1개였어요. 플랜씨는 입력창을 공용 컴포넌트 하나로 쓰고 있어서 거기만 바꾸니 대출·연봉·복리·저축 네 화면이 한 번에 따라왔어요. 작업 시간은 30분이 안 걸렸고, 대부분은 코드가 아니라 브라우저를 번갈아 열어 확인하는 데 썼어요.
테스트해 보면 확인할 게 몇 가지 있어요. 붙여넣기, 백스페이스로 중간 숫자 지우기, 맨 앞에 0 여러 개 넣기. 특히 커서 위치가 문제예요. 콤마를 다시 붙이면서 문자열 길이가 바뀌면 커서가 맨 뒤로 튀어요. 금액처럼 뒤에서부터 입력하는 값은 큰 문제가 아니지만, 중간을 고치려는 사람에겐 불편해요. 저는 일단 그대로 뒀어요.
// ponytail: 커서는 항상 맨 뒤. 중간 편집 요구 나오면 selectionStart 보정 추가언제 type=number를 쓰면 되나
전부 바꿔야 한다는 얘기는 아니에요. 개수, 나이, 층수처럼 자릿수가 짧고 증감 버튼이 오히려 편한 값에는 type="number"가 여전히 맞아요. 스텝 버튼이 기능이 되니까요.
나누는 기준은 단순해요. 세는 값이면 number, 표기하는 값이면 text + inputmode. 금액, 전화번호, 카드번호, 주민등록번호처럼 자릿수가 길고 구분 기호가 들어가는 값은 브라우저의 숫자 검증이 도움보다 방해가 돼요.
플랜씨에서 이걸 고치고 나서 계산 결과를 의심할 일이 없어졌어요. 계산 로직은 처음부터 맞았는데 입력이 조용히 0으로 들어가는 바람에 로직을 세 번쯤 다시 읽었거든요. 입력창이 정직해지니 나머지가 편해졌어요. 로그인 없이 바로 써 볼 수 있으니 금액 칸에 쉼표를 넣어 보셔도 좋아요. 이제는 그대로 읽어요. 🙂
관련 글
댓글
아직 댓글이 없어요. 첫 댓글을 남겨주세요.