🐼 눈 아이콘 클릭 시 비밀번호 보기 및 숨기 기능 구현 삽질기
그리고 공통 유틸로 추출하면서 얻은 깨달음

비밀번호 보기 토글 기능은 누구나 한 번쯤 만들어보는 단순한 UI 요소라고 생각했었다.
하지만 실제로 구현하면서 사소하다고 생각했던 부분에서 얼마나 많은 실수와 누락이 발생할 수 있는지를 경험했고,
그 과정을 통해 DOM 조작, 유틸 함수 추출, HTML 구조 설계, 디버깅 감각 등
여러 기본 요소들을 되돌아보게 되었다.
이번 작업은 기능적으로는 짧은 구현이었지만, 그 안에서 확인하지 않고 넘어갔던 것들(예: import 누락, 잘못된 셀렉터 사용, UI 상태 연동 누락 등)이 실제 사용자 경험에 어떤 영향을 줄 수 있는지 체감할 수 있었다.
또한, 단순한 토글 로직 하나도 공통 유틸로 분리하면서 재사용성과 유지보수성을 고민하게 되었고,
이후 Font Awesome 등을 활용한 개선 방향에 대한 고민으로까지 이어졌다.
작은 기능 하나를 구현하면서 ‘작은 기능’이라는 건 없다는 걸 다시 한 번 느꼈고,
UI 요소 하나를 만드는 데에도 개발자의 기본기와 설계에 대한 태도가 고스란히 드러난다는 점을 되새기게 되었다.
이 글은 그 삽질과 회고의 기록이다.
그리고 다음에 같은 실수를 반복하지 않기 위한 나 스스로에 대한 리마인더이기도 하다.
🧱 1. 기능 요구사항
- 👁️ 눈 아이콘 클릭 시 비밀번호 문자열이 보여야 함
- 👁️🗨️ 다시 클릭하면 가려져야 함
- 🙈 가려질 때는 눈에 사선 아이콘 (숨김)
- 👁️ 보일 때는 사선 없는 눈 아이콘 (표시)
🔨 2. 초기 구현
<img class="eye" src="/images/btn_visibility_off.png" />
JS에선 아래와 같이 함수로 작성:
공통 유틸로 분리
export function togglePasswordVisiblity(passwordInput, eyeIcon) {
if (!passwordInput || !eyeIcon) return;
const isPasswordVisible = passwordInput.type === "text";
passwordInput.type = isPasswordVisible ? "password" : "text";
eyeIcon.src = isPasswordVisible
? "/images/btn_visibility_off.png"
: "/images/btn_visibility_on.png";
}
👉 클릭 시 type 바꾸고 src도 바꿔주는 단순한 로직.
💥 3. 안 됐던 이유들
- ❌ import를 안 해놓고 함수 호출도 안 했음
- ❌ .eye가 여러 개 있는데 querySelector(".eye")로 첫 번째만 잡음
- ❌ confirmPassword input 관련 처리 자체가 빠져 있었음
🔍 4. 디버깅하면서 깨달은 점
- 📛 콘솔 로그 확인만 했었어도 바로 알 수 있었던 버그
- 💡 document.querySelector()는 하나만 잡는다
- 🪛 각 아이콘에 id 부여해서 명확하게 잡아야 했다
🛠️ 5. 수정한 방식
HTML 수정
<img class="eye" id="eye-password" src="/images/btn_visibility_off.png" />
<img class="eye" id="eye-confirm" src="/images/btn_visibility_off.png" />
JS 수정
const eyePassword = document.getElementById("eye-password");
const eyeConfirm = document.getElementById("eye-confirm");
eyePassword.addEventListener("click", () => {
togglePasswordVisiblity(passwordInput, eyePassword);
});
eyeConfirm.addEventListener("click", () => {
togglePasswordVisiblity(confirmPasswordInput, eyeConfirm);
});
이제 각 input에 대해 토글이 정상 작동함 👏
🎨 6. 개선 방향: Font Awesome 도입
처음에는 이미지로 눈 아이콘을 바꾸는 방식으로 구현했지만,
나중에 찾아보니 Font Awesome 아이콘을 쓰면
classList.toggle()만으로 눈/숨김 전환이 가능하고,
스타일 관리도 더 편하다는 걸 알게 됐다.
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
<input type="password" />
<i class="fa fa-eye" id="eye-password"></i>
👉 다만, 이번엔 이미지로 구현한 부분이 이미 완료된 상태라
"작업한 기반을 그대로 유지하면서 유틸 함수만 개선"하는 방향으로 마무리했다.
📌 다음 프로젝트나 컴포넌트 작업 땐 Font Awesome 방식도 고려할 예정.
💬 7. 회고
- ✅ 기능은 구현됐는데 import를 안 하면 그냥 없는 코드다
- ✅ querySelector는 하나만, querySelectorAll은 모두
- ✅ 같은 클래스 여러 개일 땐 id를 분리해서 명시적으로 바인딩하자
- ✅ 공통 로직은 유틸로 뽑고, UI 엘리먼트만 바꿔주는 구조가 깔끔하다
✅ 비밀번호 보기 기능 구현 완료

🔚 마무리
그냥 비밀번호 토글 기능 하나였지만,
실제로 얻은 건 디버깅 감각, import 실수 캐치, DOM 요소 구조 설계 등 실질적인 경험이 컸다.
다음에 똑같은 삽질 안 하기 위해 이렇게 정리해둔다. ✍️
코드를 다시 보면 리팩토링이 필요한 부분도 분명히 있다.
특히 상태 관리 방식이나 UI 변경 방식은 좀 더 추상화해서 재사용 가능하도록 개선할 여지가 있다.
어떻게 리팩토링할지에 대해서는 아직 고민 중이고,
다음 프로젝트에서 반복적으로 쓰이게 될 경우 본격적으로 구조를 잡아볼 예정이다.
지금은 기능 구현과 구조 설계 사이의 균형을 어느 정도 맞췄다고 생각하지만,
완성형은 아니고 앞으로 꾸준히 개선해나가야 할 부분이라고 생각한다.
'Frontend > 스프린트 회고' 카테고리의 다른 글
| [JavaScript] 로그인 페이지 - "유효성 검사는 했는데… 왜 버튼이 눌려?" 👉 window.location.href, 유효성 검사, input 이벤트까지 (5) | 2025.07.31 |
|---|