문의 버튼을 항상 눈에 띄게 두려는 목적이 화면의 다른 일을 가리면 안 된다. 팝업의 닫기 버튼이나 뷰어의 조작부를 사용하는 동안에는 고정 UI가 잠시 비켜나는 편이 필요할 수 있다. 나는 공통 문의바의 현재 코드를 읽고 이런 상태를 전달하는 방법을 정리했다.

원형 보존 계약에는 VR 조작부를 피하는 조건이 있고 공통 문의바에는 선택자 기반 일시 정지와 수동 API가 있다. 이번에는 여러 상태가 겹쳤을 때 복귀 시점을 조정하는 새 모듈과 합성 실행 예시를 더했다. 문의 연결의 존재보다 지금 사용자가 하는 일을 이어갈 수 있는지 함께 보는 기준이다.

읽기 중 숨김과 조작 중 정지를 나눈다

공통 문의바에는 스크롤 중 숨기고 잠시 뒤 복귀하는 동작과 페이지 상태에 따라 일시 정지하는 동작이 따로 있다. 현재 코드에는 지정한 선택자가 보이면 멈추는 조건과 수동 pause·resume API가 있다. 움직이는 동안만 비켜나는 규칙과 특정 작업이 끝날 때까지 비켜나는 규칙을 구별하는 구조다.

팝업이나 뷰어가 열린 상태를 단순한 스크롤 숨김으로 처리하면 복귀 타이머가 조작 중인 화면 위로 바를 되돌릴 수 있다. 나는 지금 조작을 이어가는 상태가 있는지 별도로 전달하도록 한다. 그 상태가 끝난 뒤에야 평소 읽기와 문의 흐름으로 돌아갈 수 있어야 한다.

팝업 두 개가 겹치면 마지막 상태까지 기다린다

팝업과 뷰어가 함께 열렸을 때 팝업 하나를 닫았다고 문의바를 바로 복귀시키면 뷰어 조작이 가려질 수 있다. 같은 종류의 팝업이 두 번 열리는 경우에도 상태 하나를 덮어쓰면 남은 창을 잊을 수 있다. 이번 새 구현은 정지 이유마다 독립적인 해제 함수를 발급한다.

해제 함수는 두 번 호출해도 한 번만 처리하고, 모든 이유가 끝나야 복귀한다. 반복해서 들어오는 상태 이벤트는 별도의 조건 인터페이스로 한 번만 등록한다. 이것은 기존 공통 문의바에 자동으로 이미 적용돼 있다는 뜻이 아니라 그 API와 연결할 수 있는 새 조정 모듈이다.

한 문의바의 수동 정지는 한 곳에서 조정한다

기존 API의 resume은 수동 정지 상태를 해제한다. 다른 코드가 별도로 pause를 호출하는데 새 모듈이 그 이유를 모르고 resume하면 상태를 지울 수 있다. 적용할 때 같은 문의바의 수동 정지 요청을 하나의 조정기로 모아야 한다. 시작 시 무조건 resume을 호출하지 않도록 이번 구현도 초기 상태에서는 어댑터를 실행하지 않는다.

페이지의 선택자 기반 정지와 별도의 수동 숨김 정책이 있다면 그 역할을 함께 정리한다. 이 모듈의 어댑터는 동기적으로 정지 상태를 적용하는 함수여야 한다. 다른 운영 코드의 요청을 자동으로 찾아 병합하는 기능은 아니다. 누가 상태를 소유하는지 확인해야 복귀도 안전하게 연결할 수 있다.

전체 화면과 외부 뷰어는 실제 상태를 받는다

전체 화면 상태는 문서의 실제 fullscreenElement와 상태 변경 이벤트로 연결할 수 있다. 예시의 바인딩 함수는 처음 연결할 때도 현재 상태를 읽고 정리할 때 자신이 관리한 조건을 해제한다. 한 문서의 전체 화면 바인딩을 여러 곳에서 중복으로 만들지 않는 조건으로 사용한다.

