javascript Intersection observer로 간단한 스크롤 애니메이션 생성해보기

김범기·2024년 4월 12일
0
post-thumbnail

개요

요새는 스크롤 내리면 스르륵 등장하거나 뭐가 옆에서 툭 튀어나오거나 하는 그런 종류의 애니메이션이 많아졌다.
그거 우예함 하고 찾던 나는 저번에도 찾아봤지만, 뭔말이지... 했는데, 이번에도 유튜브의 이끌림으로 강의를 듣게 되었다.
아니 또 코딩애플센세!!!
뭐.. 코딩애플 강의를 듣고 한 번 이것저것 다뤄봤다.

코딩애플: 웹페이지에 스크롤 애니메이션 쉽게 주는 법

나처럼 감도 못잡은 사람은 이 강의를 들어보면 될 듯 하다.
절대 광고 아님.

해보기

내 입맛대로 하다보니 html과 js가 살짝 다르긴하다.

코드는 아래처럼 강의를 했다.

전체 코드

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
</head>
<body>
 <style>
   body{
     background: black;
     /* height: 6000px; */
   }
   div{
     margin-top: 800px;
     margin-bottom: 800px;
     color: white;
     text-align: center;
     opacity: 0;
     transition: all 1s;
   }
 </style>

 <div id="first"><h1>iPhone 15출시</h1></div>
 <div id="second"><h1>충전포트를 USB-C 타입으로 바꿔달라고요?</h1></div>
 <div id="third"><h1>그래서 충전포트를 제거했습니다.</h1></div>
 <div id="fourth"><h1>오 된다</h1></div>
 <div id="fifth"><p>로또1등 당첨되면 바로 맥북max 사야징!!!!!</p></div>

 <script>
   let observer = new IntersectionObserver((e) => {
     // 감시중 박스가 화면에 등장하면 여기 코드 실행해줌.
     e.forEach((박스) => {
       console.log(박스.target)
       if(박스.isIntersecting){
         박스.target.style.opacity = 1;
         박스.target.style.transform = 'rotate(360deg)';
         박스.target.style.transitionDuration = '0.5s';
       } else{
         // 감시중인 박스가 퇴장시 실행할 코드
         박스.target.style.opacity = 0;
       }
       console.log(박스.intersectionRatio)
     })
   })

   let div = document.querySelectorAll('div')
   observer.observe(div[0]) // html요소가 화면에 등장하는지 감시해줌. 괄호안에 html요소 입력하면됨
   observer.observe(div[1])
   observer.observe(div[2])
   observer.observe(div[3])

 </script>
</body>
</html>

javascript 코드

<script>
   let observer = new IntersectionObserver((e) => {
     // 감시중 박스가 화면에 등장하면 여기 코드 실행해줌.
     e.forEach((박스) => {
       console.log(박스.target)
       if(박스.isIntersecting){
         박스.target.style.opacity = 1;
         박스.target.style.transform = 'rotate(360deg)';
         박스.target.style.transitionDuration = '0.5s';
       } else{
         // 감시중인 박스가 퇴장시 실행할 코드
         박스.target.style.opacity = 0;
       }
       console.log(박스.intersectionRatio)
     })
   })

   let div = document.querySelectorAll('div')
   observer.observe(div[0]) // html요소가 화면에 등장하는지 감시해줌. 괄호안에 html요소 입력하면됨
   observer.observe(div[1])
   observer.observe(div[2])
   observer.observe(div[3])
   observer.observe(div[4])

 </script>

여기선 우선 아이디 없이 div태그로만 진행을 했다.

let observer = new IntersectionObserve(()=>{})

이 코드를 통해 내가 감시하려는 녀석이 화면에 나타났을때 {}안에 있는 함수를 진행시킨다.

let div = document.querySelectorAll('div')

document.querySelectorAll('div')를 이용해서 모든 div를 찾아낸다.

그리고 observer변수(즉 IntersectionObserve)의 내장함수인 observe를 이용해서 각 div태그를 발견했을 때 위에서 말한 IntersectionObserve(() => {})의 함수를 실행시키도록 한다.

이렇게 하니 서서히 등장하는 모습을 확인 할 수 있었다.

나 혼자 연습

HTML의 div태그들에 id를 한 번 달아봤다.
그리고 js를 수정해봤다.

html

<div id="first"><h1>iPhone 15출시</h1></div>
  <div id="second"><h1>충전포트를 USB-C 타입으로 바꿔달라고요?</h1></div>
  <div id="third"><h1>그래서 충전포트를 제거했습니다.</h1></div>
  <div id="fourth"><h1>오 된다</h1></div>
  <div id="fifth"><p>로또1등 당첨되면 바로 맥북max 사야징!!!!!</p></div>

js

