티스토리 뷰

 

위와 같이 마우스 클릭 또는 터치로 돌렸을때 돌아가는 인터랙션을 만들었습니다.

네이버 모바일 앱 브라우저의 그린닷이 위와 같은데 위와 같은 인터랙션을 구현해야할 일이 있어서 만들게 되었습니다.

 

방법 생각하기

1. 원형 중심의 좌표 구하기

2. 원형 중심을 기준으로 처음 클릭 지점과 포인트(마우스 움직임, 터치 움직임)의 사이 각도를 계산하기

3. 구한 각도를 바탕으로 원을 돌리고 마우스를 땔때 현재 돌아가 있는 각도 저장

4. 다시 돌릴때 이전에 저장되어 있는 각도를 더한 각도로 돌리기

5. 반대 방향으로 탭 네비게이션들의 각도를 돌리기  

 

drag를 구현하려면?

우선 드래그와 함께 돌아가는 물체를 구현하기 이전에 드래그를 구현하는 방법에 대해서 생각해봅니다.

 

무언가를 클릭하고 드래그해서 움직이게 하려면 기본적으로 3가지 이벤트가 필요합니다.

 

1. 클릭시작 : mousedown, touchstart, pointerdown

2. 움직임 : mousemove, touchmove, pointermove

3. 클릭땜 : mouseup, touchend, pointerup

 

마우스와 관련된 이벤트는 mouse...이고 터치에 관련된 이벤트는 touch... 이벤트입니다.

예를들어 마우스를 사용하는 기기에서 이벤트를 동작하면 mouse.... 이벤트가 있어야하고

똑같이 터치관련 기기는 touch... 이벤트를 주어야합니다.

 

마우스와 터치 둘다 동작하려면 mouse , touch 이벤트 둘다 주어야합니다.

하지만 클릭한 지점의 위치를 받아오는 방법이 두개가 달라서 그에 따른 분기처리를 해주어야합니다.

window.addEventListener("mousemove", function(e){
	const m_x = e.clientX;
    const m_y = e.clientY;
	console.log("마우스 X 위치 : ", m_x);
    console.log("마우스 Y 위치 : ", m_y);
});

window.addEventListener("touchmove", function(e){
	const touch = e.touches[0] || e.changedTouches[0];
    const t_x = touch.clientX;
    const t_y = touch.clientY;
	console.log("터치 X 위치 : ", t_x);
    console.log("터치 Y 위치 : ", t_y);
});

pointer events

포인터 이벤트는 마우스, 터치 두 동작을 한번에 처리할 수 있는 이벤트입니다.

마우스든 터치든 pointer 이벤트 하나로 처리가 가능합니다.

window.addEventListener("pointermove", function(e){
	const p_x = e.clientX;
    const p_y = e.clientY;
	console.log("포인터 X 위치 : ", p_x);
    console.log("포인터 Y 위치 : ", p_y);
});

그럼 훨씬 더 간편하고 코드를 줄일 수 있는 포인터 이벤트를 사용해서 드래그를 구현해보겠습니다.

 

간단한 원 드래그 인터랙션 구현해보기

아래 사진에서 1번 원을 클릭해서 2번위치로 이동을 한다했을때, 

1. 1번을 클릭한다

2. 클릭한 채로 드래그해서 2번위치에서 클릭을 뗀다

의 흐름으로 동작을 할것입니다.

이때에 코드를 작성하기 전에 다음과 같이 단계별로 계획합니다.

 

1. 1번원에 클릭시작 이벤트 부여, 

2. 윈도우 창에 클릭움직임 이벤트 부여, 1번원이 클릭이 시작되었다면 1번 원을 마우스 움직임에 맞추어 움직임.

3. 윈도우 창에 클릭 떼었을때 이벤트 부여, 1번원이 클릭이 안된 상태라고 바꾸고 움직임을 멈춤

 

위의 계획을 코드로 풀자면 다음과 같습니다.

let isClick = false;

1번원.addEventListener("pointerdown", function(e){
	// 1번원 클릭함
	isClick = true;
})

