시안과 구현 화면을 나란히 놓으면 차이가 곧바로 보이는 것 같다. 그러나 애니메이션이 있는 화면은 어떤 순간에 캡처했는지에 따라 다른 모습이 나온다. 나는 과거 검수 기록을 다시 읽으면서 화면의 상태와 실제 구현 차이를 먼저 나누는 기준을 정리했다.

한 비교 문서에는 원본 슬라이드가 넘어가는 중이라 옆 이미지의 일부가 보였다는 기록이 있었다. 다른 보존 작업에는 원래 전체 화면 이동과 등장 시점을 유지하라는 조건이 있다. 여기서는 이런 근거를 바탕으로 비교 가능한 상태를 만들고 기록하는 방법을 남긴다.

먼저 비교하는 화면의 상태를 맞춘다

같은 너비와 높이를 입력해도 첫 슬라이드와 세 번째 슬라이드, 닫힌 메뉴와 열린 메뉴를 비교하면 차이가 생긴다. 확인한 과거 화면 비교 문서에는 PC와 모바일의 크기뿐 아니라 어느 슬라이드를 기준으로 했는지, 메뉴가 어떤 상태였는지도 남아 있다. 결과 이미지와 조건을 같이 기록하는 방식이다.

나는 화면의 주소 역할, 언어, 스크롤 위치, 선택한 슬라이드와 열린 패널을 먼저 맞추려 한다. 같은 크기로 찍었다는 사실은 같은 상태로 찍었다는 뜻이 아니다. 조건이 다른 부분을 먼저 표시해야 그 차이를 CSS나 간격 문제로 잘못 수정하지 않는다.

전환 중의 조각을 새 디자인으로 고치지 않는다

과거 비교 기록에는 원본 슬라이더가 넘어가는 중에 캡처되어 옆 슬라이드의 좁은 조각이 보였다는 설명이 있다. 구현은 같은 완전한 이미지 자산을 쓰되 그 순간의 조각을 별도 디자인으로 재현하지 않았다고 기록했다. 이 사례는 캡처에 보인 모든 모양을 정적인 설계 조건으로 가져오면 안 된다는 근거다.

반대로 차이가 전환 때문이라고 추측만 하고 넘겨서도 안 된다. 전환이 끝난 상태에서 같은 자료를 다시 확인하고 그때도 남는 크기와 위치 차이를 분리해야 한다. 캡처 시점의 가능성과 실제 확인한 상태를 구별해 남기는 것이 수정 판단을 정확하게 하는 방법이다.

글꼴과 이미지가 준비된 상태도 기록한다

화면을 열자마자 찍으면 대체 글꼴과 아직 나타나지 않은 이미지가 비교에 들어갈 수 있다. 같은 글자 크기의 선언이라도 글꼴이 달라지면 제목의 줄과 버튼 폭이 달라질 수 있다. 이미지가 준비되지 않았을 때의 빈 영역도 최종 화면과 다른 상태다.

실제 브라우저에서 폰트와 이미지의 준비, 선택한 프레임의 안정 상태를 확인한 뒤 기록할 수 있다. 정해진 시간만 기다렸다는 사실을 자동으로 완료 상태로 바꾸지 않는다. 자료가 많거나 연결이 다른 화면에서 같은 시간이 항상 같은 준비 상태를 보장하지 않기 때문이다.

전체 움직임과 슬라이드 움직임을 나누어 본다

전체 화면의 구간 이동과 그 안의 슬라이더가 동시에 있을 수 있다. 원형 보존 계약에는 서로 다른 이동과 등장 시점이 있었다. 하나가 멈췄다고 다른 동작까지 끝난 것은 아니다. 비교할 때 어떤 구간에 들어왔고 그 안의 어느 자료를 선택했는지 각각 확인해야 한다.

전환 시간을 임의로 바꾸어 스크린샷만 맞추면 실제 운영 동작이 달라질 수 있다. 기존 화면의 보존이 목적이라면 원래 타이밍과 연결을 유지하고 안정된 비교 상태를 마련한다. 새 동작을 설계하는 작업이라면 그 변경을 별도 결정으로 남긴다. 캡처를 맞추는 일과 기능을 바꾸는 일을 섞지 않는다.

비교 조건을 확인하는 작은 기록 도구

다음 함수는 두 캡처 기록의 조건이 비교 가능한지 확인한다. 화면 크기, 위치, 언어, 슬라이드, 메뉴 상태와 준비 확인이 빠져 있거나 다르면 이유를 돌려준다. 실제 캡처를 생성하거나 화면의 차이를 계산하지 않는다. 기록에 넣은 확인 값의 정확성은 실제 화면을 본 사람이 책임져야 한다.

이 도구에는 합성 기록으로 10개 검사를 실행했다. 같은 조건, 다른 슬라이드와 메뉴·언어·스크롤, 확인되지 않은 글꼴과 움직임, 다른 크기, 누락·문자열 상태, 입력 보존을 확인했다. 조건이 같다는 출력에는 디자인이 같다는 결과를 넣지 않았다. 준비 검사와 시각 비교가 다른 일이기 때문이다.

코드와 구현 살펴보기

JavaScript · 비교 전 캡처 조건 대조

export function compareCaptureConditions(reference,implementation){
 const reasons=[];
 for(const [name,capture] of [['reference',reference],['implementation',implementation]]){
  if(!capture||!Number.isFinite(capture.viewport?.width)||!Number.isFinite(capture.viewport?.height)||capture.viewport.width<=0||capture.viewport.height<=0)reasons.push(name+':viewport-unrecorded');
  if(!Number.isFinite(capture?.scrollY))reasons.push(name+':scroll-unrecorded');
  for(const flag of ['fontsReady','imagesReady','motionSettled'])if(capture?.[flag]!==true)reasons.push(name+':'+flag+'-unconfirmed');
  for(const field of ['pageKey','language','slideKey','menuState'])if(typeof capture?.[field]!=='string'||!capture[field])reasons.push(name+':'+field+'-unrecorded');
 }
 if(reference&&implementation){
  for(const field of ['pageKey','language','slideKey','menuState'])if(reference[field]!==implementation[field])reasons.push(field+'-differs');
  if(reference.viewport?.width!==implementation.viewport?.width||reference.viewport?.height!==implementation.viewport?.height)reasons.push('viewport-differs');
  if(Number.isFinite(reference.scrollY)&&Number.isFinite(implementation.scrollY)&&Math.abs(reference.scrollY-implementation.scrollY)>1)reasons.push('scroll-position-differs');
 }
 return {readyForComparison:reasons.length===0,reasons,scope:'Recorded capture conditions only. Matching conditions do not establish visual equivalence or verify the accuracy of supplied flags.'};
}

조건을 맞춘 뒤 실제 차이를 판단한다

상태가 맞으면 제목의 기준선, 여백, 이미지 크롭, 버튼의 위치 같은 실제 차이를 살펴볼 수 있다. 큰 구간의 구조부터 확인하고 작은 간격을 조정하면 무엇을 고쳤는지 이해하기 쉬워진다. 한 장의 이미지에서 바로 전체 동작과 반응형 화면을 통과 처리하지 않는다.

이번 글은 원형 보존 문서와 과거 캡처 검수 기록, 새 기록 함수의 코드 검사로 작성했다. 참고 화면과 원래 자산은 그 출처의 자료이며 직접 만든 디자인 성과로 바꾸어 주장하지 않는다. 새 브라우저 캡처와 시각 비교는 수행하지 않았다. 다음 적용에서는 같은 조건의 실제 화면으로 그 판단을 이어가야 한다.