Home 자바스크립트 시작!
Post
Cancel

자바스크립트 시작!

1. 스크롤에 따른 글자 효과 구현(기본원리)

드디어 static한 구조는 다 짰고 동적인 기능을 구현하려고 한다. 기본원리는 아래와 같다.

  1. pageYOffSet을 이용해서 현재 스크롤의 위치를 구함.

  2. 각 scroll-section의 크기 내에서 현재 스크롤한 위치가 어느정도 비율을 차지하는지 구함.

  3. 그 비율이 예를 들어, 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%씩 옮겨주니 가운데 정렬이 되었다.

그럼 아래와 같이 효과가 나타남.

scrollEffect

scrollEffect2

끝!

This post is licensed under CC BY 4.0 by the author.

CSS 시작!

자바스크립트 기본 문법 (변수)