window.addEventListener("pointermove", function(e){
	if(isClick){
    	// 1번원 클릭했다면 마우스 움직임에 맞춰 움직임
    	1번원.움직임 = 클릭 x위치, 클릭 y위치
    }
})

window.addEventListener("pointerup", function(e){
	// 1번원 클릭 끝냄
	isClick = false;
})

 

이제는 위의 말들을 작동하는 코드로 작성해보겠습니다.

 

우선 html 과 css로 마크업을 해줍니다.

원의 css에는 touch-action:none 을 줍니다. 

<style>
    #jigi-app { width:100%; height:100%; }
    #jigi-app #one-circle { touch-action: none; width:80px; height:80px; border-radius:50%; background:#cccccc; border:1px solid #000000; }
</style>
<div id="jigi-app">
    <div id="one-circle"></div>
</div>

더 나아가서 1번원.움직임 부분을 풀면 위에서 설명했던

클릭 위치를 받아오는 e.clientX, e.clientY,

1번원의 style에 접근해서 각 x,y 좌표로 위치를 보내줍니다.

const oneCircle = document.querySelector("#one-circle");
let isClick = false;

oneCircle.addEventListener("pointerdown", function(e){
	// 1번원 클릭함
	isClick = true;
})

window.addEventListener("pointermove", function(e){
	if(isClick){
    	// 1번원 클릭했다면 마우스 움직임에 맞춰 움직임
    	const p_x = e.clientX;
        const p_y = e.clientY;
        oneCircle.style.transform = `translate(${p_x}px, ${p_y}px)`;
    }
})

window.addEventListener("pointerup", function(e){
	// 1번원 클릭 끝냄
	isClick = false;
})

 

하지만 위와같이 했을때엔 아래와 같이 마우스 위치가 원의 좌측 상단에 맞춰져서 움직이게 됩니다.

원 중앙 지점에서부터 움직이기

마우스가 중앙에 있고 그에 따라 원을 움직이게 하고 싶다면

원의 반지름만큼 x, y 좌표에 빼주어 움직이면 됩니다!

 

const oneCircle = document.querySelector("#one-circle");
// 원의 반지름 구하기
const circleRadius = oneCircle.offsetWidth / 2;
let isClick = false;

oneCircle.addEventListener("pointerdown", function(e){
    // 1번원 클릭함
    isClick = true;
})

window.addEventListener("pointermove", function(e){
    if(isClick){
        // 1번원 클릭했다면 마우스 움직임에 맞춰 움직임
        // 원의 반지름 만큼 x, y 빼주어 움직이기
        const p_x = e.clientX - circleRadius;
        const p_y = e.clientY - circleRadius;
        oneCircle.style.transform = `translate(${p_x}px, ${p_y}px)`;
    }
})

window.addEventListener("pointerup", function(e){
    // 1번원 클릭 끝냄
    isClick = false;
})

하지만 위처럼 해도 항상 마우스의 위치는 중앙으로 가게됩니다.

원의 클릭한 지점에서 부터 움직이고 싶습니다.

원 클릭한 지점에서부터 움직이기

클릭한 지점에서부터 원을 움직이려면 위의 사진에서 x(2), y(2)의 값을 구해야합니다.

최초 원을 클릭했을 때

마우스 클릭 위치( x(0), y(0) ) - 원의 위치( x(1), y(1) ) 를 빼면 x(2), y(2) 값을 구할 수 있습니다.

<식>

y(0) - y(1) = y(2)

x(0) - x(1) = x(2)

코드로는 다음과 같습니다.

const oneCircle = document.querySelector("#one-circle");
let isClick = false;

let diff_x = 0;
let diff_y = 0;

oneCircle.addEventListener("pointerdown", function(e){
    // 1번원 클릭함
    const p_x = e.clientX;
    const p_y = e.clientY;

    // 원의 레이아웃 정보 불러오기
    const circleRect = oneCircle.getBoundingClientRect();
    // 원의 x, y 정보
    const circle_posX = circleRect.x;
    const circle_posY = circleRect.y;
    diff_x = p_x - circle_posX;
    diff_y = p_y - circle_posY;

    isClick = true;
})

