[CSS, JavaScript] 스크롤을 내리면 흔들리는 버튼 만들기

비얌·2023년 1월 8일
24
post-thumbnail

개요

Just JavaScript라는 강의 자료로 자바스크립트를 공부하다가, 어떤 흔들리는 버튼을 마주쳤다. 해당 챕터를 다 읽었는지를 체크할 수 있는 버튼이었는데, 정말 귀엽고 신기했다!!! 그래서 이 버튼을 만들어보기로 했다.



완성된 코드

풀이 과정에는 삽질 과정도 포함하여 길게 쓸 것이기 때문에 완성된 코드를 미리 올리려고 한다.


완성된 css 코드이다.

/* index.css */
.shakeBox {
  animation-duration: 1s;
}

@keyframes shake {
  0% {
    transform: rotate(0deg)
    }
  25% {
      transform: rotate(-4deg);
    }
  50% {
      transform: rotate(4deg);
    }
  75% {
      transform: rotate(-4deg);
    }
  100% {
      transform: rotate(0deg);
    }
}

완성된 자바스크립트 코드이다.

// index.js
let shakeBox = document.querySelector('.shakeBox');

const observer = new IntersectionObserver(
  ([entry]) => {
    shakeBox.style.animationName = entry.isIntersecting ? "shake" : "none";
  },
  {
    rootMargin: "-250px 0px",
    threshold: 1,
  }
);

observer.observe(shakeBox)


풀이 과정

개발자 도구 확인하기

일단 이 버튼 자체를 만들기 위해서 개발자 도구(F12)를 켜서 코드를 확인해봤다.

스크롤을 내려서 버튼이 보이면 잠시 동안 버튼이 흔들린다. 그리고 개발자 도구에서 div의 style이 변화한다.

아래의 gif를 보면 버튼이 움직임에 따라 style의 transform 속성이 변화하는 것을 알 수 있다.


더 자세히 보기 위해 캡쳐를 해봤다. 버튼이 움직이지 않을 때는 transform이 none으로 설정되고, 움직이는 동안에는 transform의 rotateZ(...deg)와 translateZ(...px) 속성이 빠르게 변화한다.

즉, 여기에 있는 코드를 복사해서 가져오고, 붙여넣은 후 style의 transfrom 속성이 적절하게 변화하도록 코드를 짜면 될 것 같다.


개발자 도구 코드 복사 붙여넣기 하기

버튼 자체는 코드 복붙을 하기로 했다. 개발자 도구에서 아래의 코드를 가져와 복사 붙여넣기 해준다.

<div style="transform: none;">
  <button type="button" class="rounded-full inline-flex items-center justify-center px-5 py-3 text-white text-lg  bg-black" role="switch" aria-checked="false">
    <div class="bg-gray-500 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 mr-2">
      <span aria-hidden="true" class="translate-x-0 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
    </div>
    Mark as learned
  </button>
</div>

그런데 이렇게 하고 실행한 후의 결과를 확인해보니 css가 전혀 적용되지 않았다. 코드의 class 부분을 확인해봤을 때, rounded-full라는 클래스명이 있어서 검색해봤다. tailwindcss에서 사용하는 클래스라고 한다. 그래서 tailwindcss를 쓰기 위해 tailwindcss를 설치하기로 했다.


tailwindcss 설치하기

예전에 tailwindcss를 설치하고 정리해둔 것이 있어서 그대로 해봤다. ( +설치 관련 공식 문서)


설치 과정은 아래와 같다.

  1. npm install -D tailwindcss (설치)
  2. npx tailwindcss init (Tailwind config파일 생성)
  3. config 파일의 content 부분 아래와 같이 변경하기
// tailwind.config.js
/ @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx,html}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
  1. 메인 CSS 파일에 다음을 추가해주기. (이번에는 index.css에 추가해줬다)
@tailwind base;
@tailwind components;
@tailwind utilities;

💥 그런데!!!

css가 여전히 적용되지 않는다.. 왜일까? 적용이 안되는 이유를 열심히 찾았으나... 결국 해결하지 못하여 설치하지 않고 <script> 태그 안에 넣어서 불러오기로 했다. 권장하는 방법은 아니라고 한다!!

<script src="https://cdn.tailwindcss.com"></script>를 헤드 태그 안에 넣은 결과, 바로 tailwindcss를 사용할 수 있었다.

아래와 같이 tailwindcss가 성공적으로 적용된 것을 확인할 수 있다!


스크롤이 되도록 버튼 앞뒤로 글자 붙여넣기

아래의 gif를 보면 알 수 있듯이, 스크롤을 내려서 버튼이 보이게 되었을 때 그때서야 버튼이 흔들려야 한다.

그래서 일단 스크롤을 내릴 수 있는 상태를 만들도록 적당히 글자들을 복사 붙여넣기 하여 버튼 앞 뒤에 붙여넣기 했다.


CSS 애니메이션 넣기

이렇게 움직이는 버튼을 자바스크립트로 만들 수도 있지만, CSS 애니메이션으로 만들 수도 있다는 것을 알게되어 CSS 애니메이션으로 만들어보기로 했다.

