원형 다이얼

SEUNGTAE CHOI·2024년 12월 24일

퍼블리싱

목록 보기
9/18

결과

모바일 네이버 닷 처럼 원형으로 스와이퍼할 수 있는 기능

코드

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
    <title>Document</title>

    <style>
        :root {
            --rotate-speed: 0.05s;
        }
        body { margin: 0; padding: 10px; height:100svh; width:100vw; overflow: hidden; overscroll-behavior: none; }
        .circle { width: 360px; height: 360px; background-color: #fff; border-radius: 50%; box-shadow: 0 2px 8px #aaa; position:relative; will-change: transform; transition: ease-in-out var(--rotate-speed); margin-top: calc(100svh - 400px); }
        .circle__center { position:absolute; width:60px; height:60px; top:50%; left: 50%; transform: translateX(-50%) translateY(-50%); }
        .link { list-style: none; width:100%; height:100%; position: relative; padding:0; margin:0; }
        .link__item { position:absolute; width:60px; height:60px; display: flex; justify-content: center; align-items: center; will-change: transform; transition: ease-in-out var(--rotate-speed); font-size:12px; flex-direction: column; }
        .link__item div { pointer-events: none; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none }
        .link__item div:nth-child(1) { font-size: 50px; line-height: 0.5; }
        .link__item:nth-child(1) { top:-120px; left:0px;  }
        .link__item:nth-child(2) { top:-90px; left:90px; }
        .link__item:nth-child(3) { top:0px; left:120px; }
        .link__item:nth-child(4) { top:90px; left:90px; }
        .link__item:nth-child(5) { top:120px; left:0px; }
        .link__item:nth-child(6) { top:90px; left:-90px; }
        .link__item:nth-child(7) { top:0px; left:-120px; }
        .link__item:nth-child(8) { top:-90px; left:-90px; }
    </style>
</head>
<body style="background-color: #ccc;">
    <div class="circle" data-rotate="0">
        <div class="circle__center">
            <ul class="link">
                <li class="link__item" data-value="1">
                    <div></div>
                    <div>음성</div>
                </li>
                <li class="link__item" data-value="2">
                    <div></div>
                    <div>내주변</div>
                </li>
                <li class="link__item" data-value="3">
                    <div></div>
                    <div>검색</div>
                </li>
                <li class="link__item" data-value="4">
                    <div></div>
                    <div>파파고번역</div>
                </li>
                <li class="link__item" data-value="5">
                    <div></div>
                    <div>쇼핑렌즈</div>
                </li>
                <li class="link__item" data-value="6">
                    <div></div>
                    <div>QR바코드</div>
                </li>
                <li class="link__item" data-value="7">
                    <div></div>
                    <div>렌즈</div>
                </li>
                <li class="link__item" data-value="8">
                    <div></div>
                    <div>음악</div>
                </li>
            </ul>
        </div>
    </div>
</body>
</html>

<script>
    const isMobile = detectMobileDevice(window.navigator.userAgent)
    const circle = document.querySelector('.circle');
    const linkItems = circle.querySelectorAll('.link__item');
    const center_X = circle.clientWidth/2;
    const center_Y = circle.clientHeight/2;
    let angle_start = 0;        // 터치 시작 시 각도
    let angle_before = 0;       // 터치 이동 시 이전 각도
    let angle_after = 0;        // 터치 이동 중 각도
    let rotate_start = 0;       // 회전 시작
    let rotate_end = 0;         // 회전 종료
    const angle_move_unit = 3.5;    // 이동 각도 간격 (클수록 빨라짐)

    if(isMobile) {
        circle.addEventListener('touchstart', (e) => {
            angle_start = 각도구하기(e);
            angle_before = angle_start;
            rotate_start = Number(circle.dataset.rotate);
        });
        circle.addEventListener('touchmove', (e) => {
            angle_before = angle_after;
            angle_after = 각도구하기(e);
            if(Math.abs(angle_before - angle_after) > 300){ return; }
            if(angle_before < angle_after){
                circle.dataset.rotate = Number(circle.dataset.rotate) - angle_move_unit;
            } else if(angle_before > angle_after){
                circle.dataset.rotate = Number(circle.dataset.rotate) + angle_move_unit;
            }
            rotate_end = Number(circle.dataset.rotate);
            
            위치조정();
        });
        circle.addEventListener('touchend', (e) => {
            let _angle = circle.dataset.rotate;
            
            const angle_45_value = parseInt( _angle / 45 );
            const angle_45_remain = (Math.abs(_angle) % 45);

            if(angle_45_remain > 22.5){
                _angle = angle_45_value * 45;
                if(rotate_start < rotate_end){ _angle += 45; }
                if(rotate_start > rotate_end){ _angle -= 45; }
            } else {
                _angle = angle_45_value * 45;
            }
            circle.dataset.rotate = Number(_angle);
            위치조정();
        });
    } else {
        // circle.addEventListener('mousedown', (e) => {
        //     console.log('mousedown')
        //     posX_start = e.pageX;
        //     PosY_start = e.pageY;
        // });
    }

    const link = document.querySelector('.link');
    link.addEventListener('click', (e) => {
        alert(`${e.target.dataset.value}번 아이템 클릭`);
    })

    function 위치조정(){
        circle.style.transform = `rotate(${circle.dataset.rotate}deg)`;
        linkItems.forEach(el => {
            el.style.transform = `rotate(${(-1)*circle.dataset.rotate}deg)`;
        })
    }

    function 각도구하기(e){
        const _posX = e.touches[0].clientX - circle.offsetLeft - center_X;
        const _posY = (-1)*(e.touches[0].clientY - circle.offsetTop - center_Y);
        const radian = Math.atan2(_posY, _posX);
        let degree = radian * 180 / Math.PI;
        if(degree < 0){
            degree += 360;
        }
        return degree;
    }
    
    function detectMobileDevice(agent) {
        const mobileRegex = [
            /Android/i,
            /iPhone/i,
            /iPad/i,
            /iPod/i,
            /BlackBerry/i,
            /Windows Phone/i
        ]
        return mobileRegex.some(mobile => agent.match(mobile))
    }
</script>
profile
겪은 이슈를 공유합니다.

0개의 댓글