MUI 기반 하이브리드 앱이나 웹 환경에서 네이티브 대기 화면(Plus Runtime)과 표준 DOM 오버레이를 자동 분기 처리할 수 있는 로딩 상태 표시 컴포넌트입니다. 기존 프레임워크 내장 메서드의 환경별 제약 사항을 보완하여, 단일 API로 일관된 UX를 제공하는 것을 목표로 합니다.
JavaScript 구현 로직
DOM 요소의 생명주기를 추적하고, 중복 호출 시 상태를 갱신하며, 환경 감지를 통해 적절한 UI 레이어를 렌더링합니다.
(function(globalContext, muiInstance) {
const OVERLAY_KEY = 'hybrid-loader-root';
const MASK_KEY = 'loader-backdrop';
const ACTIVE_STATE = 'state-active';
function initializeLayerStructure() {
let backdropNode = globalContext.document.getElementById(MASK_KEY);
if (!backdropNode) {
backdropNode = globalContext.document.createElement('div');
backdropNode.id = MASK_KEY;
backdropNode.setAttribute('aria-hidden', 'true');
globalContext.document.body.appendChild(backdropNode);
}
let spinnerContainer = globalContext.document.getElementById(OVERLAY_KEY);
if (!spinnerContainer) {
spinnerContainer = globalContext.document.createElement('div');
spinnerContainer.id = OVERLAY_KEY;
spinnerContainer.setAttribute('role', 'alert');
spinnerContainer.innerHTML = `
<i class="mui-spinner mui-spinner-white"></i>
시스템 처리 중입니다.
`;
globalContext.document.body.appendChild(spinnerContainer);
}
return { backdrop: backdropNode, container: spinnerContainer };
}
muiInstance.showLoading = function(displayMessage, forceEnv) {
const promptText = displayMessage || '데이터를 불러오는 중입니다.';
if (muiInstance.os.plus && forceEnv !== 'web') {
muiInstance.plusReady(() => {
plus.nativeUI.showWaiting(promptText);
});
return;
}
const { backdrop, container } = initializeLayerStructure();
const statusSpan = container.querySelector('.loader-status-text');
if (statusSpan) statusSpan.textContent = promptText;
backdrop.classList.add('mask-active');
container.classList.add(ACTIVE_STATE);
};
muiInstance.hideLoading = function(postHideAction) {
if (muiInstance.os.plus) {
muiInstance.plusReady(() => {
plus.nativeUI.closeWaiting();
});
}
const containerRef = globalContext.document.getElementById(OVERLAY_KEY);
const maskRef = globalContext.document.getElementById(MASK_KEY);
if (containerRef) {
containerRef.classList.remove(ACTIVE_STATE);
}
if (maskRef) {
maskRef.classList.remove('mask-active');
}
if (postHideAction && typeof postHideAction === 'function') {
setTimeout(postHideAction, 250);
}
};
})(window, mui);
CSS 시각화 및 전환 효과
Flexbox 기반 중앙 정렬과 CSS 변수를 활용하여 테마 연동이 용이하도록 개선했습니다.
:root {
--overlay-surface: rgba(15, 15, 15, 0.82);
--text-primary: #f8f9fa;
--anim-duration: 0.25s;
--scale-enter: 0.85;
--scale-exit: 1;
}
#loader-backdrop {
position: fixed;
inset: 0;
background: transparent;
z-index: 9000;
}
.mask-active {
background: transparent;
}
#hybrid-loader-root {
position: fixed;
top: 50%;
left: 50%;
width: 150px;
min-height: 150px;
padding: 20px 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: var(--overlay-surface);
color: var(--text-primary);
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 9500;
opacity: 0;
visibility: hidden;
transform: translate(-50%, -50%) scale(var(--scale-enter));
transition: opacity var(--anim-duration) ease, transform var(--anim-duration) ease, visibility var(--anim-duration);
pointer-events: none;
}
.state-active {
opacity: 1;
visibility: visible;
transform: translate(-50%, -50%) scale(var(--scale-exit));
}
#hybrid-loader-root .mui-spinner {
width: 30px;
height: 30px;
}
#hybrid-loader-root .loader-status-text {
font-size: 13px;
letter-spacing: -0.02em;
line-height: 1.6;
text-align: center;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
적용 사항
구현 코드는 애플리케이션 라우터 진입점 또는 전역 유틸리티 파일에 통합하면 됩니다. 첫 번째 인자에 문자열을 전달하면 해당 텍스트로 상태 메시지가 동적 교체되며, 두 번째 인자를 지정하지 않거나 `'web'` 값을 전달하면 표준 오버레이 모드가 실행됩니다. Plus Runtime 환경에서 두 번째 인자를 명시적으로 생략한 경우 네이티브 대기 화면으로 우회 적용됩니다. 연속 호출 시 초기화되지 않고 내부 DOM 노드의 텍스트 노드만 갱신되므로, AJAX 페칭이나 페이지 전환 간격에도 안정적인 피드백을 유지합니다.