원형 회전 날씨 카드 배너 만들기 - 원형 회전 카드 자바스크립트로 구현하기

ChoiYongHyeun·2024년 1월 17일
2

망가뜨린 장난감들

목록 보기
7/19
post-thumbnail

제작 이유

여태 가로로 뱅글 뱅글 돌아가는 토이프로젝트들을 실습했었다.

무한 슬라이드 같은 것들도 유튜브에서 많이 보았는데 대부분의 원리가 translateX 스타일을 건드려서 이동하는 거더라

그래서 슬라이드 관련해서 한 번 또 뚝딱 거려봐야지 .. 그랬는데 갑자기 번뜩 이런 생각이 들었다.

삥글뺑글 회전문처럼 돌려보자

아이디어 스케치

그래서 우선 느낌은 다음처럼 생긴 카드 배너들을 만들고 이벤트에 따라서 카드 배너들이 빙글뱅글 돌아가게 만들고 싶었다.

그래서 우선 카드들의 위치를 원점으로부터 translateX , translateY 를 이용해서 이동시켜주면 될 것 같았다.

그 다음에 생각 할 것은 카드들이 원형으로 돌아가있다는 것을 표현 하도록 카드들을 돌려야 했다.

느낌 상 카드의 Y 축을 기준으로 해서 돌려야 할 것 같은데 어떻게 할까 고민하다가

다음 같은 규칙으로 돌려주면 앞면 뒷면까지 구분 할 수 있을 것 같았다.

그래서 해봤다

ㅋㅋ 아 굿 ~~

원근감 표현을 위해서 각 카드 별로 scale 값을 조금씩 만져주었다.

그럼 이제 어떻게 쟤네들을 돌릴지 생각해봐야 한다.


JavaScript

삥글뺑글 기능 구현

html , css 부분 구조를 먼저 올리지 않는 이유는 안에 내용물을 나중에 바꿀 예정이기 때문이다.
그래도 혹시 궁금한 사람이 있을 수 있으니 가장 하단에 올려두도록 하겠다 !

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <button id="left"><</button>
    <button id="right">></button>

    <div class="container">
      <div id="first-card" class="card">1</div>
      <div id="second-card" class="card">2</div>
      <div id="third-card" class="card">3</div>
      <div id="fourth-card" class="card">4</div>
      <div id="fifth-card" class="card">5</div>
      <div id="sixth-card" class="card">6</div>
    </div>
  </body>
  <script src="script.js"></script>
</html>

우선 배치는 container 라는 div 태그 안에 card 별로 id 값을 지정해주었다.

이후 id 값 별로 원점으로부터 이동 할 거리와 회전 시킬 각도를 CSS 에서 설정해주었다.

class RotateCards {
  constructor(cards, cardSequnce) {
    this.cards = cards;
    this.cardSequnce = cardSequnce;
  }

  shuffle() {
    this.cards.forEach((item, index) => {
      const card = item;
      card.id = this.cardSequnce[index];
    });
  }

  moveLeft() {
    const value = this.cardSequnce.pop();
    this.cardSequnce.unshift(value);

    this.shuffle();
  }

  moveRight() {
    const value = this.cardSequnce.shift();
    this.cardSequnce.push(value);
    this.shuffle();
  }
}

const cards = document.querySelectorAll('.card');
const cardSequnce = [
  'first-card',
  'second-card',
  'third-card',
  'fourth-card',
  'fifth-card',
  'sixth-card',
];
const leftButton = document.querySelector('#left');
const rightButton = document.querySelector('#right');

const rotateCards = new RotateCards([...cards], cardSequnce);

leftButton.addEventListener('click', () => {
  rotateCards.moveLeft();
});

rightButton.addEventListener('click', () => {
  rotateCards.moveRight();
});

생각보다 카드를 돌리는 것은 난이도가 어렵지 않았다.

그냥 id 명을 배열에서 넣어놓고 왼쪽, 오른쪽 액션에 따라 배열의 값들을 한 칸씩 밀어주거나 땡겨주면 됐다.

이후 카드들의 id 값을 배열에 맞춰 설정해주었다.

