2015년 9월 15일 화요일

UI깎는 노인-Making a knob for Mobile

Knob, 흔히들 Dial 이라고 말하는 전통적인 인터페이스가 있다.
돌리고~돌리~고~ (출처)

webAudio를 하다보니 ( http://jsbin.com/hacotahttp://output.jsbin.com/tidoqi 등등) 직관적인 UI 컨트롤러가 필요해서 잠깐 찾다가 이럴게 아니라 최소한의 요소만 뽑아내서 만들어보기로 함.

목표는 PC/Android/iOS 모두 잘 작동하도록 하는 것.
구현할 기능을 생각해보면

  1. 현재 값을 시각화하는 요소가 있을 것 - 위 사진에서처럼 음각으로 만든 홈.
  2. 경계값이 있을 것 - 12시 방향 기준으로 -140도~140도까지
  3. 클릭 후 드래그 시만 움직이고 즉각적으로 반응
정도로 잡고 일단 SVG로 원을 두개 그려본다.

      <circle cx="6em" cy="6em" r="5em"/>
      <circle class="handle" cx="6em" cy="3em" r="0.6em"/>

단위는 서로 다른 디바이스에서도 잘 보이도록 em을 써서 만든다. 1em은 글자 한글자 높이정도의 단위인데 해상도에 대한 걱정을 덜어줘서 참 편하다.
em은 이정도, 즉 1em=1글자 높이

이걸 돌려주기만 하면 된다.
돌리는 건 뭘로? css transform / rotate 를 사용한다.
두 개의 원을 그룹으로 묶어서 돌리자.

    <g class="jog" style="-webkit-transform: rotate3d(0,0,1,{{knob}}deg); transform: rotate3d(0,0,1,{{knob}}deg)">
      <circle cx="6em" cy="6em" r="5em"/>
      <circle class="handle" cx="6em" cy="3em" r="0.6em"/>
    </g>

g를 이용해 두 개의 원을 한번에 transform(-webkit-transform)으로 회전한다. 어짜피 Blaze(http://meteor.github.io/blaze/)를 사용할꺼니까 은근 슬적 {{konb}}을 끼워넣었다 ㅋㅋㅋㅋ
가로 세로 정중앙을 중심으로 회전할 것이므로 CSS에서 .jog {transform-origin: 50% 50%;} 필수체크!
여기서 주목할 것은 rotate3d를 사용한 것인데, 이는 css3 가속이 3d에서만 이루어지기 때문.
애플이 어서 Safari 를 업뎃하든 포기하든 해서 저 -webkit prefix를 그만 썼으면 하는 바램이다.

외견은 어쨌든 되었고 이제 저걸 돌려봐야하는데 마우스나 터치를 하고 있는 좌표가 큰 원의 중심으로부터 수평선을 그엇을 때 몇 도나 나오는지 알아내는 것이 관건.
Math.atan2를 사용하여 구해보자.

x,y만 알면 간단하게 구할 수 있다.
저 값은 Radian 값인데 이 Radian이 무엇인고 하니

이런 거.
너무 움짤 설명이 훌륭해서 더 이상 자세한 설명은 생략한다.
파이 3.14 라디안이 180도이므로 rad * 180 / Math.PI 를 하면 계산이 딱 나온다.
12시 방향 기준이니 90도 보정을 해주고 Blaze로 {{knob}}에 꽂아주기만 하면 끝.

터치인터페이스에서 해보니 손가락이 핸들을 가릴 수 있어서 좀 더 UI를 강화해야할 필요를 느꼈다.
큰 원엔 바깥쪽 그림자, 작은 원엔 안쪽 그림자를 적용해보고 이 전체를 회전하자.

defs 로 필터를 일단 정의하자.

  <defs>
      <filter id="f3" x="-10%" y="-5%" width="210%" height="205%">
        <feoffset result="offOut" in="SourceAlpha" dx="0" dy="2" />
        <fegaussianblur result="blurOut" in="offOut" stddeviation="1" />
        <feblend in="SourceGraphic" in2="blurOut" mode="normal" />
      </filter>
  </defs>

fegaussianblur 를 사용하여 gaussain blur(https://en.wikipedia.org/wiki/Gaussian_blur)로 만든다음에 feoffset으로 아래쪽으로 2만큼 번지게 하였다.

이걸 큰쪽 바닥이 되는 원에 적용하자.
      <circle class="floor" cx="6em" cy="6em" r="5em" filter="url(#f3)"/>

마찬가지로 f4 를 만들어서 위쪽으로 2만큼 번지게 변화를 줘보자.

      <filter id="f4" x="-10%" y="-30%" width="210%" height="205%">
        <feoffset result="offOut" in="SourceAlpha" dx="0" dy="-4" />
        <fegaussianblur result="blurOut" in="offOut" stddeviation="1" />
        <feblend in="SourceGraphic" in2="blurOut" mode="normal" />
      </filter>

반대쪽으로 -4만큼 깊게.

이게 전부다. 무한 회전 노브라던가 기준점이 바뀐다던가 여러가지 변화가 있으니 역시 궁리해보자.

최종 결과물은 이쪽.