<script>
    let observer1 = new IntersectionObserver((e) => {
      e.forEach((박스) => {
        if(박스.isIntersecting){
          박스.target.style.opacity = 1;
        } else{
          박스.target.style.opacity = 0;
        }
      })
    })
    let observer2 = new IntersectionObserver((e) => {
      // 감시중 박스가 화면에 등장하면 여기 코드 실행해줌.
      e.forEach((박스) => {
        // console.log(박스.target)
        if(박스.isIntersecting){
          박스.target.style.opacity = 1;
          박스.target.style.transform = 'rotate(360deg)';
          박스.target.style.transitionDuration = '0.5s';
        } else{
          // 감시중인 박스가 퇴장시 실행할 코드
          박스.target.style.opacity = 0;
        }
        // console.log(박스.intersectionRatio)
      })
    })

    let observer3 = new IntersectionObserver((e) => {
      // console.log(e)
      e.forEach((entry)=>{
        // 화면에 등장하면 => isIntersecting === true
        if(entry.isIntersecting){
          entry.target.style.opacity = 1;
          switch (entry.target.id) {
            case 'fifth':
              entry.target.style.transform = 'translateX(30%)';
              break;
          
            default:
              entry.target.style.transform = 'translateX(-30%)';
              break;
          }
        }else{
          entry.target.style.transform = 'translateX(0%)';
          entry.target.style.opacity = 0;
        }
      })
    })

    let div = document.querySelectorAll('div')
    observer1.observe(div[0]) // html요소가 화면에 등장하는지 감시해줌. 괄호안에 html요소 입력하면됨
    observer1.observe(div[1])
    observer2.observe(div[2])
    observer3.observe(div[3])
    observer3.observe(div[4])
  </script>

각 div마다 다르게 표현해보고 싶어서 기존 observer 변수를 observer1, 2 3 로 만들고 시도해봤다.
잘 되는 것이 확인이 되었다.

observer1은 원래 계획대로 스윽 나타나고,
observer2는 360도 회전도 해주고,
observer3는 fourth는 왼쪽으로 fifth는 오른쪽으로 가는 모습을 볼 수 있었다.

다만 여기서 아쉬운 점은

let div = document.querySelectorAll('div')
    observer1.observe(div[0])
    observer1.observe(div[1])
    observer2.observe(div[2])
    observer3.observe(div[3])
    observer3.observe(div[4])

이것을 다르게 할 수는 없을 까이다.
아마 중복되는 애니메이션을 줘야하는 뭔가가 있을 때, 이것은 #아이디명 이 아닌 .클래스명 을 이용해서 찾아야되지 않을까 한다.

아니면 document.querySelecotrAll이 아닌 document.querySelector()를 사용하는 방법도 있겠지.

일단 전체코드

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
  <style>
    body{
      background: black;
    }
    div{
      margin-top: 800px;
      margin-bottom: 800px;
      color: white;
      text-align: center;
      opacity: 0;
      transition: all 1s;
    }
  </style>

  <div id="first"><h1>iPhone 15출시</h1></div>
  <div id="second"><h1>충전포트를 USB-C 타입으로 바꿔달라고요?</h1></div>
  <div id="third"><h1>그래서 충전포트를 제거했습니다.</h1></div>
  <div id="fourth"><h1>오 된다</h1></div>
  <div id="fifth"><p>로또1등 당첨되면 바로 맥북max 사야징!!!!!</p></div>

  <script>
    let observer1 = new IntersectionObserver((e) => {
      e.forEach((박스) => {
        if(박스.isIntersecting){
          박스.target.style.opacity = 1;
        } else{
          박스.target.style.opacity = 0;
        }
      })
    })
    let observer2 = new IntersectionObserver((e) => {
      // 감시중 박스가 화면에 등장하면 여기 코드 실행해줌.
      e.forEach((박스) => {
        // console.log(박스.target)
        if(박스.isIntersecting){
          박스.target.style.opacity = 1;
          박스.target.style.transform = 'rotate(360deg)';
          박스.target.style.transitionDuration = '0.5s';
        } else{
          // 감시중인 박스가 퇴장시 실행할 코드
          박스.target.style.opacity = 0;
        }
        // console.log(박스.intersectionRatio)
      })
    })

    let observer3 = new IntersectionObserver((e) => {
      // console.log(e)
      e.forEach((entry)=>{
        // 화면에 등장하면 => isIntersecting === true
        if(entry.isIntersecting){
          entry.target.style.opacity = 1;
          switch (entry.target.id) {
            case 'fifth':
              entry.target.style.transform = 'translateX(30%)';
              break;
          
            default:
              entry.target.style.transform = 'translateX(-30%)';
              break;
          }
        }else{
          entry.target.style.transform = 'translateX(0%)';
          entry.target.style.opacity = 0;
        }
      })
    })

    let div = document.querySelectorAll('div')
    observer1.observe(div[0]) // html요소가 화면에 등장하는지 감시해줌. 괄호안에 html요소 입력하면됨
    observer1.observe(div[1])
    observer2.observe(div[2])
    observer3.observe(div[3])
    observer3.observe(div[4])
  </script>

