디블리셔에서 프론트엔드가 되어가는 불쌍한 IT땔감

[Javascript] 마우스에 반응하는 3D Animation (feat.Dev Ed) 본문

IT/Javascrpt, Jquery

[Javascript] 마우스에 반응하는 3D Animation (feat.Dev Ed)

lilnagi 2021. 3. 23. 16:22

 

 

사실 이 유튜브 영상만 따라해도 바로 알 수 있음. 왠만해선 바로 이해될 것임.

그런데 javascript를 이제 막 공부하기 시작한 주변사람들 중 이게 영어라서 못알아들어 공부가 안되고 이해가 안돼서 결국 구글링해 긁어다 쓰는거랑 다를바가 없다는 사람이 있어 직접 해설을 해보기로. 해설이라기보단 그냥 알기 쉽게 이 유튜버 분의 소스를 설명해보겠음.

쭉 읽고 난 뒤엔 javascript에 대한 가독성이 뭔가 더 좋아졌음을 느낄 수 있을것임.

하코사라던지 여러 커뮤에서 관련분야 질문을 해도, 대체로 친절하고 세세하게는 안알려주고

답변해주는 사람들도 '이정돈 당연히 알고 있겠지' 라고 생각하고 답을 해주니까 그것조차도 모르는 사람들은 결국 원하는 답을 못얻고 헤메는 경우가 많음...

그렇게 답답했던 사람들에게 조금 도움이 되기를. ㅠㅠ

(※ css나 js는 따로 리소스 폴더를 만들어 관리하는 쪽을 추천함)

 

 

  • HTML
<div class="container">
    <div class="card">
        <div class="item">
            <div class="circle"></div>
            <img src="resources/images/kakao.png" alt="kakao">
        </div>
        <div class="info">
            <h1 class="title">Kakao</h1>
            <h3>Kakao Friends 3D Test Page Example Example Example</h3>
            <div class="options">
                <button class="active">1EA</button>
                <button>2EA</button>
                <button>3EA</button>
                <button>4EA</button>
            </div>
            <div class="purchase">
                <button>Purchase</button>
            </div>
        </div>
    </div>
</div>

 

3D Effect의 연습을 위해 만든 HTML.

kakao.png는 어차피 연습용이라 구글에서 "카카오프렌즈 png" 키워드로 검색해 아무거나 받은것임.

 

  • CSS
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
body {
    font-family: "Poppins", sans-serif;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    perspective: 1000px;
}
.container {
    width: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
}
.card {
    /* min-height: 80vh; */
    width: 35rem;
    border-radius: 30px;
    padding: 0rem 5rem;
    box-shadow: 0 20px 20px rgba(0,0,0,.2), 0 0 50px rgba(0,0,0,.05);
    transform-style: preserve-3d;
    -webkit-transform-style: preserve-3d;
    transition: all .3s ease;
    -webkit-transition: all .3s ease;
}
.item {
    min-height: 35vh;
    display: flex;
    justify-content: center;
    align-items: center;
}
.item img {
    width: 20rem;
    z-index: 2;
    transition: all .75s ease-out;
    -webkit-transition: all .75s ease-out;
}
.circle {
    width: 15rem;
    height: 15rem;
    background: linear-gradient(to right, rgba(245,70,66,.75), rgba(8,83,156,.75));
    position: absolute;
    border-radius: 50%;
    z-index: 1;
}
.info h1 {
    font-size: 3rem;
    transition: all .75s ease-out;
    -webkit-transition: all .75s ease-out;
}
.info h3 {
    font-size: 1.3rem;
    padding: 2rem 0rem;
    color: #585858;
    font-weight: lighter;
    transition: all .75s ease-out;
    -webkit-transition: all .75s ease-out;s
}
.options {
    display: flex;
    justify-content: space-between;
    transition: all .75s ease-out;
    -webkit-transition: all .75s ease-out;
}
.options button {
    padding: .5rem 1.5rem;
    background: none;
    border: none;
    box-shadow: 0 5px 10px rgba(0,0,0,.2);
    border-radius: 30px;
    cursor: pointer;
    font-weight: bold;
    color: #585858
}
button.active {
    background: #585858;
    color: #fff;
}
button:focus {
    outline: 0;
}
.purchase {
    margin: 5rem 0 3rem 0;
}
.purchase button {
    width: 100%;
    padding: 1rem 0rem;
    background: #f54642;
    border: none;
    color: #fff;
    cursor: pointer;
    border-radius: 30px;
    font-weight: bolder;
    transition: all .98s ease-out;
    -webkit-transition: all .98s ease-out;
}
.purchase button:hover {
    background: #646fff;
}

가급적 영상속에 나온 css와 최대한 똑같이 하려고 노력은 함...

 

 

  • JS
// Movement Animation to happen
const card = document.querySelector('.card');
const container = document.querySelector('.container');