window.addEventListener("pointermove", function(e){
    if(isClick){
        // 1번원 클릭했다면 마우스 움직임에 맞춰 움직임
        const p_x = e.clientX;
        const p_y = e.clientY;

        const o_x = p_x - diff_x;
        const o_y = p_y - diff_y;

        oneCircle.style.transform = `translate(${o_x}px, ${o_y}px)`;
    }
})

window.addEventListener("pointerup", function(e){
    // 1번원 클릭 끝냄
    isClick = false;
})

 

자. 이제 드래그를 구현하는 법을 알았으니

드래그를 했을때 각도를 구하는 방법을 알아봅시다!

 

삼각함수

원형의 인터랙션을 다루기 위해서 삼각함수를 사용했습니다.

점 1과 점 2의 각도를 구하기 위해선 아크탄젠트를 이용해 구합니다.

 

 

아크탄젠트

아크탄젠트란 탄젠트의 역함수로써 역탄젠트라고 부릅니다.

아크탄젠트를 사용하면 점 1과 점 2의 각도를 구할 수 있습니다.

javascript는 Math 모듈에 atan과 atan2를 사용해서 아크탄젠트를 구합니다.

 

atan2

atan2를 사용하는 이유는 atan은 상대적인 위치가 아닌 절대적인 각을 반환시켜

음수가 나올 수 없는 반면 atan2은 상대적인 위치로 음수값 반환해 점 1보다 점 2점의

x와 y 값이 작아도 음수의 각을 반환시킬 수 있어 사용한다고합니다.

 

위의 내용은 더 공부를 해서 정리가 필요하지만

우선 원리는 알았으니 시작해보겠습니다.

 

 

 

마크업

원을 만들고 내부의 네비게이션 요소들을 원의 중앙에 정렬시켜주고

내부 요소들을 중앙을 기점으로 transform: translate로 각 위치에 맞게

보낼 계획입니다.

 

wheel-container : 회전하는 휠

wheel-nav : 휠 내부 네비게이션

 

wheel-container 클래스 dom이 마우스 또는 터치에 따라 돌아가는 요소이고

wheel-nav가 내부에 들어가는 네비게이션 요소입니다.

<div class="app">
    <div class="container">
        <ul class="wheel-container">
            <li class="wheel-nav">
                <a href="#">nav01</a>
            </li>
            <li class="wheel-nav">
                <a href="#">nav02</a>
            </li>
            <li class="wheel-nav">
                <a href="#">nav03</a>
            </li>
            <li class="wheel-nav">
                <a href="#">nav04</a>
            </li>
            <li class="wheel-nav">
                <a href="#">nav05</a>
            </li>
            <li class="wheel-nav">
                <a href="#">nav06</a>
            </li>
        </ul>
    </div>
</div>

 

 

터치 이벤트가 정상적으로 작동하기 위해서

.app .wheel-container( 이벤트를 줘서 회전할 요소 )에 touch-action : none을 추가해줬습니다.

css를 작성한 핵심은 휠을 기준으로 네비게이션들이 중앙에 위치해야 한다는 점입니다.

 

 

 

.app { 
	position:relative; width:100%; max-width:360px; height:100%; 
    margin:0 auto; background:#ffffff; 
}
.app .container { 
	position:absolute; top:50%; left:50%; width:80%; 
    transform:translate(-50%, -50%);
}
.app .wheel-container { 
	touch-action: none; 
	margin:0 auto; width:100%; padding-bottom:100%; 
    border-radius:50%; border:1px solid #000000; 
}
.app .wheel-container .wheel-nav { 
	position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
}
.app .wheel-container .wheel-nav a { 
	display:block; 
}

 

Javascript

스크립트 작성하기

1. class 구조 만들기

독단적으로 변수를 관리하기 편리한 클래스로 작업을 시작했습니다. 

 

this.wheel : 회전하는 휠

this.nav :내부의 네비게이션 요소

this.setting : 휠 세팅값

constructor에 parameter값으로 element와 세팅값을 설정해줍니다.

class WheelCreate{
    constructor(element, setting){
        const {wheel, nav} = element || {};

        this.wheel = wheel;
        this.nav = nav;
        this.setting = setting;
    }  
}

 