</body>
</html>

도와줘요~ chatGPT

우리의 친구 gpt한테 한 번 물어봤다.
js 코드를 아래처럼 바꿔보란다.

<script>
  // 공통 옵저버 콜백 함수
  const observerCallback = (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const targetId = entry.target.id;
        entry.target.style.opacity = 1;
        
        switch (targetId) {
          case 'third':
            // 'third'에 대한 특별한 애니메이션
            entry.target.style.transform = 'rotate(360deg)';
            entry.target.style.transition = 'opacity 1s, transform 0.5s';
            break;
          case 'fourth':
          case 'fifth':
            // 'fourth'와 'fifth'에 대한 특별한 애니메이션
            const translateXValue = targetId === 'fifth' ? '30%' : '-30%';
            entry.target.style.transform = `translateX(${translateXValue})`;
            entry.target.style.transition = 'opacity 1s, transform 1s';
            break;
          default:
            // 기본 애니메이션
            entry.target.style.transition = 'opacity 1s';
            break;
        }
      } else {
        // 요소가 뷰포트에서 벗어났을 때 초기 상태로 복귀
        entry.target.style.opacity = 0;
        entry.target.style.transform = 'translateX(0%)'; // 초기 위치로 복귀
        // 'third'에 적용된 회전 초기화
        if (entry.target.id === 'third') {
          entry.target.style.transform = 'rotate(0deg)';
        }
      }
    });
  };

  // 옵저버 인스턴스 생성
  const observer = new IntersectionObserver(observerCallback);

  // 모든 'div' 요소에 대해 옵저버 등록
  document.querySelectorAll('div').forEach((div) => {
    observer.observe(div);
  });
</script>

이렇게 하니 내가 만든 코드 처럼 작동을 해준다.

observerCallback이라는 함수를 생성했는데,
기존 IntersectionObserver((() => {}) 대신에
IntersectionObserver(observerCallback)처럼 사용을 하는 모습이다.

그리고 내가 observer 1, 2, 3를 나눈 것을 하나로 합치고, 그 안에서 switch-case문을 이용해서 각각에 맞는 상황을 만들어줬다. 만약 더 자세하게 하고 싶다면,

if (entry.isIntersecting){
  switch(){
    case:
  }
}else{}

만 하는게 아닌

if (entry.isIntersecting){
  switch(){
    case:
  }
}else{
  switch(){
    case:
  }
}

로 사용하면 되지 않을까 싶다.

아직 초보인 나에겐 이렇게 하는게 더 나은 선택인 듯 하다.

이런 것도 알게 되어서 너무 씐나는군.

gpt를 통한 전체코드

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
  <style>
    body{
      background: black;
    }
    div{
      margin-top: 800px;
      margin-bottom: 800px;
      color: white;
      text-align: center;
      opacity: 0;
      transition: all 1s;
    }
  </style>

  <div id="first"><h1>iPhone 15출시</h1></div>
  <div id="second"><h1>충전포트를 USB-C 타입으로 바꿔달라고요?</h1></div>
  <div id="third"><h1>그래서 충전포트를 제거했습니다.</h1></div>
  <div id="fourth"><h1>오 된다</h1></div>
  <div id="fifth"><p>로또1등 당첨되면 바로 맥북max 사야징!!!!!</p></div>

  <script>
    // 공통 옵저버 콜백 함수
    const observerCallback = (entries, observer) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const targetId = entry.target.id;
          entry.target.style.opacity = 1;
          
          switch (targetId) {
            case 'third':
              // 'third'에 대한 특별한 애니메이션
              entry.target.style.transform = 'rotate(360deg)';
              entry.target.style.transition = 'opacity 1s, transform 0.5s';
              break;
            case 'fourth':
            case 'fifth':
              // 'fourth'와 'fifth'에 대한 특별한 애니메이션
              const translateXValue = targetId === 'fifth' ? '30%' : '-30%';
              entry.target.style.transform = `translateX(${translateXValue})`;
              entry.target.style.transition = 'opacity 1s, transform 1s';
              break;
            default:
              // 기본 애니메이션
              entry.target.style.transition = 'opacity 1s';
              break;
          }
        } else {
          // 요소가 뷰포트에서 벗어났을 때 초기 상태로 복귀
          entry.target.style.opacity = 0;
          entry.target.style.transform = 'translateX(0%)'; // 초기 위치로 복귀
          // 'third'에 적용된 회전 초기화
          if (entry.target.id === 'third') {
            entry.target.style.transform = 'rotate(0deg)';
          }
        }
      });
    };
  
    // 옵저버 인스턴스 생성
    const observer = new IntersectionObserver(observerCallback);
  
    // 모든 'div' 요소에 대해 옵저버 등록
    document.querySelectorAll('div').forEach((div) => {
      observer.observe(div);
    });
  </script>
  

</body>
</html>

profile
반드시 결승점을 통과하는 개발자

0개의 댓글