1. 스크롤에 따른 글자 효과 구현(기본원리)
드디어 static한 구조는 다 짰고 동적인 기능을 구현하려고 한다. 기본원리는 아래와 같다.
pageYOffSet을 이용해서 현재 스크롤의 위치를 구함.
각 scroll-section의 크기 내에서 현재 스크롤한 위치가 어느정도 비율을 차지하는지 구함.
그 비율이 예를 들어, scroll-section-0 안에서 이 section의 길이의 0.1 ~ 0.2 구간을 지나고 있으면
sticky-elem .main-message.a
을 표시함.
2. 새로배운 접근법
관건은 스크롤이 얼마나 되었는지 그 비율을 정확히 구하는것이다. 그리고 캔버스의 크기도 줄어든 크기에 맞추어서 정확히 계산하는 것이다.
- 전역변수가 다른곳에 쓰여 혼란이 일어나는것을 방지하기 위해 익명함수 안에다가 변수를 선언함. 이러한 표현을 즉시실행함수(Immediately Invoked Function Expression) 라고 함.
1
2
3
4
(() => {
const a = 0;
function blahblah() {}
})();
- 각 scroll-section-숫자 에 관련된 정보를 array(sceneInfo)에 담은다음 활용 함(json형태로)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const sceneInfo = [
{
// 0
type: "sticky",
heightNum: 5, // view port의 5배의 높이로 스크롤 설정
scrollHeight: 0,
objs: {
// 작업할 DOM 객체를 따로 보관
// 그리고 .a를 .main-message와 붙여서 표기해야 인식함
container: document.querySelector("#scroll-section-0"),
},
// 각 sticky message에 따른 css값을 values에서 설정
},
{
// 1
type: "normal",
// heightNum: 5, // type normal 에서는 필요없음. 스크롤 효과가 없는 section이니깐.
scrollHeight: 0,
objs: {
container: document.querySelector("#scroll-section-1"),
},
},
{
// 2
type: "sticky",
heightNum: 5, // 브라우저의 5배의 높이로 스크롤 설정
scrollHeight: 0,
objs: {
container: document.querySelector("#scroll-section-2"),
},
},
{
// 3
type: "sticky",
heightNum: 5, // 브라우저의 5배의 높이로 스크롤 설정
scrollHeight: 0,
objs: {
container: document.querySelector("#scroll-section-3"),
},
},
];
각각의 콘텐츠의(사진,글귀 등등) 크기가 나타나기 전에 사이즈를 조절하는 함수를 만듬(setLayOut). 그리고 이 함수가 browser로드하기 전에, 그리고 창의 크기를 조절할때마다 실행되도록 함으로써 반응형으로 만듬.(load,resize)
지금 현재 스크롤하고 있는 위치가 어느 section인지 판별하는 법: 예를들어, scroll-section-2에 도달했다는 것은
scroll-section-0높이 + scroll-section-1높이 < Yoffset(스크롤한 전체 px)
라는 뜻이다. 이런식으로 계산하면 됨.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function scrollLoop() {
// currentScene을 설정한다.
// 0으로 초기화 시켜주지 않으면 끊임없이 누적된다.
enterNewScene = false;
prevScrollHeight = 0;
for (let i = 0; i < currentScene; i++) {
prevScrollHeight += sceneInfo[i].scrollHeight;
}
// 버그
// prevScrollHeight += 를 해주지 않아서 prevScrollHeight의 크기가 맨 마지막 section의 크기만 계산됨.
// 그럼 마지막 currentScene의 크기(대략 3000px) + 그 이전의 scene의 크기(3000px) 이니깐
// 이것보다 스크롤된 y축이 더 커지는 순간 currentScene이 계속 늘어남.
// 그럼 sceneInfo에서 out of bound가 되면서 scrollHeight 가 not defined라는 에러가 뜨는 것임.
if (yOffSet > prevScrollHeight + sceneInfo[currentScene].scrollHeight) {
enterNewScene = true;
currentScene++;
document.body.setAttribute("id", `show-scene-${currentScene}`);
}
if (yOffSet < prevScrollHeight) {
// 브라우저 바운스 효과로 인해 currentScene이 마이너스가 되는것을 방지
if (currentScene === 0) return;
enterNewScene = true;
currentScene--;
document.body.setAttribute("id", `show-scene-${currentScene}`);
}
// 이렇게 하면 새로운 씬으로 접근할때 순간적으로 playAnimation이 실행되기 전에 return함으로써
// opacity가 마이너스가 되기 전에 종료해버림
if (enterNewScene) return;
playAnimation();
}
이벤트리스너에서 DOMContentLoaded 랑 load랑 차이는 DOM은 말그대로 HTML구조만 다 rendering하면 실행되는거고 load는 안에 이미지, 영상파일까지 다 setting한 후에 실행되는거다. 그래서 여기선 load가 더 적합하다.
코드가 길어지는 걸 방지하기 위해 길어진 코드중에 자주 사용되는것은 변수에 저장해둠
특정 범위 내에서 스크롤의 비율에 따라 값이 출력되도록 계산(calcValues()-rv) – 크… 예술
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 현재 scene에서 얼마나 스크롤 되었는지 비율을 계산
function calcValues(values, currentYOffset) {
let rv;
const scrollHeight = sceneInfo[currentScene].scrollHeight;
const scrollRatio = currentYOffset / scrollHeight;
if (values.length === 3) {
const partScrollStart = values[2].start * scrollHeight;
const partScrollEnd = values[2].end * scrollHeight;
const partScrollHeight = partScrollEnd - partScrollStart;
// 현재 스크롤의 위치를 비율로 나타냄.
// 여기도 후속사용의 용의성을 위해서 따로 변수화 시켜줬음. 크...
// 특정 범위 내에서 스크롤이 움직였을 때(messageA같은경우 첫번째 씬의 0.1%~0.2% 사이) opacity의 값을 구함(keyFrame)
if (currentYOffset >= partScrollStart && currentYOffset <= partScrollEnd) {
rv =
((currentYOffset - partScrollStart) / partScrollHeight) *
(values[1] - values[0]) +
values[0];
} else if (currentYOffset < partScrollStart) {
rv = values[0];
} else if (currentYOffset > partScrollEnd) {
rv = values[1];
}
} else {
// 이건 조금 이해하기 힘들 수 도 있는데, 예를 들어 scrollRatio가 0 ~ 1사이를 움직인다고 했을때
// scrollRatio * 300이라고 하면 1 ~ 300사이의 숫자가 출력
// 그럼 values의 첫번째 값 ~ values의 마지막 값 까지 스크롤값에 따라 출력하게 하려면
// 아래와 같이 rv를 설정해야함.
rv = scrollRatio * (values[1] - values[0]) + values[0];
}
// console.log(rv);
return rv;
}
- 그리고 playAnimation에서 scrollRatio의 값에 따라 메세지가 출력되는 특정 위치범위를 선정하고(switch/currentScene을 이용해서) 그 범위안에 scroll이 들어오면 calcValues를 이용해 각각의 메세지의 투명도와 y축의 수치에 변화를 줌
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
function playAnimation() {
// 이렇게 변수를 설정해주면 훨씬 깔끔하게 후속 사용가능.
const objs = sceneInfo[currentScene].objs;
const values = sceneInfo[currentScene].values;
// currentYOffset이 마이너스가 되는 이유는 prev > yOffSet 이기 때문.
// 페이지가 넘어갈 때 이렇게 된다는 것인데 그 이유는?
const currentYOffset = yOffSet - prevScrollHeight;
const scrollHeight = sceneInfo[currentScene].scrollHeight;
const scrollRatio = currentYOffset / scrollHeight;
switch (currentScene) {
case 0:
// messageA
if (scrollRatio < 0.22) {
// IN
objs.messageA.style.opacity = calcValues(
values.messageA_opacity_in,
currentYOffset
);
objs.messageA.style.transform = `translate3d(0,${calcValues(
values.messageA_translateY_in,
currentYOffset
)}%,0)`;
} else {
// OUT
objs.messageA.style.opacity = calcValues(
values.messageA_opacity_out,
currentYOffset
);
objs.messageA.style.transform = `translate3d(0,${calcValues(
values.messageA_translateY_out,
currentYOffset
)}%,0)`;
}
// messageB
if (scrollRatio < 0.42) {
// IN
objs.messageB.style.opacity = calcValues(
values.messageB_opacity_in,
currentYOffset
);
objs.messageB.style.transform = `translate3d(0,${calcValues(
values.messageB_translateY_in,
currentYOffset
)}%,0)`;
} else {
// OUT
objs.messageB.style.opacity = calcValues(
values.messageB_opacity_out,
currentYOffset
);
objs.messageB.style.transform = `translate3d(0,${calcValues(
values.messageB_translateY_out,
currentYOffset
)}%,0)`;
}
// messageC
if (scrollRatio < 0.62) {
// IN
objs.messageC.style.opacity = calcValues(
values.messageC_opacity_in,
currentYOffset
);
objs.messageC.style.transform = `translate3d(0,${calcValues(
values.messageC_translateY_in,
currentYOffset
)}%,0)`;
} else {
// OUT
objs.messageC.style.opacity = calcValues(
values.messageC_opacity_out,
currentYOffset
);
objs.messageC.style.transform = `translate3d(0,${calcValues(
values.messageC_translateY_out,
currentYOffset
)}%,0)`;
}
// messageD
if (scrollRatio < 0.82) {
// IN
objs.messageD.style.opacity = calcValues(
values.messageD_opacity_in,
currentYOffset
);
objs.messageD.style.transform = `translate3d(0,${calcValues(
values.messageD_translateY_in,
currentYOffset
)}%,0)`;
} else {
// OUT
objs.messageD.style.opacity = calcValues(
values.messageD_opacity_out,
currentYOffset
);
objs.messageD.style.transform = `translate3d(0,${calcValues(
values.messageD_translateY_out,
currentYOffset
)}%,0)`;
}
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
}
- 첫번째 canvas 크기를 조절할 때 css에서 일단 맨위 맨왼쪽에서 시작점을 잡아줌. canvas크기의 50%씩 위치를 세팅. 그리고 다시 javascript의 translate3d를 이용해서 x,y를 50%씩 옮겨주니 가운데 정렬이 되었다.
그럼 아래와 같이 효과가 나타남.
끝!