[WebTranslator 개발기 #16] 툴바 빠른 설정 팝업(optionPopup) 확장과 드롭다운 일원화: 실시간 프리뷰와 UI 정규화
앞서 15편에서는 다크모드 및 복합 배경 웹페이지에서 발생하는 가독성 붕괴 문제를 해결하기 위해 상위 DOM 트리의 유효 배경색을 역추적하고, YIQ 기반 보색 계산을 통해 4종의 인라인 가독성 옵션과 블록 스타일 엔진을 구축한 과정을 다루었습니다.
당시 본문 말미에서 언급했듯이, 가독성 옵션과 배경 투명도 슬라이더가 실제 웹페이지에서 정상 동작하는지 실시간으로 검증하기 위해 브라우저 툴바용 빠른 설정 팝업(optionPopup.html)을 간이 형태로 연동해 테스트를 진행했습니다.
하지만 이 간이 팝업을 정식 제어 허브로 확장하는 과정에서 AI 코딩 특유의 과다 생성(Over-generation) 문제와 UI 파편화 현상이 발생했습니다. 이번 글에서는 난잡해진 팝업 항목을 정규화하고, 대형 옵션 창 이동 없이 1초 만에 스타일을 실시간 조절할 수 있도록 툴바 빠른 설정 팝업과 통일된 드롭다운 UI를 완성한 과정을 살펴보겠습니다.
1. AI 개발의 특이점과 팝업 UI 정규화(Normalization)
15편에서 가독성 엔진을 테스트하기 위해 AI에게 "툴바 아이콘 클릭 시 열리는 빠른 설정 팝업을 만들어 달라"고 지시했을 때, 예상치 못한 UI 팽창 현상을 마주했습니다.
AI는 "설정 팝업"이라는 프롬프트를 받자 기존 대형 옵션 페이지(options.html)에 있던 모든 설정을 팝업 안에 무비판적으로 복제해 넣었습니다:
- 비필수 항목의 무분별한 침범: 화면 내 텍스트만 지연 번역하는 고급 최적화 토글(Lazy Translation), 단축키 재설정 인풋, 복잡한 API 엔드포인트 설정 등 일상적인 웹 서핑에서 거의 건드리지 않는 항목들이 좁은 팝업 창에 가득 찼습니다.
- 본래 목적의 상실: 팝업의 핵심 목적은 글자 크기나 투명도, 번역 엔진처럼 자주 바꾸는 설정을 빠르게 조절하는 것인데, 스크롤을 한참 내려야 원하는 슬라이더를 찾을 수 있는 주객전도 상황이 벌어졌습니다.
이에 따라 AI가 임의로 추가한 비필수 항목들을 과감히 걷어내는 UI 정규화(Normalization) 작업을 단행했습니다. 팝업 창은 오직 사용 빈도가 높은 핵심 컨트롤(번역 엔진, 목표 언어, 표시 방식, 테마 색상, 투명도 슬라이더, 글자 크기, 4종 가독성 메뉴)만 선별하여 320px 컴팩트 다크 뷰에 정갈하게 배치하도록 구조를 재정립했습니다.
2. 대형 옵션 페이지의 UX 병목과 툴바 팝업 중심 조작 전환
초기 WebTranslator는 모든 환경 설정이 별도 탭으로 열리는 대형 옵션 페이지(options.html)에 집중되어 있었습니다. 하지만 웹 서핑 도중 스타일을 미세 조정할 때 다음과 같은 3단계 단절이 발생했습니다:
| 구분 | 기존 대형 옵션 페이지 조작 | 개선된 툴바 빠른 설정 팝업 조작 |
|---|---|---|
| 조작 단계 | 확장 아이콘 우클릭 ➔ 옵션 탭 이동 ➔ 저장 ➔ 본래 탭 복귀 후 새로고침 (3단계) | 툴바 아이콘 클릭 ➔ 슬라이더/메뉴 조작 즉시 1초 완료 (1단계) |
| 화면 전환 | 새 탭으로 전환되어 보고 있던 본문 화면의 시각적 맥락 상실 | 현재 웹페이지 위에 오버레이되어 배경을 보며 즉각 조절 가능 |
| 반영 주기 | 저장 후 페이지를 수동 새로고침해야 변경된 스타일 확인 가능 | 마우스를 움직이는 순간 실시간 렌더링 반영 (Live Preview) |
이러한 분석을 바탕으로 대형 옵션 창 중심의 조작 방식을 전면 개편하고, manifest.json의 기본 액션 팝업(action.default_popup)을 optionPopup.html로 지정하여 일상 조작의 중심을 툴바 팝업으로 격상시켰습니다.
{
"manifest_version": 3,
"name": "__MSG_appName__",
"action": {
"default_title": "Web Translator 설정",
"default_popup": "optionPopup.html"
},
"options_ui": {
"page": "options.html",
"open_in_tab": true
}
}
팝업 상단 헤더에는 번역 캐시를 즉시 비울 수 있는 초기화 버튼(clearCacheBtn)과 세부 API 키 관리를 위한 옵션 바로가기 버튼(openOptionsBtn)을 배치하여 편의성을 보완했습니다.
3. CSS 변수 기반 실시간 라이브 프리뷰와 스토리지 쿼터 분리
팝업에서 글자 크기나 투명도 슬라이더를 움직일 때 웹페이지가 새로고침 없이 즉시 반응하려면 Content Script와의 유기적인 통신 파이프라인이 필요합니다.
하지만 여기서 엔지니어링 측면의 중요한 제약이 존재합니다. 슬라이더를 연속으로 드래그할 때 수십 번 이상 발생하는 input 이벤트마다 chrome.storage.sync.set()을 호출하면, 크롬 익스텐션의 스토리지 쓰기 쿼터(MAX_WRITE_OPERATIONS_PER_MINUTE: 120)를 순식간에 초과하게 됩니다.
-
input 이벤트: 활성 탭에 가벼운 단방향 메시지(notifyPreview)만 발송하여 CSS 변수를 즉시 리렌더링.-
change 이벤트: 마우스 버튼을 놓거나 드래그가 끝난 최종 시점에만 saveSettings를 호출하여 Chrome Storage에 영구 저장.
// src/optionPopup/index.js (실시간 렌더링 메시징과 스토리지 저장 분리)
async function notifyPreview(key, value) {
try {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
if (tabs.length > 0 && tabs[0].id) {
chrome.tabs.sendMessage(tabs[0].id, {
action: "updateStylePreview",
key,
value
}).catch(() => {});
}
} catch (e) {}
}
// 투명도 슬라이더: 드래그 중에는 프리뷰 메시지만 발송, 마우스를 뗄 때 영구 저장
if (elements.transBgAlpha) {
elements.transBgAlpha.addEventListener("input", (e) => {
notifyPreview("transBgAlpha", parseFloat(e.target.value));
});
elements.transBgAlpha.addEventListener("change", (e) => {
updateSetting("transBgAlpha", parseFloat(e.target.value));
});
}
메시지를 수신하는 Content Script(src/content/index.js)는 전달받은 키와 값을 메모리 상의 캐시 설정에 덮어쓴 뒤 ui.updateCustomStyles()를 호출하여 document.documentElement의 스타일 속성을 즉시 갱신합니다:
// src/content/index.js (실시간 스타일 갱신 리스너)
chrome.runtime.onMessage.addListener((message) => {
if (message.action === "updateStylePreview") {
if (state.cachedSettings) {
state.cachedSettings[message.key] = message.value;
import("./ui.js").then((ui) => {
ui.updateCustomStyles(state.cachedSettings);
});
}
}
});
이 설계를 통해 글자 크기(85%~130%) 및 투명도(0~1) 슬라이더를 조작하는 즉시 활성 탭의 번역문이 부드럽게 리사이징되며, 스토리지 API 쿼터 제약도 안정적으로 준수할 수 있었습니다.
팝업에서 가독성 3번 옵션(글자색 환경 적응)을 활성화했을 때, 실시간 프리뷰 시점에는 상위 DOM 트리를 즉각 재연산하지 않아 글자가 기본 검은색(
#000)으로 렌더링될 수 있습니다. 이 경우 페이지를 새로고침하면 초기화 파이프라인에서 전체 DOM 순회 및 YIQ 보색 계산(dom.js)이 정상 수행되어 최적의 보색 텍스트가 표시됩니다.
4. UI 파편화 극복: 일관된 다크 테마 드롭다운 컴포넌트 일원화
팝업과 옵션 페이지를 통합하는 과정에서 나타난 또 다른 문제는 드롭다운 메뉴 스타일의 불일치였습니다. 브라우저 기본 <select> 태그와 커스텀 CSS 레이어가 혼재되어 시각적 이질감을 주었습니다.
이를 해결하기 위해 optionPopup.css와 options.css 전반에 공통 다크 스타일 규격을 정립했습니다:
- 일관된 다크 배경: 반투명 검은색 배경(
rgba(0, 0, 0, 0.2))과 미세한 경계선(rgba(255, 255, 255, 0.1))을 적용하여 전체적인 톤앤매너 통일. - 패딩 및 폰트 표준화: 13px 폰트와 6~10px 내부 여백을 공통 적용하여 팝업과 옵션 창 간의 시각적 통일성 확보.
- 인라인 가독성 멀티 체크박스 메뉴: 4종 가독성 옵션을 좁은 팝업 창에 담기 위해 토글 레이어(
inlineOptionsMenu)를 구성하고, 하단 공간 부족으로 메뉴가 잘리지 않도록bottom: 100%를 지정하여 상단으로 부드럽게 펼쳐지도록 배치.
/* optionPopup.css & options.css - 통일된 드롭다운 규격 */
.styled-select {
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #f8fafc;
padding: 6px 10px;
border-radius: 6px;
font-size: 13px;
font-family: inherit;
cursor: pointer;
outline: none;
transition: all 0.2s ease;
width: 100%;
}
.styled-select option {
background: #1e293b;
color: #f8fafc;
}
마무리하며
이번 작업을 통해 사용자가 웹페이지를 벗어나지 않고도 툴바 미니 팝업에서 번역 모드 변경, 테마 색상, 배경 투명도, 글자 크기, 가독성 옵션까지 1초 만에 제어할 수 있는 컴팩트 UI 체계가 완성되었습니다.
특히 AI 도구를 활용해 복합 인터페이스를 구축할 때는 AI의 과다 생성을 적절히 억제하는 '정규화' 지시가 중요하며, 실시간 렌더링 메시징(input)과 스토리지 영구 저장(change)의 역할을 엄격히 분리하는 것이 안정적인 사용자 경험에 도움이 됨을 실증할 수 있었습니다.
혹시 크롬 확장 프로그램을 개발하시면서 브라우저 팝업의 좁은 뷰포트나 Chrome Storage API 쓰기 쿼터 제약을 다루며 적용하셨던 UI 최적화 팁이 있다면 댓글로 의견을 나눠주시면 감사하겠습니다. 다음 글에서는 텍스트 번역의 안정화를 바탕으로, 웹툰 말풍선이나 게임 패치 일러스트 등 이미지 속 외국어를 추출하고 번역하기 위한 온디바이스 Tesseract OCR 및 Multimodal Vision AI 연구 과정을 다루겠습니다.