//Items
const title = document.querySelector('.title');
const item = document.querySelector('.item img');
const purchase = document.querySelector('.purchase button');
const description = document.querySelector('.info h3');
const options = document.querySelector('.options');

// Moving Animation Event
container.addEventListener("mousemove", (e) => {
    console.log(e.pageX, e.pageY);
    let xAxis = (window.innerWidth / 2 - e.pageX) / 20;
    let yAxis = (window.innerHeight / 2 - e.pageY) / 20;
    
    card.style.transform = `rotateY(${yAxis}deg) rotateX(${xAxis}deg)`
});

// Animate In
container.addEventListener('mouseenter', (e) => {
    card.style.transition = 'none';
    
    // Popout
    title.style.transform = 'translateZ(150px)';
    item.style.transform = 'translateZ(200px) rotateZ(-45deg)';
    description.style.transform = 'translateZ(125px)';
    options.style.transform = 'translateZ(100px)';
    purchase.style.transform = 'translateZ(75px)';
})

// Animate Out
container.addEventListener('mouseleave', e => {
    card.style.transition = 'all .3s ease';
    card.style.transform = `rotateY(0deg) rotateX(0deg)`

    // Popback
    title.style.transform = 'translateZ(0px)';
    item.style.transform = 'translateZ(0px) rotateZ(0)';
    description.style.transform = 'translateZ(0px)';
    options.style.transform = 'translateZ(0px)';
    purchase.style.transform = 'translateZ(0px)';
});

이제부터 설명 시작.

 

// Movement Animation to happen
const card = document.querySelector('.card');
const container = document.querySelector('.container');

//Items
const title = document.querySelector('.title');
const item = document.querySelector('.item img');
const purchase = document.querySelector('.purchase button');
const description = document.querySelector('.info h3');
const options = document.querySelector('.options');

각 HTML Elements들에 대한 상수 정의를 했다. 그럼 스크립트 상에서 불러와 쓰게 될 상수들은 총