그리고 parameter 첫번째 인자에 object로 휠과 네비게이션을 보내줍니다.

setting에도 설정값들을 object로 선언해주었습니다.

필요한 세팅값들을 세팅하고 new WheelCreate()로 실행해줍니다.

positionOffset : 원의 테두리부터 네비게이션의 위치 (0 ~ 100 이 기본)

transition의 duration : 터치를 때었을때 네비게이션이 제자리로 돌아가는 시간

const wheelEl = document.querySelector(".wheel-container");
const navEls = document.querySelectorAll(".wheel-nav");

const el = {
    wheel:wheelEl,
    nav:navEls
}
const setting = {
    positionOffset:80,
    transition:{
        duration:0.5,
        timing:"easing"
    }
};

const wheel = new WheelCreate(el, setting);

 

2. constructor

기본적인 구조는 다음과 같습니다.

초기설정값, wheel과 nav의 설정값 세팅

이벤트 부여, 이벤트 동작 함수 작성

class WheelCreate{
    constructor(element, setting){
        const {wheel, nav} = element || {}; // 휠과 네비게이션 element

        this.wheel = wheel; // this.wheel 에 wheel element 넣기
        this.nav = nav; // 마찬가지로 nav elements 넣기
        this.setting = setting; // 세팅값 설정

        this.objInitial(); // 초기 설정값 실행
        this.addEvent(); // 이벤트 부여
    }
    objInitial(){
		// 초기 설정값
    }
    resize(){
		// 윈도우 리사이즈 시 세팅 수정
    }
    wheelSetting(){
		// 휠의 세팅값 설정
    }
    navSetting(){
		// 네비게이션의 세팅값 설정
    }
    down(e){
    	// 포인터를 클릭했을 때 실행
    }
    move(e){
		// 포인터를 움직였을 때 실행
    }
    up(){
		// 포인터를 떼었을 때 실행
    }
    addEvent(){
    	// 필요한 이벤트 부여 및 실행
        window.addEventListener("resize", this.resize.bind(this));
        this.wheel.addEventListener("pointerdown", this.down.bind(this), false);
        window.addEventListener("pointermove", this.move.bind(this), false);
        window.addEventListener("pointerup", this.up.bind(this), false);
    }    
}

3. 초기설정 및 wheel , nav 세팅

내용이 많아서 주석으로 설명을 작성했습니다.

간략하게는 초기에 휠과 네비게이션의 위치, 변수값들, 사용할 함수를 세팅해주었습니다.