(일단 스크롤을 생각하지 않고 사용자의 커서(마우스 포인터)가 요소 위에 올라갔을 때 흔들리도록 하는 hover 속성을 사용했다.)

mdn 문서를 찾아보니, CSS 애니메이션 사용하기라는 문서가 있었다.

CSS 애니메이션은 keyframe을 사용하여 만들 수 있는데, keyframe을 사용하여 진행도에 따른 스타일을 지정하면 된다고 한다.

시도 1

개발자 도구에서 transform: rotate()라는 속성을 주었길래 이와 비슷하게 만들어 시도해보았다. 이때 마우스를 올리면(hover) 움직인다고 가정했다. 음.. 뭔가 이상하게 움직인다. 그러니까 흔들리는 모습이 아니라 위아래로 움직이는 모습이다. 흔들리게 만들려면 어떻게 해야 할까? 일단 중심이 이것처럼 오른쪽이 아니라 중앙에 있어야 할 것 같다.

.shakeBox:hover {
  animation: shake 1000ms
}

@keyframes shake {
  0% {transform: rotateZ(0deg);}
  50% {transform: rotateZ(4deg);}
  100% {transform: rotateZ(-4deg);}
}


시도 2

중심축을 가운데에 두면 될 것 같아서, transform-origin으로 속성을 줘봤다. 하지만 이렇게 해도 동작하지 않는다.

.shakeBox:hover {
  animation: shake 1000ms
  transform-origin: center center;
}

@keyframes shake {
  0% {transform: rotateZ(0deg);}
  50% {transform: rotateZ(4deg);}
  100% {transform: rotateZ(-4deg);}
}

시도3(성공!)

왜 안되는지 모르겠어서 하염없이 구글링을 하던 중, 나와 똑같은 케이스로 흔들 버튼을 만드는 방법에 대한 포스팅을 보게 되었다.

.shakeBox:hover {
  animation-name: shake;
  animation-duration: 1s;
}

@keyframes shake {
  0% {
    transform: rotate(0deg)
    }
  25% {
      transform: rotate(-4deg);
    }
  50% {
      transform: rotate(4deg);
    }
  75% {
      transform: rotate(-4deg);
    }
  100% {
      transform: rotate(0deg);
    }
}

그래서 이와 같이 코드를 작성해보았다!! 하지만... 그래도 이전과 결과가 다르지 않았다.


그래서 자세히 살펴보니...!!!!!!! 내가 button이 아니라 button을 감싸는 div 태그에 클래스 이름을 주고 있었다.. 그래서 의도대로 움직이지 않은 것이다😂

<div class='shakeBox'>
  <button></button>
</div>

아래와 같이 div가 아니라 button에 클래스를 지정했더니 의도한 대로 움직인다!! 성공!!

<div>
  <button class='shakeBox'></button>
</div>



스크롤이 버튼이 있는 위치까지 내려오면 움직이게 하기

이전에는 스크롤이 버튼이 있는 위치에 내려왔을 때 흔들리게 하는 법을 몰랐다. 그래서 hover 속성으로 버튼에 마우스를 올렸을 때 버튼을 흔들리게 한 것이다.

이제는 스크롤이 버튼이 있는 위치까지 내려왔을 때 흔들리게 해보자.


자바스크립트를 이용해야 할 것 같은데 잘 모르겠다.

👉 검색해도 잘 모르겠고 너무 어려워보여서, 지인의 도움을 받았다.

IntersectionObserver를 사용해야 한다고 한다.

CSS 코드 수정하기

기존에는 CSS 코드가 아래와 같았다. 일단 hover를 할 때가 아니라 스크롤을 내렸을 때 움직이도록 할 것이기 때문에, hover 속성을 빼준다. 그리고 .sakeBox에서 animation-name를 지정하는 코드도 빼준다.

.shakeBox:hover {
  animation-name: shake;
  animation-duration: 1s;
}

@keyframes shake {
  0% {
    transform: rotate(0deg)
    }
  25% {
      transform: rotate(-4deg);
    }
  50% {
      transform: rotate(4deg);
    }
  75% {
      transform: rotate(-4deg);
    }
  100% {
      transform: rotate(0deg);
    }
}

js 파일에서 상황에 따라 animation-name을 다르게 지정하기

animation-name을 정의해야 애니메이션을 호출할 수 있다.

CSS 파일의 아래와 같은 부분을 호출하려면 animation-name을 지정해야 하기 때문에 shake라는 이름을 정의하여 애니메이션을 호출하고, none이라는 이름을 정의하여 애니메이션이 호출되지 않게 할 것이다.

@keyframes shake {
  0% {
    transform: rotate(0deg)
    }
  25% {
      transform: rotate(-4deg);
    }
  50% {
      transform: rotate(4deg);
    }
  75% {
      transform: rotate(-4deg);
    }
  100% {
      transform: rotate(0deg);
    }
}

그래서 js 파일에서 버튼이 흔들려야 할 때 animation-name을 shake로, 아닐 때 none으로 바꿔줄 것이다. js 파일에 이 두 함수를 추가해 준다.

