최근 AI 모델이 비약적으로 발전하면서, 비개발자인 저도 정적인 웹사이트뿐만 아니라 다양한 애니메이션을 적용한 웹사이트를 만들 수 있겠다 싶어 개인 프로젝트와 업무에 적용시켜보고 있습니다.
이번에 구현하고자 했던 웹페이지의 주요 특성은 다음과 같습니다.
- 스크롤에 따라서 화면이 멈추고 안의 컨텐츠가 애니메이션으로 나타났다 사라졌다 함
- 배경 백그라운드를 움직이는 패턴으로 구현
이 글에서는 그 과정에서 반복된 문제, 실제로 확인한 원인, 해결한 방법의 순서로 정리합니다.
1. 주로 구현하고자 한 것: 스크롤 연계 애니메이션
1) 주요 목표
한 페이지에는 여러가지 섹션(section)들이 있되, 일반적인 웹사이트들과 달리 각 섹션들이 스크롤에 따라 다양하게 움직이도록 하는 것이 목표였습니다.


예를 들어, 위의 사진과 같이 전체 페이지 바탕과 컨텐츠는 고정이지만, 우측의 여러 창들이 스크롤에 따라 넘어가면서 내용이 순서대로 잘 읽히게끔 구현하는 것입니다.
2) 작업 전 우려 사항
(1) 스크롤을 올렸을 때도 정상작동 할 것
예전에 랜딩 페이지를 만드는 프로젝트를 할 때, 아래로 내려가는 단방향 흐름만 고려한 개발 작업으로 인해 위로 올렸을 때 컨텐츠가 사라지거나 애니메이션이 엉켜 새로고침하지 않으면 웹페이지를 온전히 경험하지 못했던 기억이 있었습니다. 스크롤을 아래로 내렸을 때뿐만 아니라 위로 다시 올렸을 때도 정상 작동하는지를 꼼꼼히 확인해야 했습니다.
(2) 복잡한 반응형 구현
AI로 웹페이지 작업을 하다보면 하나의 환경(보통 데스크탑)에만 집중하게 되어 모바일 구현을 놓치는 경우가 종종 있습니다. 데스크탑 환경과 스마트폰과 같은 모바일 환경은 활용 가능한 대지 캔버스(canvas) 크기가 다르기 때문에, 레이아웃과 컴포넌트의 위치 구성이 달라질 수밖에 없습니다. 데스크탑에서 구현했던 좌우 또는 2열 구성을 모바일에서는 완전히 1열로 펼쳐서 구현해야 하기 때문에 이를 꼭 염두하고 작업해야 했습니다.
3) 주요 사용 기술과 레퍼런스
(1) 주요 사용 기술
전체 페이지는 Next.js 16과 React 19로 구성하고, 화면 크기에 따른 레이아웃과 스타일은 Tailwind CSS 4로 구현했습니다. 스크롤에 맞춰 요소를 움직이거나 특정 구간에 고정하는 효과에는 GSAP의 ScrollTrigger와 ScrollSmoother를 사용했습니다.
| 기술 | 사용한 부분 |
|---|---|
| Next.js 16·React 19 | 페이지와 컴포넌트 구성, 사용자 동작에 따른 화면 변화 처리 |
| Tailwind CSS 4 | 반응형 레이아웃, 요소의 위치·크기·간격 조정 |
| GSAP ScrollTrigger | 스크롤 위치에 따른 애니메이션 실행과 요소 고정(pin) |
| GSAP ScrollSmoother | 페이지의 부드러운 스크롤 효과 |
| Three.js·React Three Fiber | 움직이는 배경과 셰이더 효과를 React 컴포넌트로 구현 |
| WebGL | Three.js로 만든 셰이더와 그래픽 효과를 브라우저에서 렌더링 |
Three.js와 React Three Fiber는 움직이는 배경과 셰이더 효과를 구현하는 과정에서 사용했습니다. Three.js가 그래픽을 처리하는 라이브러리라면 React Three Fiber는 이를 React 컴포넌트 안에서 사용할 수 있도록 연결해 주는 도구입니다. 이 작업에서는 WebGL을 기반으로 렌더링했습니다.
(2) 주요 레퍼런스
페이지 구성과 애니메이션에 대한 영감은 GSAP Showcase, Awwwards.com과 같은 사이트들을 적극적으로 활용했습니다. 페이지에 사용될 컴포넌트(component)들은 21st.dev, Codepen에 올라온 것들을 찾아 활용했습니다. ChatGPT, Claude 와 같은 AI에게 어떤 레퍼런스와 어떤 컴포넌트를 찾고 싶은지에 대해 상세하게 말하면 도움이 되는 웹사이트들을 추천해주니 적극적으로 활용하시는 것을 추천드립니다.
(3) 주요 사용 스킬
AI가 일정한 작업 절차를 따르도록 하기 위해 Superpowers와 GSD(Get Shit Done)도 활용했습니다. 여기서 스킬(skill)은 AI가 작업할 때 따라야 할 절차와 확인 사항을 정리해 놓은 지침입니다.
| 사용 스킬 | 활용한 부분 |
|---|---|
| Superpowers | 요구사항 정리, 작업 계획 수립, 문제 원인 조사, 수정 후 검증과 코드 검수 |
| GSD | 프로젝트 목표와 요구사항 정리, 작업 단계 분리, 진행 상태와 결정 사항 기록 |
Superpowers는 AI가 바로 코드를 수정하지 않고 요구사항과 구현 방법을 먼저 정리한 뒤, 계획·구현·검증의 순서로 작업하도록 하는 데 활용했습니다. GSD는 규모가 큰 작업을 여러 단계로 나누고, 각 단계의 목표와 현재 진행 상태를 파일로 남기는 데 활용했습니다. 대화가 길어지면서 AI가 이전 결정이나 현재 상태를 놓치는 문제를 줄이기 위한 목적도 있었습니다.
다만 스킬을 사용한다고 해서 AI가 해당 절차를 반드시 지키는 것은 아니었습니다. 작업 세션이 길어지면 AI가 실제 확인 절차를 수행하지 않고도 스킬을 사용했거나 검증을 완료했다고 답하는 경우가 있었습니다. 따라서 스킬은 작업 방식을 보조하는 지침으로 활용하고, 실제 화면 확인과 최종 검증은 별도로 진행해야 했습니다.
2. 스크롤 지옥의 시작
스크롤 연계 애니메이션을 구현하면서 고정되어야 할 화면이 사라지기도 했고, 화면 크기를 바꿀수록 빈 공간이 커지거나 모바일에서 화면이 떨리기도 했습니다. 프레임은 정상인데 스크롤이 느리게 느껴지는 경우도 있었습니다.
반복된 문제는 다음 다섯 가지로 분류할 수 있었습니다.
- 섹션 고정 애니메이션이 만들어졌다가 사라지는 문제
- 브라우저 크기를 바꾸면 레이아웃과 스크롤 위치가 틀어지는 문제
- 모바일에서 고정 구간이 위아래로 진동하듯 떨리거나 끊기는 문제
- 스크롤했을 때 화면이 늦게 따라오는 문제
- 움직이는 배경으로 인한 성능 저하로 스크롤이 버벅거리는 경우
- AI가 문제를 재현하지 않고 수정해 새로운 문제가 생기는 경우
1) 고정 애니메이션이 만들어졌다가 사라지는 문제
(1) 핵심 주요 현상
가장 오랫동안 해결하지 못했던 것은 화면을 불러온 뒤 고정(pin) 애니메이션이 사라지는 문제였습니다. 페이지가 나타나자마자 스크롤하면 정상적으로 작동했지만, 페이지를 열고 잠시 기다린 다음 스크롤하면 고정 구간이 작동하지 않았습니다. 개발 환경에서는 정상인 것처럼 보이다가 실제 배포 환경에서 더 자주 발생하기도 했습니다.
GSAP이 고정 구간을 만들 때 생성하는 pin-spacer의 개수를 시간에 따라 확인해 보니 0 → 4 → 0으로 변했습니다. 고정 애니메이션이 처음부터 만들어지지 않은 것이 아니라, 네 개가 정상적으로 만들어진 뒤 다시 제거되고 있었습니다.
(2) 근본 원인
원인은 GSAP 설정 하나가 잘못된 것이 아니라, 애니메이션을 만들고 화면을 다시 계산하는 여러 작업이 같은 시점에 겹친 것이었습니다. 먼저 서버에서 화면을 만들 때와 브라우저에서 화면을 이어받을 때 일부 설정값이 달라졌습니다. 해당 값이 바뀌면서 애니메이션을 만드는 코드가 다시 실행됐고, 처음 만든 고정 애니메이션이 정리된 뒤 다시 생성됐습니다.
컴포넌트의 실행 순서도 예상과 달랐습니다. 전체 스크롤을 관리하는 ScrollSmoother가 먼저 준비되고 각 섹션의 고정 애니메이션이 나중에 만들어져야 했지만, 실제로는 자식 섹션이 먼저 고정을 만들었습니다. 이후 부모가 스크롤 환경을 준비하면서 자식이 만들어 놓은 고정까지 함께 제거했습니다.
여기에 여러 컴포넌트에서 ScrollTrigger.refresh()를 각각 실행하고 있었습니다. refresh()는 현재 화면의 위치를 다시 계산하기 위해 고정을 잠시 풀었다가 다시 만드는 작업입니다. 여러 곳에서 이 작업이 겹치자 한 코드가 고정을 정리하는 동안 다른 코드가 같은 요소를 다시 변경하는 문제가 발생했습니다.
(3) 해결책
실행 시간을 임의로 늦추는 방식은 기기나 배포 환경마다 화면이 준비되는 시간이 다르기 때문에 “몇 초 뒤 실행”과 같은 방식으로는 같은 문제가 다시 발생할 수 있었습니다. 대신 ScrollSmoother가 준비됐다는 신호를 명시적으로 만들었습니다. 부모가 스크롤 환경을 모두 만든 뒤 준비 완료 신호를 보내고, 자식 섹션들은 해당 신호를 받은 다음에 고정 애니메이션을 만들도록 순서를 정했습니다.
ScrollTrigger.refresh()도 각 컴포넌트가 직접 실행하지 않고 페이지 전체에서 한곳이 관리하도록 변경했습니다. 짧은 시간 안에 여러 요청이 들어오면 한 번만 실행하고, 이미 재계산 중이라면 현재 작업이 끝난 뒤 다시 실행하도록 했습니다. 화면 높이를 맞추기 위해서만 사용하는 값은 React의 상태로 관리하지 않고 해당 요소에 직접 적용했습니다. 높이 하나가 바뀔 때마다 React가 화면 전체를 다시 반영하면서 GSAP의 재계산과 충돌하는 일을 줄이기 위해서였습니다.
결과적으로 고정 애니메이션은 다음 순서로 만들어지도록 정리했습니다.
- 브라우저 환경과 사용자 설정을 확인합니다.
ScrollSmoother를 먼저 준비합니다.- 준비 완료 신호를 보냅니다.
- 각 섹션의 고정 애니메이션을 만듭니다.
- 모든 고정이 만들어진 뒤 전체 위치를 한 번 다시 계산합니다.
2) 브라우저 크기를 바꾸면 레이아웃과 스크롤 위치가 틀어지는 문제
(1) 핵심 주요 현상
브라우저 너비를 늘렸다가 줄이면 섹션 높이가 유동적으로 변경되고, 같은 동작을 반복할수록 이미지와 글 사이의 빈 공간도 점점 커졌습니다. 420px 너비에서 실제 내용에는 약 390px의 높이가 필요했지만, 섹션에는 975px의 높이가 적용됐습니다. 데스크톱 크기로 넓혔다가 다시 모바일 크기로 줄이자 높이는 1,378px까지 늘어났습니다.
여러 개의 고정 구간을 연이어 배치했을 때는 뒤쪽 애니메이션의 시작 지점도 어긋났습니다. 페이지 아래에서 새로고침하면 원래 보던 위치보다 위로 이동했고, 다른 페이지를 다녀온 뒤 돌아오면 이전 스크롤 위치로 다시 끌려가는 현상도 발생했습니다.
(2) 근본 원인
화면 크기가 바뀐 직후에는 새로운 레이아웃이 완전히 반영되지 않습니다. 글의 줄바꿈, 반응형 레이아웃, 이미지 크기 등이 아직 변경되는 중인데 높이를 측정하면서 이전 화면의 값을 읽고 있었습니다. 더 큰 문제는 이전에 적용한 높이를 해제하지 않고 같은 요소를 다시 측정한 것이었습니다. 잘못 적용된 높이가 다음 측정의 기준이 됐기 때문에 화면 크기를 바꿀 때마다 오차가 누적됐습니다.
같은 요소를 측정하는 기능이 여러 곳에 존재한 것도 문제였습니다. 한 기능이 요소의 높이를 바꾸면 다른 기능은 자연스러운 내용 높이가 아니라 이미 변경된 높이를 측정했습니다. ResizeObserver가 측정한 결과로 다시 같은 요소의 크기를 바꾸면서 측정과 변경이 반복되는 경우도 있었습니다.
고정 애니메이션 자체도 문서 전체 높이에 영향을 줬습니다. 앞쪽 고정 구간이 만든 여유 공간만큼 뒤쪽 섹션의 위치가 달라지기 때문에, 모든 고정을 동시에 계산하면 뒤쪽 애니메이션이 잘못된 위치를 기준으로 만들어질 수 있었습니다. 스크롤 위치 복원도 문서 높이가 확정되기 전에 실행되고 있었습니다. 고정 애니메이션이 만들어지기 전의 짧은 문서를 기준으로 스크롤 위치를 복원한 뒤 문서 높이가 늘어나면서, 최종적으로 다른 위치에 도착한 것입니다.
(3) 해결책
화면을 다시 측정할 때는 기존에 적용한 높이와 위치 값을 먼저 제거했습니다. 요소를 자연스러운 크기로 되돌린 뒤 두 번의 화면 갱신을 기다리고 새로운 높이를 측정했습니다. 측정 시간을 늦추는 것보다 이전 값을 먼저 해제하는 것이 중요했습니다.
하나의 요소를 측정하는 기능도 한곳으로 통합했습니다. 자연스러운 높이와 같은 값을 다시 기록하는 불필요한 측정은 제거하고, 실제로 고정값이 필요한 화면에서만 높이를 적용했습니다.
고정 애니메이션은 페이지에서 나타나는 순서대로 계산하도록 했습니다. 앞쪽 고정이 문서에 추가한 공간을 반영한 다음 뒤쪽 고정의 시작 위치를 계산해야 했기 때문입니다.
스크롤 위치는 모든 고정 애니메이션이 만들어져 문서 높이가 확정된 다음 복원했습니다. 새로고침이나 뒤로 가기로 돌아온 경우에만 이전 위치를 복원하고, 일반적인 페이지 이동에서는 과거 위치를 강제로 적용하지 않도록 구분했습니다. 복원 중 사용자가 휠이나 터치로 스크롤하면 자동 복원도 즉시 중단했습니다.
3) 모바일에서 고정 구간이 떨리거나 끊기는 문제
(1) 핵심 주요 현상
데스크톱에서 정상적으로 작동하던 고정 애니메이션은 모바일에서 다른 문제를 만들었습니다. 스크롤할 때 고정된 화면이 위아래로 떨리거나, 화면 아래에 흰색 빈 공간이 나타났습니다. 터치 스크롤이 계단처럼 끊겨 움직이는 경우도 있었고, 고정이 시작되는 순간 화면이 덜컥이거나 한 프레임 정도 깜빡이기도 했습니다.
특정 스마트폰에서만 발생하는 문제처럼 보였지만, 실제로는 같은 화면 너비에서도 브라우저의 상단과 하단 메뉴가 차지하는 공간에 따라 결과가 달라졌습니다.
(2) 근본 원인
모바일 브라우저에서는 주소창과 하단 메뉴가 스크롤에 따라 접히거나 다시 나타납니다. 이 과정에서 브라우저가 알려주는 화면 높이도 계속 달라집니다. 변하는 높이를 기준으로 고정 구간의 크기를 계산하면 스크롤하는 동안 섹션 높이도 함께 바뀝니다. innerHeight나 dvh를 사용했을 때 주소창 상태에 따라 고정 구간이 다시 계산되면서 화면이 흔들린 이유입니다.
터치 스크롤에서는 ScrollSmoother, normalizeScroll, scrub, anticipatePin과 같은 설정이 서로 영향을 줬습니다. 한 설정만 바꾸면 떨림은 줄어들지만 스크롤이 계단처럼 끊기거나, 끊김은 줄어들지만 고정 순간의 깜빡임이 커지는 식이었습니다. 페이지 전체에 적용한 overflow 설정도 iOS의 고정 위치 계산에 영향을 줬습니다. 가로 스크롤을 막기 위해 페이지 전체의 넘침을 숨겼지만, 그 결과 고정된 요소가 화면과 함께 밀리는 새로운 문제가 발생했습니다.
(3) 해결책
고정 구간의 높이는 스크롤 중 값이 계속 변하는 dvh (Dynamic Viewport Height, 브라우저 주소창/툴바가 나타나거나 사라질 때 현재 실제 화면 높이에 맞춰 값이 동적으로 변화) 대신 비교적 일정하게 유지되는 svh(Small Viewport Height, 주소창/툴바가 모두 표시되어 화면이 가장 작을 때의 높이를 기준으로 고정)를 기준으로 계산했습니다. svh를 사용하면 브라우저 메뉴가 접혔을 때 화면 아래에 배경이 부족하게 보일 수 있습니다. 이 공간을 없애기 위해 섹션 자체의 높이를 늘리지는 않았습니다. 높이를 늘리면 고정 위치가 다시 흔들릴 수 있기 때문입니다. 대신 레이아웃에 영향을 주지 않는 별도의 배경을 화면 아래까지 확장했습니다.
터치 관련 설정은 하나씩 조정하지 않고 하나의 조합으로 검증했습니다. 떨림, 계단식 움직임, 고정 순간의 덜컥임과 깜빡임을 함께 확인하고 가장 부작용이 적은 조합을 선택했습니다. 가로 스크롤을 막을 때도 페이지 전체의 넘침을 숨기지 않았습니다. 가로 방향 등장 애니메이션처럼 실제로 화면 밖으로 나가는 요소에서만 넘침을 제한했습니다.
모바일 반응형 검사는 화면 너비만 기준으로 하지 않았습니다. 같은 너비에서도 실제 화면 높이가 다를 수 있기 때문에 너비와 높이를 함께 바꿔가며 확인했고, 실제 기기에서는 브라우저 메뉴가 접히고 펼쳐지는 상태에서 직접 터치했습니다.
4) 스크롤했을 때 화면이 늦게 따라오는 문제
(1) 핵심 주요 현상
스크롤시 특정 섹션에서 버벅거리고 느리게 움직이는 경우가 종종 있었습니다. 브라우저에서 마우스로 직접 이동시킨 스크롤 위치와 실제 화면에 표시되는 위치 사이에는 큰 차이가 느껴졌습니다.
(2) 근본 원인
원인은 ScrollSmoother에 설정한 smooth 값이었습니다. ScrollSmoother는 사용자가 스크롤한 위치로 화면을 바로 이동시키지 않고, 일정한 시간에 걸쳐 천천히 따라가도록 만듭니다. 이 값을 높이면 움직임이 부드러워 보이지만, 사용자의 조작보다 화면이 늦게 반응할 수 있습니다.
당시 smooth 값은 0.6으로 설정되어 있었습니다. 연속해서 스크롤했을 때 사용자가 이동시킨 위치와 실제 화면 위치가 최대 381px까지 벌어졌고, 휠 입력을 멈춘 뒤에도 약 316ms 동안 화면이 계속 움직였습니다. 페이지가 실제로 끊긴 것은 아니었지만, 사용자의 조작에 즉시 반응하지 않았기 때문에 버벅거리거나 무겁게 움직이는 것처럼 느껴졌던 것입니다.
(3) 해결책
ScrollSmoother의 smooth 값을 0.6에서 0.4로 낮췄습니다. 변경 후 연속 스크롤 중 위치 차이는 최대 381px에서 135px로 줄었고, 입력이 끝난 순간의 위치 차이도 311px에서 62px로 감소했습니다. 스크롤의 부드러운 움직임은 유지하면서 사용자의 조작을 더 빠르게 따라오도록 조정한 것입니다.
이후에는 스크롤이 부드럽게 보이는지만 확인하지 않고, 휠이나 손가락을 멈췄을 때 화면도 적절한 시점에 멈추는지를 함께 확인했습니다. ScrollSmoother를 사용할 때는 smooth 값을 높일수록 좋은 것이 아니었습니다. 부드러운 움직임과 사용자의 조작에 빠르게 반응하는 정도 사이에서 적절한 값을 찾아야 했습니다.
5) 움직이는 배경으로 인한 성능 저하로 스크롤이 버벅거리는 경우
(1) 핵심 주요 현상
페이지에는 총 여섯 개의 셰이더 배경(shader background, 브라우저가 색상과 무늬를 실시간으로 계산해 그리는 움직이는 배경)이 적용되어 있었습니다. 여러 배경이 동시에 실행되면 CPU나 GPU가 낮은 기기들에서 엄청난 렉을 유발할 수 있기 때문에 주의해야 할 사항이었습니다.
(2) 근본 원인
처음에는 브라우저에서 CPU 성능을 낮추는 방식으로 페이지를 확인했습니다. 하지만 CPU를 느리게 설정한다고 해서 셰이더를 처리하는 GPU의 성능까지 같은 비율로 낮아지는 것은 아니었습니다.
셰이더가 화면을 그리는 방식에도 불필요한 작업이 포함되어 있었습니다. 고해상도 화면에서는 실제 화면보다 많은 픽셀을 그리고 있었고, 화면의 주사율에 맞춰 초당 60회 또는 120회씩 새로운 화면을 만들고 있었습니다. 그라디언트나 물결처럼 경계가 뚜렷하지 않은 배경은 이 정도의 해상도와 실행 횟수가 필요하지 않았습니다. 사용자가 보고 있지 않은 화면 밖의 셰이더도 계속 실행되고 있었습니다.
(3) 해결책
각 셰이더가 화면을 한 번 그릴 때 GPU를 얼마나 사용하는지 직접 측정했습니다. 이를 통해 어떤 셰이더가 상대적으로 많은 성능을 사용하는지 확인했습니다.
셰이더의 색상이나 움직임을 단순하게 만드는 대신 다음 두 가지를 먼저 조정했습니다.
- 셰이더가 한 번에 그리는 픽셀 수를 줄였습니다.
- 셰이더가 새로운 화면을 만드는 횟수를 초당 30회로 제한했습니다. (적용중인 배경화면은 고해상도 및 고프레임이 필수적이지 않았습니다.)
화면 밖으로 나간 셰이더는 실행을 멈추고, 다시 화면에 들어왔을 때만 움직이도록 변경했습니다. 변경 후 셰이더가 화면을 한 번 그리는 데 필요한 GPU 사용 시간은 종류에 따라 약 6배에서 14배 감소했습니다. 변경 전후 화면을 캡처해 비교했을 때는 눈에 띄는 차이가 없었습니다.
움직이는 배경을 최적화할 때는 효과의 색상이나 움직임부터 줄일 필요가 없었습니다. 사용자가 보는 결과를 바꾸기 전에 해상도와 실행 횟수, 화면 밖에서도 계속 실행되고 있는지를 먼저 확인해야 했습니다.
6) AI가 화면을 확인하지 않고 추측으로 수정한 문제
(1) 핵심 주요 현상
문제가 발생할 때마다 AI에게 실제 화면에서 현상을 먼저 확인하고, AI에게 원인을 파악한 뒤 수정하라고 명시했습니다. 그러나 AI는 화면에서 같은 문제를 재현하지 않은 상태에서도 코드만 보고 원인을 추측해 수정하는 경우가 심각하게 많이 있었습니다. AI가 수정할 때마다 제가 다시 화면을 확인하고, 문제가 해결되지 않은 변경을 찾아 되돌려야 했습니다. 잘못된 가설 하나에도 수정, 빌드, 배포, 재확인 과정이 반복됐습니다.
(2) 근본 원인
근본적인 문제는 AI가 제가 요청한 화면 확인 과정을 생략하고, 가능성이 있어 보이는 원인을 실제로 확인된 원인처럼 취급한 것이었습니다. AI가 사용한 자동화 브라우저에서는 화면이 보이지 않는 동안 애니메이션의 화면 갱신이 멈추거나 매우 느려지는 경우가 있었습니다. 이런 환경에서는 등장 효과, 캐러셀 전환, 스크롤 애니메이션의 움직임을 정상적으로 확인하기 어려웠습니다.
그런데도 AI는 움직임을 확인할 수 없는 정지 화면을 기준으로 애니메이션이 정상인지 판단했습니다. 실제 마우스 휠이나 터치 입력을 사용하지 않고 scrollTo()로 특정 위치에 이동한 뒤, 스크롤 동작 전체를 확인한 것처럼 판단하기도 했습니다. 현재 확인하고 있는 브라우저 탭과 페이지 주소, 배포 버전을 확인하지 않은 경우도 있었습니다. 수정된 코드가 반영된 화면이 아니라 이전 배포본을 보고 있으면서, 새로운 코드에 문제가 있다고 판단한 사례도 있었습니다.
결국 AI가 다음 항목을 구분하지 않은 것이 문제였습니다.
- 실제 화면에서 확인한 현상
- 코드만 보고 추측한 원인
- 아직 재현하지 못한 문제
- 수정을 통해 검증해야 하는 가설
추측에 불과한 내용을 확인된 원인처럼 다루면서 불필요한 수정이 반복됐습니다.
(3) 해결책
이 문제는 AI에게 확인 절차를 더 자세히 지시하는 것만으로 해결되지 않았습니다. 코드 수정 전에 화면을 확인하고, 수정 후에는 다시 QA를 진행하라고 요청해도 실제로 검사하지 않은 채 확인을 완료했다고 답하는 경우가 있었습니다. 특정 지침이나 스킬을 사용하라고 명시했을 때도 실제 절차를 수행하지 않고 사용했다고 보고하는 문제가 반복됐습니다.
이러한 현상은 하나의 세션에서 오랫동안 작업할수록 더 자주 발생했습니다. 대화 안에 이전 수정안과 기각한 방법, 서로 다른 화면의 문제, 여러 차례 변경된 지시가 함께 쌓이면서 AI가 현재 코드의 상태와 작업 조건을 제대로 구분하지 못했습니다. 이미 되돌린 방법을 다시 제안하거나, 이전에 확인한 화면을 현재 화면으로 착각하는 경우도 있었습니다.
같은 세션 안에서 확인 지시를 반복하는 것보다, 누적된 컨텍스트를 정리해 새로운 세션을 시작하고 실제 로컬 브라우저에서 다시 검사하게 하는 것이 더 효과적이었습니다. 같은 문제가 반복되기 시작하면 기존 세션에서 지시를 계속 추가하지 않고, 현재 상황을 정리해 새로운 세션에서 작업을 시작했습니다. 새로운 세션에는 다음 내용만 다시 전달했습니다.
- 현재 구현하려는 화면과 정상 작동 기준
- 지금 실제로 발생하고 있는 문제
- 이미 적용했다가 되돌린 수정안
- 현재 코드에 적용되어 있는 방법
- 문제가 발생한 화면 크기와 브라우저
- 수정 전후에 반드시 확인해야 할 동작
화면 검사는 화면이 보이지 않는 자동화 브라우저보다 사용자 로컬 환경에서 직접 페이지를 실행해 진행하도록 했습니다. 자동화 브라우저에서는 애니메이션의 화면 갱신이 멈추거나 실제보다 느리게 실행될 수 있어, 스크롤 속도와 장면 전환, 깜빡임을 정확하게 판단하기 어려웠기 때문입니다.
로컬에서 실행한 실제 브라우저를 사용해 마우스 휠과 터치에 가까운 입력으로 스크롤하고, 페이지를 연 직후와 잠시 기다린 뒤의 동작을 각각 확인하도록 했습니다. 화면 크기를 변경하거나 새로고침과 페이지 이동을 실행하는 검사도 같은 환경에서 진행했습니다.
3. 다음 작업에서 달라져야 할 것
1) 공통적인 근본 원인은 ‘비효율적인 실행 구조’
페이지를 처음 만들 때는 필요한 애니메이션과 컴포넌트를 하나씩 추가했습니다. 각 컴포넌트가 개별적으로 작동할 때는 큰 문제가 없어 보였지만, 하나의 페이지에서 동시에 실행되자 서로 같은 요소를 측정하고 위치를 다시 계산하거나 화면을 반복해서 그리기 시작했습니다.
예를 들어, 여러 컴포넌트가 각자 ScrollTrigger.refresh()를 실행했고, 같은 요소의 높이를 측정하는 기능도 두 곳 이상 존재했습니다. 한 기능이 변경한 높이를 다른 기능이 다시 측정하면서 잘못된 값이 누적됐습니다. 화면 밖에 있는 셰이더도 계속 새로운 화면을 그리고 있었습니다.
근본적인 문제는 같은 작업을 여러 곳에서 서로 다른 시점에 실행하는 구조였습니다. 이를 해결하려면 다음 세 가지 원칙에 따라 전체 구조를 먼저 정리해야 했습니다.
| 구조 개선 원칙 | 적용 방법 |
|---|---|
| 같은 작업은 한곳에서 관리 | 화면 높이 측정과 ScrollTrigger.refresh()를 여러 컴포넌트가 각각 실행하지 않도록 통합 |
| 실행 순서를 먼저 결정 | 화면 배치 완료 → ScrollSmoother 준비 → 고정 애니메이션 생성 → 전체 위치 재계산 → 스크롤 위치 복원 순서로 실행 |
| 필요한 순간에만 연산 | 화면 크기가 실제로 바뀌었을 때만 다시 측정하고, 화면 밖의 애니메이션과 셰이더는 정지 |
화면을 다시 측정할 때도 기존 값을 그대로 둔 채 새 값을 계산하지 않아야 합니다. 이전에 적용한 높이와 위치를 먼저 제거하고 자연스러운 레이아웃으로 되돌린 뒤, 화면에 새로운 배치가 반영된 다음 다시 측정해야 합니다. 내용의 높이처럼 자주 바뀌지 않는 값은 매 프레임 다시 계산하지 않고 한 번 측정해 저장해야 합니다. 화면 크기나 글꼴처럼 해당 값에 실제로 영향을 주는 조건이 변경됐을 때만 다시 계산하면 됩니다.
결국 효율적인 구조란 단순히 애니메이션의 수를 줄이는 것이 아니었습니다.
- 같은 계산을 여러 곳에서 반복하지 않고,
- 어떤 기능이 언제 실행될지를 한곳에서 관리하며,
- 사용자가 보지 않는 작업은 멈추는 구조를 만드는 것이었습니다.
2) AI에게는 기능 수정 전에 전체 실행 구조를 확인하도록 지시해야 합니다
다음부터는 AI에게 화면에서 문제가 보이는 부분만 수정하라고 요청하지 않아야 합니다. 먼저 현재 코드에서 같은 연산이 어디에서 반복되고 있으며, 어떤 순서로 실행되는지를 확인하도록 해야 합니다.
AI에게는 다음과 같이 지시할 수 있습니다.
코드를 수정하기 전에 스크롤 생성, 요소 크기 측정,
ScrollTrigger.refresh(), React 상태 변경과 애니메이션 렌더링이 각각 어디에서 실행되는지 확인합니다. 같은 작업이 여러 컴포넌트에서 중복되는지 찾고, 각 작업을 어느 한곳에서 관리할지 먼저 제안합니다. 화면 배치부터 스크롤 위치 복원까지의 실행 순서를 정리한 다음 수정합니다. 수정 후에는 사용자 로컬 브라우저에서 같은 입력과 화면 크기로 다시 검사합니다.
화면 검사는 화면이 보이지 않는 자동화 브라우저가 아니라 사용자 로컬 환경에서 직접 실행한 브라우저를 사용하도록 해야 합니다. 실제 마우스 휠이나 터치 입력으로 스크롤하고, 페이지를 연 직후와 잠시 기다린 뒤의 결과를 각각 확인해야 합니다. 또한, 하나의 세션이 길어지면서 AI가 현재 구조와 이전 수정안을 혼동하기 시작하면 같은 세션에서 지시를 계속 추가하지 않아야 합니다. 현재 적용된 구조, 이미 기각한 방법과 해결할 문제를 정리한 뒤 새로운 세션에서 다시 작업하는 편이 효율적이었습니다.
3) 인간은 구현 목표와 최종 사용 경험을 확인해야 합니다
AI는 smooth 값이나 셰이더 해상도를 조정할 수 있지만, 변경된 결과가 처음 의도했던 경험과 맞는지는 대신 판단할 수 없습니다. 부드러운 움직임과 빠른 반응 중 어느 쪽을 우선할지, 움직이는 배경의 화질과 기기 성능 사이에서 어느 정도를 허용할지는 인간이 결정하고, 결정된 사항을 AI가 작업 중에 염두해 둘 수 있도록 프롬프트로 체계화 합니다. 제가 직접 판단해야 할 것은 개별 코드의 작성 방식보다(만약 체계적인 코드 구조를 짜는 능력이 있다면 예외사항이겠다만) 최종 화면이 의도대로 움직이는지입니다.
