375는 최대 변경 가능한 범위이고, progress는 그 범위 내에서 얼마나 진행되었는지를 나타내는 비율입니다. 이 개념을 수학적 원리부터 브라우저 내부 동작까지 완전히 분석해드리겠습니다.
실제_변화량 = 최대_변화_범위 × 진행_비율
실제_변화량 = 375 × progress
현재_값 = 시작_값 - 실제_변화량
현재_값 = 400 - (375 × progress)
// progress = 0.3 (30% 진행)일 때:
1단계: 실제_변화량 = 375 × 0.3 = 112.5
2단계: 현재_값 = 400 - 112.5 = 287.5px
// 해석: "최대 375만큼 변할 수 있는데, 30%만큼 진행되어서 112.5만큼 변했다"
시작점: 400px (완전한 원)
끝점: 25px (둥근 사각형)
범위: 400 - 25 = 375px (이동할 수 있는 최대 거리)
// 375 = "변화의 전체 스펙트럼"
// 온도계 범위 설정
최고_온도 = 40도 (여름 최고기온)
최저_온도 = 5도 (겨울 최저기온)
온도_범위 = 40 - 5 = 35도 (변화 가능한 범위)
// 현재 온도 계산
진행률 = 0.6 (60% 지점)
실제_온도_하락 = 35 × 0.6 = 21도
현재_온도 = 40 - 21 = 19도
progress = (현재_위치 - 시작_위치) / (끝_위치 - 시작_위치)
progress = (scrollY - startPoint) / (endPoint - startPoint)
// progress는 항상 0~1 사이의 값
// 0 = 시작점 (0%)
// 0.5 = 중간점 (50%)
// 1 = 끝점 (100%)
// 스크롤 이벤트마다 실행되는 계산
function calculateProgress() {
const scrollY = window.pageYOffset; // 현재: 1500px
const triggerStart = 0; // 시작: 0px
const triggerEnd = 3000; // 끝: 3000px (300vh)
const progress = (scrollY - triggerStart) / (triggerEnd - triggerStart);
const progress = (1500 - 0) / (3000 - 0);
const progress = 1500 / 3000 = 0.5; // 50% 진행
return progress;
}
// 각 스크롤 위치에서의 완전한 계산 과정
스크롤_0px (진행률 0%):
├── progress = 0 / 3000 = 0.0
├── 실제_변화량 = 375 × 0.0 = 0
└── borderRadius = 400 - 0 = 400px (완전한 원)
스크롤_750px (진행률 25%):
├── progress = 750 / 3000 = 0.25
├── 실제_변화량 = 375 × 0.25 = 93.75
└── borderRadius = 400 - 93.75 = 306.25px
스크롤_1500px (진행률 50%):
├── progress = 1500 / 3000 = 0.5
├── 실제_변화량 = 375 × 0.5 = 187.5
└── borderRadius = 400 - 187.5 = 212.5px
스크롤_3000px (진행률 100%):
├── progress = 3000 / 3000 = 1.0
├── 실제_변화량 = 375 × 1.0 = 375
└── borderRadius = 400 - 375 = 25px (둥근 사각형)
// 길이 375mm인 슬라이더가 있다고 상상
┌─────────────────────────────────────┐
│ 0mm 93.75mm 187.5mm 375mm │ ← 슬라이더 눈금
│ 0% 25% 50% 100% │ ← 진행률
│ 400px 306px 212px 25px │ ← 실제 borderRadius
└─────────────────────────────────────┘
// 슬라이더 핸들의 위치 = progress × 375
// borderRadius = 400 - 슬라이더_위치
범위(375) ──┐
│ × (곱셈)
진행률(0.5)──┘
│
▼
실제변화량(187.5)
│
│ - (빼기)
시작값(400)──┘
│
▼
현재값(212.5)
// 목표: A범위[a1, a2]를 B범위[b1, b2]로 매핑
// 1단계: 정규화 (0~1 범위로)
normalized = (input - a1) / (a2 - a1)
// 2단계: 목표 범위로 확장
output = b1 + normalized * (b2 - b1)
// 3단계: 통합 공식
output = b1 + ((input - a1) / (a2 - a1)) * (b2 - b1)
// 스크롤 범위 [0, 3000px]를 borderRadius 범위 [400px, 25px]로 매핑
borderRadius = 400 + ((scrollY - 0) / (3000 - 0)) * (25 - 400)
borderRadius = 400 + (scrollY / 3000) * (-375)
borderRadius = 400 - (scrollY / 3000) * 375
borderRadius = 400 - progress * 375
// progress = scrollY / 3000 (정규화된 비율)
// 375 = |25 - 400| (변화 범위)
// 60FPS로 실행되는 실제 계산
function onAnimationFrame() {
// 1단계: 현재 스크롤 위치 측정
const scrollY = window.pageYOffset;
// 2단계: 정규화된 진행률 계산
const progress = Math.max(0, Math.min(1, scrollY / 3000));
// 3단계: 범위 내 실제 변화량 계산
const actualChange = MAX_CHANGE_RANGE * progress;
const actualChange = 375 * progress;
// 4단계: 최종값 계산 및 적용
const currentRadius = START_VALUE - actualChange;
const currentRadius = 400 - actualChange;
// 5단계: GPU로 렌더링
element.style.borderRadius = currentRadius + 'px';
}
// RAF 루프에 등록
gsap.ticker.add(onAnimationFrame);
// 0.5에서 1.0으로 증가 (범위: 0.5)
const SCALE_RANGE = 1.0 - 0.5; // 0.5
scale = 0.5 + progress * SCALE_RANGE;
scale = 0.5 + progress * 0.5;
// progress = 0.6일 때:
// 실제_변화량 = 0.5 × 0.6 = 0.3
// scale = 0.5 + 0.3 = 0.8
// 1.0에서 0.0으로 감소 (범위: 1.0)
const OPACITY_RANGE = 1.0 - 0.0; // 1.0
opacity = 1.0 - progress * OPACITY_RANGE;
opacity = 1.0 - progress * 1.0;
// progress = 0.3일 때:
// 실제_변화량 = 1.0 × 0.3 = 0.3
// opacity = 1.0 - 0.3 = 0.7
function createRangeMapper(startValue, endValue) {
const range = Math.abs(endValue - startValue);
const isReverse = endValue < startValue;
return function(progress) {
const actualChange = range * progress;
if (isReverse) {
return startValue - actualChange; // 감소형
} else {
return startValue + actualChange; // 증가형
}
};
}
// 사용 예시
const getBorderRadius = createRangeMapper(400, 25); // 역방향
const getScale = createRangeMapper(0.5, 1.0); // 정방향
const getOpacity = createRangeMapper(1.0, 0.0); // 역방향
// 실행
const currentRadius = getBorderRadius(0.4); // 400 - (375 × 0.4) = 250
const currentScale = getScale(0.4); // 0.5 + (0.5 × 0.4) = 0.7
const currentOpacity = getOpacity(0.4); // 1.0 - (1.0 × 0.4) = 0.6
1️⃣ 범위 설정: "375 = 변할 수 있는 최대 거리"
2️⃣ 비율 측정: "progress = 현재 얼마나 진행되었나 (0~1)"
3️⃣ 실제 적용: "375 × progress = 지금 실제로 변한 양"
"400에서 25까지 총 375만큼 변할 수 있어.
지금 50% 진행됐으니까, 375의 50%인 187.5만큼 변했어.
그러니까 400에서 187.5를 빼면 212.5가 현재값이야."
borderRadius = 400 - progress * 375
// 직역: "시작값에서 (비율 × 범위)만큼 뺀다"
// 의역: "전체 변화 중에서 현재 진행된 만큼만 적용한다"
마스터 레벨 테스트:
progress = 0.7일 때 borderRadius는?
실제변화량 = 375 × 0.7 = 262.5
borderRadius = 400 - 262.5 = 137.5px
새로운 범위 [100, 20]에서 progress = 0.4일 때는?
범위 = 100 - 20 = 80
실제변화량 = 80 × 0.4 = 32
현재값 = 100 - 32 = 68
증가형 [0.2, 0.8]에서 progress = 0.6일 때는?
범위 = 0.8 - 0.2 = 0.6
실제변화량 = 0.6 × 0.6 = 0.36
현재값 = 0.2 + 0.36 = 0.56
🎉 축하합니다! 이제 범위 × 비율 = 실제변화량의 핵심을 완전히 마스터하셨습니다!