card, container, title, item, purchase, description, options 이렇게 6개가 되겠다.

 

  • const 란?
    - constant(상수)라는 의미의 줄임말로, 특정 html element를 가져와서 상수를 정의할때 씀. 재선언, 재할당이 불가한 고유의 이름을 붙임. 불필요하게 변수가 재사용되는것을 막고자 한다면(=변수가 재사용될 여지도, 그럴 필요도 없다고 한다면) const를 써도 좋음.

    const card = document.querySelector('.card');
    라는 구문을 해석하자면,
    상수의 이름을 card로 할건데, 이 card라는 상수가 가지는 값은 문서 내에 있는 클래스명이 card인 애로 선택할거  야. 라는 의미.
    결국 ('.card) 를 card라는 이름의 상수로 정의했으니, javascript 상에서 그냥 card라고 이름만 써도 ('.card') 와 동일해서, 길고 복잡한 소스의 길이도 단축되고 좋음'ㅅ'

    (참고 페이지: developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/const)

 

 

const - JavaScript | MDN

const const 선언은 블록 범위의 상수를 선언합니다. 상수의 값은 재할당할 수 없으며 다시 선언할 수도 없습니다. The source for this interactive example is stored in a GitHub repository. If you'd like to contribute to th

developer.mozilla.org

 

 

// Moving Animation Event
container.addEventListener("mousemove", (e) => {
    //console.log(e.pageX, e.pageY); //콘솔테스트용
    let xAxis = (window.innerWidth / 2 - e.pageX) / 20;
    let yAxis = (window.innerHeight / 2 - e.pageY) / 20;
    
    card.style.transform = `rotateY(${yAxis}deg) rotateX(${xAxis}deg)`
});

앞서 정의했던 container 에다가 이벤트리스너를 추가할건데, 이건 지정된 요소 내에서 마우스가 움직일 때에 발생할 이벤트야 라는 의미.

  • mousemove ??
    - 이건 내가 임의로 정한 변수같은게 아니라 javascript의 기능이라고 이해하면 됨. 
  • (e) ??
    - 가끔 javascript를 하다보면 function(e) 같은걸 본 적이 있을것임. 뭔지 궁금해 검색해봐도 다들 "event 관련 object를 받는 argument입니다." 라고만 대답하니 이걸 못알아듣는 비전공자들은 속이 터졌을것 같음...
    일반 function()과 아주 많이 다른건 아니고, "mousemove"라는 event가 발생할 때의 정보를 e 안에 담겠다는 의미임. 궁금하면 console.log(e) 로 콘솔찍어보면 쉽게 알수 있을 것임. 이건 나중에 매개변수에 대해 설명하게 되면 자세히 다뤄보기로...
  • let ???
    - javascript의 변수 선언 방식은 3가지가 있음. 

    (1) var : 변수의 재선언, 재할당이 가능. 함수 스코프 내에 있는 한 { } 블록 내부에서 선언한 변수라 할지라도 외부에서 참조가 됨. 쉽게말해 블록 바깥 영역에서 전역변수를 마구잡이로 쓰게되는 경우가 생길 수가 있다는 의미. 제약 없이 자유롭게 갖다쓰기 편리하다는 장점이 있겠으나 일이 커지면 감당이 힘들어질 가능성이 있다는 단점 또한 존재.
var test = 'vartest01'
var test = 'vartest02'

console.log(test) // vartest02가 출력됨

     작업이 방대해지면 나중에 중복선언을 막기위해 var들을 찾아 헤멜 수가 있으니 가급적이면 let을 사용하길 추천하       는 추세.

 

     (2) let : 변수의 재선언은 불가능함. 재할당은 가능함.

let test = 'let을 테스트';
let test= = 'let을 또 테스트';

     ↑이걸 실행하면 아래와 같은 에러를 만나게 될 것임. 이미 선언돼있는걸 왜 또 쓰냐는 의미임.

 

     (3) const : 앞서 설명했듯, 재선언 및 재할당이 불가한 고유의 상수 정의.
     

결국은 {} 블록 바깥에서 참조가 되냐 안되냐, 재선언 재할당이 가능하냐 아니냐의 차이라고 할수있는데, 추후 호이스 팅 개념과 함께 자세하게 정리해볼까 함.

 

  • innerWidth ?
    - javascript에서 브라우저 내의 영역을 구분지을 때 outerWidth, innerWidth, outerHeight, innerHeight를 씀.
      (innderWidth, outerWidth 등에 관한 설명은 이미지로 대체.
    )

 

  • pageX, pageY ???
    - 브라우저의 왼쪽 맨위(inner기준) 을 기점으로 x축, y축으로 마우스이벤트가 발생한 값을 반환.

    결국
    let xAxis = (window.innerWidth/2 - e.pageX)/20;
    이라는 구문의 의미는
    xAxis 라는 이름의 변수를 정의할건데, 그 변수의 값은 브라우저 innerWidth를 절반으로 나눈 값에서 마우스가 x축으로 이벤트 발생한 값 만큼을 뺀 뒤, 거기서 20을 나눈 값이야
    라고 해석 가능함. (마지막에 나누는 값이 작아질 수록 결과값은 커질 것이기 때문에 마우스의 움직임에 따라 반응하는 범위도 넓어짐.) 
  • card.style.transform ???
    - 여기서 맨앞에 있는 card 라는 키워드는 앞서 const로 지정해준 상수를 의미함. 즉 클래스명이 card인 영역에 스타일 지정을 할건데, 그 스타일은 transform이란 의미임.

    card.style.transform = `rotateY(${yAxis}deg) rotateX(${xAxis}deg)`
    여기서 ${xAxis}, ${yAxis} 는 let으로 정의한 xAxis와 yAxis의 값을 불러옴을 의미. css를 javascript를 이용해 가변적으로 작성했다고 볼 수 있겠음.
    참고로 `` 으로 감싸줘야 함. ''가 아님. 키보드에서 숫자1 왼쪽에 있는 것.

 

// Animate In
container.addEventListener('mouseenter', (e) => {
    card.style.transition = 'none';
    
    // Popout
    title.style.transform = 'translateZ(150px)';
    item.style.transform = 'translateZ(200px) rotateZ(-45deg)';
    description.style.transform = 'translateZ(125px)';
    options.style.transform = 'translateZ(100px)';
    purchase.style.transform = 'translateZ(75px)';
})

여기서 container 또한 앞서 const로 정의한 상수임. 즉

container.addEventListener('mouseenter'){} 를 해석하자면, container로 상수정의한 영역 안에 마우스가 들어왔을 때의 이벤트를 만들어보겠단 의미임. 

{ } 내에는 const로 정의한 것들을 선언해 가져오고, style.transform 으로 css속성을 추가하는 모습임.

이렇게 mouseenter 이벤트와 mouseleave (마우스가 영역에서 벗어나 떠날때) 이벤트를 정의하여 3D Effect를 만들 수 있게 되는것임. mouseleave일땐 당연히 css의 transform 속성값을 모두 0으로 되돌려주면 원래대로 돌아오고,

transition 'all .3s ease' 를 줘서 부드러운 애니메이션이 되도록 한 것임.

translateZ는 당연히 x축, y축이 아닌 입체적인 z축을 의미하며, translateZ 값을 준 후 마우스로 움직여 보면 위로 솟아올라 있는걸 확인 가능함.

 

다 완성하고 보면 이렇게 마우스 움직임에 따라 각도가 틀어지는 3D Effect가 완성됨.

사실 마우스 움직이는 값 읽어오는 것 말고는 css만으로 구현가능한 3D Effect와 크게 다를건 없다.

오른쪽 숫자는 콘솔값 찍히는거 확인하는 것임. 마우스가 조금이라도 움직일 때마다 xAxis와 yAxis 값이 찍힘.

 

오랜만에 포스팅인데 개귀찮넹'ㅅ'.....

Comments