function startShake() {
  shakeBox.style.animationName = 'shake';
}

function stopShake() {
  shakeBox.style.animationName = 'none';
}

IntersectionObserver 사용하기

이제 IntersectionObserver를 사용하여 스크롤을 내려 버튼이 보이면 흔들리게 할 것이다.

IntersectionObserver에 관한 공식 문서를 읽어보았다.


observer가 엘리먼트를 탐지하기 위해서는 아래와 같이 써줘야 한다고 한다.

// Targeting an element to be observed
// Once you have created the observer, you need to give it a target element to watch:
let target = document.querySelector('#listItem');
observer.observe(target);

그래서 나도 아래와 같이 써줬다.

let shakeBox = document.querySelector('.shakeBox');
observer.observe(shakeBox)

또, 문서에 의하면 내가 써야 할 속성은 바로 isIntersecting이다. isIntersecting은 querySelector로 선택한 엘리먼트인 entry가 화면에 보일 때를 탐지한다.

let callback = (entries, observer) => {
  entries.forEach(entry => {
    // Each entry describes an intersection change for one observed
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

아래와 같이 new IntersectionObserver를 선언하고 그 안에 callback 함수와 options(옵션)을 넣어주어 함수 표현식을 만들어줄 수 있다. 그리고 이를 observer.observe(shakeBox)로 실행시켜줘야 한다.

let observer = new IntersectionObserver(callback, options);

observer.observe(shakeBox)

위를 참고하여 아래와 같이 만들어주었다. isIntersecting로 화면에서 탐지되면 startShake 함수를 실행하고, 화면에서 탐지되지 않으면 stopShake 함수를 실행한다.

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      startShake()
    } else {
      stopShake()
    }
  })
})

observer.observe(shakeBox)

하지만 문제가 있다. 바로, 화면에서 버튼이 보이자마자 흔들리는 것이다. 원본은 화면에서 보이자마자가 아니라, 화면의 중간쯤까지 버튼이 내려와서야 흔들린다. 그래서 옵션을 주기로 했다.

{
	rootMargin: '-250px 0px',
    threshold: 1
}
  • rootMargin에 -250px(세로 - 아래에서 250px이 되었을 때 탐지하도록), 0px(가로 - 가로에서 0px이 되었을 때 탐지하도록) 속성을 준다.
  • threshold으로 0 ~ 1 사이의 값을 지정할 수 있는데, 값으로 1을 준다.(1은 요소가 모두 보일 때를 의미한다)

이렇게 하면 버튼이 화면에 나타나고 스크롤을 어느정도 내려서 중간쯤 왔을 때 버튼이 움직인다!



js 코드 단순화

구조분해할당(entries에서 entry가 하나밖에 없으므로 구조분해 할당으로 하나만 지정할 수 있다)과 삼항 연산자로 아래와 같이 더 단순하게 표현할 수도 있다.

const observer = new IntersectionObserver(
  ([entry]) => {
    shakeBox.style.animationName = entry.isIntersecting ? "shake" : "none";
  },
  {
    rootMargin: "-250px 0px",
    threshold: 1,
  }
);

observer.observe(shakeBox)


결과

웹사이트에서 본 것과 비슷하게 완성하는 것에 성공했다 😆



🐹 회고

많은 도움을 받았지만... 그래도 완성하다니!! 정말 뿌듯하다. 처음 보는 개념을 많이 접해서 신기했다.

CSS로 그저 화면의 칸을 나누고, 색과 테두리 등을 설정하는 것만 알았는데 이렇게 animation을 사용하여 움직이는 요소를 만든 것은 새로운 경험이었다. CSS로 만들 수 있는 것에 대한 시야가 조금은 넓어진 기분이다!!

그리고 스크롤을 내려 사용자가 버튼을 확인할 수 있을 때, 더 나아가 버튼이 화면의 중간쯤 왔을 때를 탐지하여 움직이게 했다는 점이 뿌듯했다. 처음에 IntersectionObserver를 사용해야 한다고 들어서 공식 문서를 확인해봤을 때 절망적이었다😂 설명이 너무 길고 무척 어려워보였기 때문이다. 그래서 사실 이건 구현하지 않고 버튼이 흔들리는 것만 만들려고 했었다. 하지만 주변에서 도움을 받게 되었고, 생각보다 단순하게 구현할 수 있음에 놀랐다. 그래서 어려워보여도 포기하지 말고, 또 질문하는 것이 나에게 무조건 도움이 된다는 생각을 가지고 해결하기로 했다(아직도,, 질문해서 알게된 것은 나에게 도움이 되지 않는다는 생각이 있다😥 이것은 전혀 사실이 아니라는 것을 기억하자!!!)

지나가다가 귀여워보여서 나도 저렇게 만들고 싶다는 단순한 생각으로 한 건데, 얻어간 게 많아서 좋았던 경험이었다. 클론코딩이 생각보다 재미있는 것 같다. 다음에도 이와 같이 클론코딩을 종종 해보고 싶다.

profile
🐹강화하고 싶은 기억을 기록하고 공유하자🐹

0개의 댓글