잘 돌아간다 .

근데 너무 잘 돌아간다.

가장 맨 뒤에 존재하는 카드 부분에서 문제가 발생한다.

12시 방향에서 이동 할 때 트리플 악셀을 하고 난리가 난다.

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 왜 ?

사과만 마저 먹고 이유를 생각해봐야지 ..


트리플 악셀 오류 해결하기

원인을 찾았다. 그건 바로 기존 rotateY 값 설정을 + , - 를 혼용해서 써서 그랬다.

rotate 에서 + , - 는 시계방향과 반시계 방향을 의미한다.

그러니까 rotateY(45deg)rotateY(-315deg) 와 같은 결과값을 보이지만

회전하는 방향이 반대이다.

나는 12시 방향에 있는 카드는 시계 방향으로 180deg 회전인데 10시 방향에 있는 카드는 반시계 방향으로 135deg 회전 되어있다.

그로 인해 12시 방향에서 반시계 방향이 되는 부분으로 갈 때 반시계 방향으로 우선 180도 돈 후, 또 반시계 방향으로 135도를 도니까 저렇게 삥글 뺑글 도는 것이다.

그러니 회전축을 하나로 통일해줬다.

#first-card {
  transform: translateX(0px) translateY(150px) scale(2);
  box-shadow: 0px 0px 30px 5px white;
  z-index: 6;
}

#second-card {
  transform: translateX(350px) translateY(130px) rotateX(-15deg) rotateY(45deg);
  z-index: 5;
}

#third-card {
  transform: translateX(350px) translateY(-130px) rotateX(-45deg)
    rotateY(125deg) scale(0.8);
  z-index: 2;
}

#fourth-card {
  transform: translateX(0px) translateY(-200px) rotateX(-45deg) rotateY(180deg)
    scale(0.7);
  z-index: 1;
}

#fifth-card {
  transform: translateX(-350px) translateY(-130px) rotateX(-45deg)
    rotateY(225deg) scale(0.8);
  z-index: 3;
}

#sixth-card {
  transform: translateX(-350px) translateY(130px) rotateX(-15deg)
    rotateY(315deg);
  z-index: 4;
}

요로코롬 말이다.

해결 완료 ~!! 야호


이벤트 핸들러 등록

원래는 버튼을 눌러서 이동하게 만들고 자려고 누웠는데 계속 찜찜했다.

여태까지 버튼을 눌러서 스와이프 하는 거는 너무 많이 해봤기 때문에

사실 3번 해봄

이번에는 마우스 드래그를 이용한 이벤트 핸들러를 구현해보자고 생각했다.

그래소 호다닥 벌떡 일어나 자기 전에 Swiper 라는 이벤트 핸들러 객체를 만들었다.

class Swiper {
  constructor(eventArea) {
    this.eventArea = eventArea;
    this.partialLength = 1;
    this.width = 0;
    this.isClick = false;
    this.initalPoint = 0;
  }

  init(numPartial = 6) {
    this.width = this.eventArea.offsetWidth;
    this.partialLength = this.width / numPartial;
  }

  setInitalPoint(e) {
    this.initalPoint = e.clientX;
  }

  changeIsClick() {
    this.isClick = !this.isClick;
  }

  calculateDistance(e) {
    const isMoveRight = this.initalPoint < e.clientX;
    const distance = isMoveRight
      ? e.clientX - this.initalPoint
      : this.initalPoint - e.clientX;

    return [isMoveRight, distance];
  }

  rotateObject(e, rotCard) {
    // click 상태가 아니면 early return
    if (!this.isClick) return;

    // target 은 RotateCards 객체여야 함
    if (!(rotCard instanceof RotateCards)) {
      throw new Error(
        'Target object must be an instance of the RotateCards class!',
      );
    }

    const [isMoveRight, distance] = this.calculateDistance(e);
    const { partialLength } = this;
    if (distance < partialLength) return;

    if (isMoveRight) {
      rotCard.moveRight();
    } else {
      rotCard.moveLeft();
    }

    this.changeIsClick();
  }
}

