숫자판이 틱,틱 소리를 내며 가벼이 넘어간다. 지금 이 순간 흐르는 시간이 내는 소리다. 우리를 둘러싼 수 많은 장치들이 아날로그에서 디지털로 바뀌어 버린것도 이젠 오래 전이 되어버렸다. 시대가 변하고 우리는 새로운 세상에 완전히 적응해버린 듯 해도 문득 아날로그만의 독특한 감성의 결이 그리워진다.
스플릿 플랩 디스플레이(Split-Flap Display)라고 불리는 우리에게 익숙한 이 기계적 표현 방식은 실용성과는 거리가 있지만 디지털 인터페이스 시대에도 즐겨 사용되는 UI 중 하나이다.
스플릿 플랩 디스플레이로 표현된 시계는 어떻게 만들 수 있을까? 호기심이 나를 이끌었었다. 한 해 전 취미 삼아 가볍게 배웠던 자바스크립트로 만든 나의 작고 보잘 것 없는 장난감을 소개한다.
지금 보면 바보같은 코드이기 때문에 혹여라도 참고하지 않으셨으면 좋겠다.
이 글은 '제가 이런걸 만들었습니다 여러분!' 보단 '이런걸 만들기도 했었지요.'에 가깝다
메커니즘은 매우 간단하다. 구동 축이 동일한 방향으로, 매 초마다 입력을 받아서 설정된 각도만큼 회전하며, 궤도에 연결된 숫자판(Flap)이 하나씩 뒤집어지면서 다음 숫자를 보여준다.
각 숫자판은 반으로 쪼개져 있으며, 뒷면엔 다음 숫자의 절반이 표시되어 있다.
외부에서 보여지는 물리적 동작만 화면으로 표현하기 위해 아래와 같이 규칙을 정했다.
규칙
- 각 플랩은 0부터 9까지 구성되어있으며 오름차순으로 표시된다.
- 컴포넌트 별 최대 표시값 : Hour = 24, Min = 60, Sec = 60이며 최대 값을 넘기면 상위 파트의 숫자가 증가한다. (플립 UI작동)
- 최대값을 초과하면 다시 0으로 리셋된다.
- 제일 작은 표시 단위인 오른쪽 Sec 플랩에만 Date 객체의 값을 전달하고, setInterval 함수로 구동한다.
크게 시, 분, 초로 나누고 Upper-Left / Lower-Left / Upper-Right / Lower-Right로 플랩을 나눈다.
보일지 모르겠지만 DOM node Assigning을 보면 각 플랩마다 요소가 한 개 더 있는것을 볼 수 있다.
숫자판이 내려오면서 Upper Plate는 다음 숫자가 보이고, Lower Plate는 플립 애니메이션이 끝날때 까지 현재 숫자를 유지해야 하는 엇박자를 해결해야 했다. 그래서 매 초마다 보여져야 할 숫자를 표시하는 Backplate와 플립 애니메이션 효과를 위한 Foreplate로 레이어를 나눴다.
각 숫자를 반으로 나눈 이미지들을 준비해서 매 초마다 img 태그 source의 주소값을 데이터셋을 참조하여 바꿔줄까 고민했지만, 결정적으로 이미지는 CSS 애니메이션 중
RotateX
효과에 적합하지 않았던 것으로 기억한다.
이미지 대신Number
를 그대로 표현하기로 했으며, 간결한 레터 레이아웃을 위해 Helvetica로 자간을 맞춰가며 적용했다.
지금 보면 setInterval에 욱여 넣은 무지막지한 코드 블럭이지만..
설명하자면 Date 객체로부터 시간 단위별 값을 가져오는 것으로 시작한다. 한 자리 단위일 때 앞에 무조건0
이 붙어야 해서 10시 이전에는 강제로 0을 붙였다. 그땐 24시간제로 값을 가져올 수 있는 걸 몰랐었다.
1초마다 시간을 가져오다가 타이밍이 어긋나는 경우가 있어서 Interval은 500ms 간격으로 설정했다.
let h0, h1, m0, m1, s0, s1, hDigit, minDigit, secDigit;
const realTime = setInterval(() => {
const getTime = new Date();
// HOUR
const hour = getTime.getHours();
hDigit = hour.toString();
h0 = hDigit.charAt(0);
h1 = hDigit.charAt(1);
if (hDigit < 10) {
h0 = 0;
h1 = hDigit;
}
// MIN
const min = getTime.getMinutes();
minDigit = min.toString();
m0 = minDigit.charAt(0);
m1 = minDigit.charAt(1);
if (minDigit < 10) {
m0 = 0;
m1 = minDigit;
}
// SEC
const sec = getTime.getSeconds();
secDigit = sec.toString();
s0 = secDigit.charAt(0);
s1 = secDigit.charAt(1);
if (secDigit < 10) {
s0 = 0;
s1 = secDigit;
}
}, 500);
아래는 숫자가 바뀔때마다 플랩 애니메이션을 적용하고 해제하기 위한 클래스 토글 함수다.
// FLIP UI
const flipUI = (ele1, ele2) => {
ele1.classList.remove('active');
ele2.classList.remove('active');
void ele1.offsetWidth;
void ele2.offsetWidth;
ele1.classList.add('active');
ele2.classList.add('active');
}
setInterval
의 향연..
각 단위별 로직이 조금씩 다르지만 부끄러워서 Sec 로직만 적었다. 각 숫자 단위가 어떻게 달라지는지에 따라 표현을 조건별로 분기하여 나름 애썼던 모습이 보인다.
// SEC FLIPPER
const countSec = setInterval(() => {
if (s1 != 0) {
flipUI(upFlipSecR, loFlipSecR);
} else {
flipUI(upFlipSecL, loFlipSecL);
flipUI(upFlipSecR, loFlipSecR);
}
if (s1 % 10 === 0) {
flipUI(upFlipSecL, loFlipSecL);
}
upSecL.textContent = s0;
loFlipSecL.textContent = s0;
upSecR.textContent = s1;
loFlipSecR.textContent = s1;
delaySec(s0, s1);
}, 1000)
const delaySec = function (c, d) {
setTimeout(() => {
upFlipSecL.textContent = c;
loSecL.textContent = c;
upFlipSecR.textContent = d;
loSecR.textContent = d;
}, 800);
}
자연스럽게 앞 자리 단위가 오르는 것을 보고 즐거워했던 생각이 난다. 플랩이 회전하는 효과는 CSS
rotateX
를 사용했고 원근감 효과를 위해perspective
프로퍼티를 사용했다.
시간은 쉼없이 멈추지 않고 흐른다. 하나씩 덮혀가는 플랩들을 바라보면 추억도 한꺼풀씩 다른 기억들로 덮여가는 느낌을 받는다.
글과 그림 ⓒ Wonkook Lee
🙏🏻 잘못된 정보가 있다면 지적해주세요
초반 Digit부분을 string length 체크 후 padStart로 교체해도 좋을 듯 합니다!