objInitial(){
	// 초기 세팅값 설정
    this.navRound = 90; // nav 초기 rotate 설정값
    this.navLen = this.nav.length; // nav의 갯수
    this.digit = 360/this.navLen; // nav 1개의 회전률(6개면 360/6이니 60도씩)
    this.index = 0; // 현재 회전 index
    this.isClick = false; // wheel 을 클릭했는지

    this.tDeg = 0; // 임시 회전값 저장
    this.navTransformArr = []; // nav의 위치값 array형식으로 저장 

    this.wheelSetting(); // 휠 세팅 실행
    this.navSetting(); // 네비게이션 세팅 실행
}
resize(){
	// 윈도우가 리사이즈 될때마다 실행시킬 함수들
    // 윈도우가 리사이즈 될때마다 포지션값들, 세팅값들을 다시 불러와줘야 합니다.
    this.wheelSetting();
    this.navSetting();
    this.degTransform();
    this.isTransition(false); // 리사이즈동안 트랜지션이 들어가지 않도록 실행
}
wheelSetting(){
	// 휠의 정보, 세팅값 설정
    this.wheelRect = this.wheel.getBoundingClientRect(); // 휠의 rect 정보
    this.wheelWidth = this.wheelRect.width; // 휠의 가로폭
    this.wheelHeight = this.wheelRect.height; // 휠의 세로폭

    this.radius = this.wheelWidth/2; // 휠(원)의 반지름을 가로폭을 2로 나누어 구합니다.
    this.w_oX = this.wheelRect.x; // 휠의 왼쪽 위 x포지션
    this.w_oY = this.wheelRect.y; // 휠의 왼쪽 위 y포지션
    this.w_cX = this.w_oX + this.radius; // 휠의 중앙 x포지션
    this.w_cY = this.w_oY + this.radius; // 휠의 중앙 y포지션
    // 휠의 중앙 좌표로 부터 포인터 포지션이 어디에 위치해 있는지를
    // 상대값으로 알기위해 중앙 포지션을 구해줍니다.
}
navSetting(){
    const {positionOffset} = this.setting || {};
	
    // 네비게이션의 가장자리에서 몇 % 위치에 네비게이션이 위치할 지 정하는 변수입니다.
    this.positionOffset = positionOffset ? this.radius/(100/positionOffset) : this.radius/2;
    // 네비게이션의 포지션을 설정해줍니다.
    // 위치는 sin, cos를 사용해서 구합니다.
    // 삼각비로 세타의 각도를 알고 있다면 포지션의 x, y 좌표를 알 수 있습니다.
    // positionOffset을 통해서 가장자리에서부터 얼마나 위치해 있는지를 설정해 줍니다.
    for(let i = 0, len = this.navLen; i < len; i++){
        const x = Math.floor( Math.cos((Math.PI / 180) * this.navRound ) * this.positionOffset );
        const y = Math.floor( Math.sin((Math.PI / 180) * this.navRound ) * (this.positionOffset * -1) );
        this.nav[i].style.transform = `translate(calc(${x}px - 50%), calc(${y}px - 50%)) rotate(${this.tDeg * -1}deg)`;
        // 네비게이션이 다시 세팅될 때 x, y 값을 설정해 줄 arr입니다.
        this.navTransformArr.push({x:x, y:y});
        // nav가 0도면 왼쪽 기준이기때문에 처음 인덱스의 네비게이션은
        // 위쪽에 위치해야 하므로 navRound초기 값을 90으로 설정해주고
        // nav 사이 값을 for문이 돌때 마다 빼줍니다.
        this.navRound  -= this.digit;
    }
}
isTransition(bool){
    const {transition} = this.setting || {};
    const {duration, timing} = transition || {};
    // 파라미터인 bool값이 true, false에 따라 트랜지션을 주고 안주고를 분기처리해줍니다.
    // 이렇게 처리한 이유는 드래그를 통해 휠을 돌릴 때와 
    // 윈도우 창을 리사이즈 할 때에 리트랜지션을 주지않고,
    // 포인트를 떼었을 때 트랜지션을 주기위함입니다. 
    // duration, timing을 변수로 설정해 유동적으로 바꿀 수 있도록 해주었습니다.
    const strTransition = bool ? `all ${duration ? duration : '0.2'}s ${timing ? timing : 'linear'}` : `none`;
    this.wheel.style.transition = strTransition;
    for(let i = 0, len = this.navLen; i < len; i++){
        this.nav[i].style.transition = strTransition;
    }
}
degTransform(transformDeg){
	// 받은 파라미터 deg 값으로 휠을 돌려줍니다.
    // 이 값은 이제 이벤트에서 받아온 값을 보내줄 예정입니다.
    this.wheel.style.transform = `rotate(${transformDeg}deg)`;
    for(let i = 0, len = this.navLen; i < len; i++){
    	// 위에서 설정해준 navTransformArr값으로 네비게이션의 x, y 값을 설정해주고
        // 받은 파라미터 deg값에 *-1을 해주어 반대로 돌아가도록 해주었습니다.
        const {x, y} = this.navTransformArr[i];
        this.nav[i].style.transform = `translate(calc(${x}px - 50%), calc(${y}px - 50%)) rotate(${transformDeg * -1}deg)`;
    }
}
degCalc(x, y){
	// x, y 값을 받아 atan2를 이용해 각도를 반환 해 주는 함수를 만들었습니다.
    // 각도를 return 합니다.
    // 0~180 -180~0 도를 반환하기 때문에 0~360을 알기위해 다음과 같이
    // pi 를 더해주고 360을 곱하고 360의 나머지를 구합니다.
    const radian = Math.atan2(y, x) + Math.PI;
    return (radian / (Math.PI * 2) * 360) % 360;
}