원리는 되게 간단하다. Swiper 객체는 마우스가 눌린 상태에서 rotateObject 메소드가 작동되어 인수로 받은 rotCardmoveLeft , moveRight 중 조건에 맞는 것을 호출한다.

이후 동작이 끝나면 isClick 의 값을 다시 false 로 변경하여 rotateObject 가 클릭 버튼이 떼진 상태에서는 동작되지 않도록 막는다.

이동한 거리인 distance 는 첫 번째 클릭이 일어난 장소부터 이동한 거리를 의미한다.

카드들은 마우스 버튼이 눌린 채로 이동한 distanceSwiper 객체에서 설정한 ParticalLength 보다 클 경우 회전한다.

ParticalLength 값은 init(numPartical = 6) 메소드를 실행 시켜 카드들을 담은 container 영역을 numPartical 으로 나눈만큼의 길이다.

const cards = document.querySelectorAll('.card');
const cardSequnce = [
  'first-card',
  'second-card',
  'third-card',
  'fourth-card',
  'fifth-card',
  'sixth-card',
];
const rotateCards = new RotateCards([...cards], cardSequnce);
const $container = document.querySelector('.container');
const swiper = new Swiper($container);

swiper.init();

$container.addEventListener('mousedown', (e) => {
  swiper.changeIsClick();
  swiper.setInitalPoint(e);
});

$container.addEventListener('mousemove', (e) => {
  swiper.rotateObject(e, rotateCards);
});

RotateCards , Swiper 객체를 생성해주고 이벤트 핸들러로 등록해주면

작동 끝 ~!!


HTML / CSS

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="container">
      <div id="first-card" class="card">1</div>
      <div id="second-card" class="card">2</div>
      <div id="third-card" class="card">3</div>
      <div id="fourth-card" class="card">4</div>
      <div id="fifth-card" class="card">5</div>
      <div id="sixth-card" class="card">6</div>
    </div>
  </body>
  <script src="rotate.js"></script>
  <script src="event.js"></script>
</html>
@import url('https://fonts.googleapis.com/css2?family=Hahmlet:wght@300&display=swap');

* {
  font-family: 'Hahmlet', serif;
  font-weight: 900;
  user-select: none;
}

body {
  background-color: black;
}

.container {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 200px;
  position: relative;
  margin: 0px auto;
  height: 800px;
}

.card {
  width: 200px;
  height: 300px;
  background-color: #04bfbf;
  position: absolute;
  color: white;
  font-size: 40px;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 10%;
  transition: transform 1s;
}

#first-card {
  transform: translateX(0px) translateY(150px) scale(2);
  box-shadow: 0px 0px 30px 5px white;
  z-index: 6;
}

#second-card {
  transform: translateX(350px) translateY(130px) rotateX(-15deg) rotateY(45deg);
  z-index: 5;
}

#third-card {
  transform: translateX(350px) translateY(-130px) rotateX(-45deg)
    rotateY(125deg) scale(0.8);
  z-index: 2;
}

#fourth-card {
  transform: translateX(0px) translateY(-200px) rotateX(-45deg) rotateY(180deg)
    scale(0.7);
  z-index: 1;
}

#fifth-card {
  transform: translateX(-350px) translateY(-130px) rotateX(-45deg)
    rotateY(225deg) scale(0.8);
  z-index: 3;
}

#sixth-card {
  transform: translateX(-350px) translateY(130px) rotateX(-15deg)
    rotateY(315deg);
  z-index: 4;
}

회고

망가뜨린 장난감만큼 성장한다는 문구를 가슴에 새기며 ..

재밌었다.

다음에는 API 를 이용해서 해당 카드 배너에 날씨나 내용물 같은 것들을 채워놔봐야겠다.

리액트를 배워보진 않았지만 리액트는 뚝닥뚝닥 태그 자체를 안에 넣어버리던데

나도 innerHTML 을 이용해서 태그를 만드는 함수를 만들고 채워넣어봐야지 :)

캬캭 가보자고

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

1개의 댓글

comment-user-thumbnail
2024년 1월 18일

정말 멋진 트리플 악셀 잘 보고 갑니다😀😀

답글 달기