외부 프레임 안의 VR 조작 상태는 부모 페이지가 임의로 읽을 수 있다고 가정하지 않는다. 해당 뷰어가 지원하는 이벤트나 연결 방법을 확인해야 한다. 메시지 연결을 새로 설계한다면 출처와 보낸 프레임, 허용한 메시지 형태도 검토할 수 있다. 아래 실행 예시는 실제 VR이나 전체 화면을 호출하지 않고 합성 상태로 중첩 조건만 보여준다.

정지 이유를 관리하는 코드

다음 코드는 독립된 정지 이유와 지속 조건을 관리한다. 실제 문의바에 연결할 경우 어댑터에서 기존 pause와 resume API를 사용할 수 있다. 이 글의 예시는 자신만 사용하는 바의 표시 상태를 바꾸며 고객 사이트의 문의 기능을 호출하지 않는다.

정리 함수는 관리 중인 이유를 해제하고 이후 새 요청을 거절한다. 어댑터가 실패한 경우에는 이유를 보존하거나 획득을 취소해 다시 처리할 수 있도록 했다. 상태를 계산했다는 사실과 실제 요소가 가려지지 않는지 확인한 결과는 다르므로 화면 검토가 뒤따라야 한다.

코드와 구현 살펴보기

JavaScript · 여러 조작 상태의 정지 조정기

export function createPauseGate(setPaused){
 if(typeof setPaused!=='function')throw new TypeError('A synchronous pause adapter is required');
 const leases=new Map(),conditions=new Map();let paused=false,closed=false;
 function snapshot(){return {paused:leases.size>0,reasons:[...new Set(leases.values())],leases:leases.size};}
 function apply(){const next=leases.size>0;if(next!==paused){setPaused(next,snapshot());paused=next;}}
 function acquire(reason){
  if(closed||typeof reason!=='string'||!reason.trim())throw new TypeError('An active gate and reason are required');
  const token=Symbol(reason);leases.set(token,reason);try{apply();}catch(error){leases.delete(token);throw error;}
  let released=false;
  return ()=>{if(released||closed)return;leases.delete(token);try{apply();released=true;}catch(error){leases.set(token,reason);throw error;}};
 }
 function setCondition(reason,active){
  if(typeof active!=='boolean')throw new TypeError('Condition must be a boolean');
  if(active&&!conditions.has(reason))conditions.set(reason,acquire(reason));
  else if(!active&&conditions.has(reason)){conditions.get(reason)();conditions.delete(reason);}
 }
 return {acquire,setCondition,snapshot,destroy(){if(closed)return;const previous=new Map(leases);leases.clear();try{apply();}catch(error){for(const [k,v]of previous)leases.set(k,v);throw error;}conditions.clear();closed=true;}};
}
export function bindFullscreenPause(document,gate){
 const update=()=>gate.setCondition('fullscreen',Boolean(document.fullscreenElement));
 document.addEventListener('fullscreenchange',update);update();
 return ()=>{document.removeEventListener('fullscreenchange',update);gate.setCondition('fullscreen',false);};
}

겹친 상태를 하나씩 닫는 실행 예시

모의 뷰어 상태를 켜고 팝업을 열어볼 수 있게 작성했다. 팝업을 닫아도 뷰어 상태가 남으면 바가 복귀하지 않고, 마지막 상태를 끄면 다시 표시한다. 실제 공간이나 고객 자료를 재현하지 않는 작은 동작 예시다. 공개 HTML 프레임에서 같은 새 조정 함수를 사용한다.

이번에 합성 상태와 가짜 문서 이벤트로 10개 검사를 실행했다. 중첩과 같은 이유의 독립 요청, 반복 해제, 반복 조건 이벤트, 초기 상태, 잘못된 옵션, 전체 화면 조건 정리, 어댑터 실패와 재시도, 조정기 정리를 확인했다. 실제 팝업·VR·전체 화면과 화면 충돌은 새 브라우저에서 검수하지 않았다.