4. 이벤트 부여 및 기능 동작

down(e){
	// "휠"에서 포인터를 클릭했을때 실행합니다.
    
    // 어떠한 인터랙션, 동작이 이루어지고 유동적인 휠 세팅이 이루어지는 경우에는
    // 휠의 세팅값이 변경이되어 원하는대로 동작이 안될 수도 있는 상황이 생길 수 있기 때문에,
    // 휠세팅 함수를 클릭 할 때에 다시 실행해주었습니다.
    this.wheelSetting();
    
    // 포인터 포지션의 위치를 휠의 중앙 지점으로 빼주어서 중앙 위치에서 부터의
    // 포지션을 받아와서 degCalc 함수를 통해서 해당 위치의 각도를 받아옵니다.
    // oDeg의 역할은 포인터 처음 클릭한 지점의 각도를 받아서
    // 그 지점으로부터 포인터가 움직인 위치의 각도를 빼주어
    // 포인터 시작점으로 부터의 각도를 구하기 위함입니다.
    const p_sX = e.clientX - this.w_cX;
    const p_sY = e.clientY - this.w_cY;
    this.oDeg = this.degCalc(p_sX, p_sY);

	// 휠을 클릭 했는지를 판별합니다.
    this.isClick = true;
}
move(e){
	// "window"에서 포인터를 움직였을 때 실행됩니다.
    // isClick변수가 휠에서 true로 바꾸었을때만 해당 로직이 실행이 되게끔 분기처리해줍니다.
    if(this.isClick){
    	// 포인트의 중앙으로부터 움직인 위치값을 받아옵니다.
        const p_oX = e.clientX - this.w_cX;
        const p_oY = e.clientY - this.w_cY;
		// deg에 받아온 포인트의 각도 값을 반환합니다.
        const deg = this.degCalc(p_oX, p_oY);
        // dDeg에는 클릭했을때 저장했던 oDeg를 deg에서 빼어서 클릭한 지점으로부터의
        // 각도를 구해옵니다.
        const dDeg = deg - this.oDeg;

		// resultDeg는 최종적으로 휠이 돌아갈 각도를 계산해줍니다.
        // tDeg는 이전값에서 부터 마우스 휠이 이루어 질 수 있도록하고
        // 포인터를 떼었을 때 네비게이션의 현재 인덱스 방향이 위를 향할 수 있도록,
        // 임시로 이전값을 저장해주는 역할을 합니다.
        const resultDeg = this.tDeg + dDeg;
		// 휠의 현재 인덱스를 최종 휠 각도에 digit값을 나누고 내림을 해서 구해줍니다.
        this.index = Math.floor(resultDeg / this.digit);

		// 휠이 돌아갈 때엔 트랜지션을 주지 않습니다.
        this.isTransition(false);
        // degTransform에 resultDeg값을 보내 휠을 돌려줍니다.
        this.degTransform(resultDeg);
    }
}
up(){
	// 포인터를 떼었을때 설정된 인덱스값에 digit을 곱해서 현재 각도를 재설정해줍니다.
    this.tDeg = this.index * this.digit;
    // 재설정된 각도로 다시 휠을 돌려줍니다.
    this.degTransform(this.tDeg);
    // 휠이 돌아갈때에 트랜지션을 활성화 해줍니다.
    this.isTransition(true);
    // 휠이 클릭되었다는 것을 false로 바꾸어줍니다.
    this.isClick = false;
}

 

5. 최종적인 javascript 코드는 다음과 같습니다.

