본문 바로가기

UI 디자인 애니메이션 연구소

HTML,CSS,JS + GSAP 로 만드는 버블 어택 애니메이션

반응형

버블 어택 애니메이션이란?

여름철 물 대신에 거품이 나오는 장난감 총을 보고 영감을 받아서 제작한 애니메이션 입니다. 그래서 이름도 버블 어택 애니메이션이죠.

 

완성하게 된다면, 하단 영상처럼 거대한 장난감총이 마우스의 위치에 따라 각도를 조절하고, 클릭한 위치에 무수히 많은 거품이 날아가는 애니메이션을 볼 수 있습니다.

 

 

 

HTML

svg 는 커서 입니다.  

<div class="gun"></div>
<div class="cursor">
  <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <circle cx="50" cy="50" r="48" fill="none" stroke="white" stroke-width="1" opacity="0.3" />
    <circle cx="50" cy="50" r="40" fill="none" stroke="white" stroke-width="1" opacity="0.5" />
    <line x1="0" y1="50" x2="100" y2="50" stroke="white" stroke-width="1" />
    <line x1="50" y1="0" x2="50" y2="100" stroke="white" stroke-width="1" />
    <circle cx="50" cy="50" r="2" fill="white" />
    <path d="M50 15 L53 25 L47 25 Z" fill="white" />
    <path d="M50 85 L53 75 L47 75 Z" fill="white" />
    <path d="M15 50 L25 53 L25 47 Z" fill="white" />
    <path d="M85 50 L75 53 L75 47 Z" fill="white" />
  </svg>

</div>

 

CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  position: relative;
  overflow: hidden;
  background: #333;
  height: 100vh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gun {
  position: absolute;
  border: none;
  background: #f3f3f3;
  font-size: 1.25em;
  left: -150px;
  top: 50%;
  border-top-right-radius: 50%;
  border-bottom-right-radius: 50%;
  transform: translate(0, -50%);
  padding: 10px;
  width: 400px;
  height: 95%;
  z-index: 1;
  box-shadow: inset -20px -80px 50px rgba(0, 0, 0, 0.4),
    inset 20px 80px 50px rgba(255, 255, 255, 0.4);
}

.gun::before {
  content: "";
  position: absolute;
  border-radius: 20%;
  top: 50%;
  right: 0;
  transform: translate(0, -50%);
  background: black;
  width: 50px;
  height: 90%;
}

.bubble {
  position: absolute;
  left: 0;
  z-index: 1;
  z-index: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.3);
  box-shadow: inset -15px -15px 10px 0 rgba(0, 0, 0, 0.3);
  height: 2px;
  transform-origin: 0% 50%;
}

/* 커서 */

.cursor {
  position: fixed;
  pointer-events: none;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  animation: 12s infinite rotation linear;
  transform-origin: 0% 0%;
}

.cursor svg {
  width: 50px;
  height: 50px;
}

@keyframes rotation {
  0% {
    transform: rotate(0deg) translate(-50%, -50%);
  }
  100% {
    transform: rotate(360deg) translate(-50%, -50%);
  }
}

 

JS

const container = document.querySelector("body");
const gun = document.querySelector(".gun");
const cursor = document.querySelector(".cursor");
const bubbles = Array.from({ length: 30 });

container.addEventListener("mousemove", (e) => {
  moveGunUpDown(e);
  cursorMove(e);
});
container.addEventListener("click", animate);

function animate(e) {
  const clientY = e.clientY;

  // 거품 생성
  bubbles.forEach(() => {
    const bubble = document.createElement("div");
    const randomSize = Math.random() * 150;
    const randomY = Math.random() * 500 - 250;
    const randomX = Math.random() * 500 - 250;
    const randomDuration = Math.random() * 5 + 5;

    bubble.classList.add("bubble");
    bubble.style.transform = `translate(${randomX * 2}px, ${randomY * 1.5}px)`;

    // 거품 추가
    container.append(bubble);
    gsap.set(bubble, {
      width: `${randomSize}`,
      height: `${randomSize}`,
      rotate: () => {
        const isTop = -(clientY - window.innerHeight / 2) > 0;
        if (isTop) return -5;
        else if (
          -(clientY - window.innerHeight / 2) > -200 &&
          -(clientY - window.innerHeight / 2) < 200
        )
          return 0;
        else return 5;
      }
    });

    gsap.to(bubble, {
      duration: randomDuration,
      x: 3000,
      y: () => {
        return clientY - window.innerHeight / 2;
      }
    });

    // 2초 뒤 거품 삭제
    setTimeout(() => {
      container.removeChild(bubble);
    }, 6000);
  });
}

// 총 업 다운
function moveGunUpDown(e) {
  const clientY = e.clientY;

  gsap.to(gun, {
    rotate: () => {
      console.log((clientY / window.innerHeight) * 10 - 5);
      return (clientY / window.innerHeight) * 10 - 5;
    }
  });
}

// 커서 무브
function cursorMove(e) {
  cursor.style.left = e.clientX + "px";
  cursor.style.top = e.clientY + "px";
}

 

 

구현 결과

현재 거품은 한 번에 사라지지만 setTimeout이 동작하는 시간대도 랜덤하다면 어떻게 될까요? 그리고 그 타이밍에 거품이 터지는 효과를 넣는다면? 코드 자체가 복잡한 것은 없으니 참고하셔서 더 재밌는 효과를 만들어보면 좋은 체험이 될거라 생각합니다.

 

See the Pen BubbleAttack by youngwan2 (@youngwan2) on CodePen.

0.25 배율로 보시는 걸 추천합니다.

반응형