class WheelCreate{
    constructor(element, setting){
        const {wheel, nav} = element || {};

        this.wheel = wheel;
        this.nav = nav;
        this.setting = setting;

        this.objInitial();
        this.addEvent();
    }
    objInitial(){
        this.navRound = 90;
        this.navLen = this.nav.length;
        this.digit = 360/this.navLen;
        this.index = 0;
        this.isClick = false;

        this.tDeg = 0;
        this.navTransformArr = [];

        this.wheelSetting();
        this.navSetting();
    }
    resize(){
        this.wheelSetting();
        this.navSetting();
        this.degTransform();
        this.isTransition(false);
    }
    wheelSetting(){
        this.wheelRect = this.wheel.getBoundingClientRect();
        this.wheelWidth = this.wheelRect.width;
        this.wheelHeight = this.wheelRect.height;

        this.radius = this.wheelWidth/2;
        this.w_oX = this.wheelRect.x;
        this.w_oY = this.wheelRect.y;
        this.w_cX = this.w_oX + this.radius;
        this.w_cY = this.w_oY + this.radius;
    }
    navSetting(){
        const {positionOffset} = this.setting || {};

        this.positionOffset = positionOffset ? this.radius/(100/positionOffset) : this.radius/2;
        for(let i = 0, len = this.navLen; i < len; i++){
            const x = Math.floor( Math.cos((Math.PI / 180) * this.navRound ) * this.positionOffset );
            const y = Math.floor( Math.sin((Math.PI / 180) * this.navRound ) * (this.positionOffset * -1) );
            this.nav[i].style.transform = `translate(calc(${x}px - 50%), calc(${y}px - 50%)) rotate(${this.tDeg * -1}deg)`;
            this.navTransformArr.push({x:x, y:y});
            this.navRound  -= this.digit;
        }
    }
    isTransition(bool){
        const {transition} = this.setting || {};
        const {duration, timing} = transition || {};
        const strTransition = bool ? `all ${duration ? duration : '0.2'}s ${timing ? timing : 'linear'}` : `none`;
        this.wheel.style.transition = strTransition;
        for(let i = 0, len = this.navLen; i < len; i++){
            this.nav[i].style.transition = strTransition;
        }
    }
    degTransform(transformDeg){
        this.wheel.style.transform = `rotate(${transformDeg}deg)`;
        for(let i = 0, len = this.navLen; i < len; i++){
            const {x, y} = this.navTransformArr[i];
            this.nav[i].style.transform = `translate(calc(${x}px - 50%), calc(${y}px - 50%)) rotate(${transformDeg * -1}deg)`;
        }
    }
    degCalc(x, y){
        const radian = Math.atan2(y, x) + Math.PI;
        return (radian / (Math.PI * 2) * 360) % 360;
    }
    down(e){
        this.wheelSetting();
        const p_sX = e.clientX - this.w_cX;
        const p_sY = e.clientY - this.w_cY;
        this.oDeg = this.degCalc(p_sX, p_sY);

        this.isClick = true;
    }
    move(e){
        if(this.isClick){
            const p_oX = e.clientX - this.w_cX;
            const p_oY = e.clientY - this.w_cY;
            
            const deg = this.degCalc(p_oX, p_oY);
            const dDeg = deg - this.oDeg;
            
            const resultDeg = this.tDeg + dDeg;

            this.index = Math.floor(resultDeg / this.digit);
            
            this.isTransition(false);
            this.degTransform(resultDeg);
        }
    }
    up(){
        this.tDeg = this.index * this.digit;
        this.degTransform(this.tDeg);
        this.isTransition(true);
        this.isClick = false;
    }
    addEvent(){
        window.addEventListener("resize", this.resize.bind(this));
        this.wheel.addEventListener("pointerdown", this.down.bind(this), false);
        window.addEventListener("pointermove", this.move.bind(this), false);
        window.addEventListener("pointerup", this.up.bind(this), false);
    }    
}
const wheelEl = document.querySelector(".wheel-container");
const navEls = document.querySelectorAll(".wheel-nav");

const el = {
    wheel:wheelEl,
    nav:navEls
}
const setting = {
    positionOffset:80,
    transition:{
        duration:0.5,
        timing:"ease"
    }
};

const wheel = new WheelCreate(el, setting);

 

이렇게 휠 네비게이션 인터랙션을 구현해 보았습니다.

더 좋은 방법이 있고 불필요한 코드들도 있고 비효율적인 부분도 있겠지만,

계속 리팩토링을 하며 모듈화를 할 예정입니다.

 

설명이 부족한 부분이 많아서 아쉬움이 많습니다.

세부적인 설명이 더 필요한 부분은 앞으로 더 채워나갈 예정입니다.

댓글
최근에 올라온 글