반응: 아날로그 시계의 초침을 부드럽게 움직입니다.

14864 단어
아날로그 시계에 대한 마지막 글에서 나는 그것을 만드는 방법을 보여주었다. 초침이 초에서 초로 건너뛰는 것뿐이지만 부드럽게 움직이도록 하고 싶었습니다.

초침 코드:

const Seconds = styled(Hours).attrs<DateProps>(({ time }) => ({
  style: { transform: `rotateZ(${time.getSeconds() * 6}deg)` },
}))<DateProps>`
  background-color: red;
  height: 50px;
  left: calc(50% - 0.5px);
  top: 5px;
  width: 1px;
`


첫 시도



내 즉각적인 생각은 단순히 CSS에 transition: transform 1s linear;를 추가하는 것이 었습니다.

const Seconds = styled(Hours).attrs<DateProps>(({ time }) => ({
  style: { transform: `rotateZ(${time.getSeconds() * 6}deg)` },
}))<DateProps>`
  background-color: red;
  height: 50px;
  left: calc(50% - 0.5px);
  top: 5px;
  transition: transform 1s linear;
  width: 1px;
`


그거였다! 작업 완료...

불행히도.

처음에는 손이 59초에서 0으로 이동할 때까지 좋아 보였습니다. 계산은 어떻게 됩니까?

59 * 6 = 354
0 * 6 = 0


이것은 손이 354도에서 360도로 움직이는 것이 아니라 0도로 움직이는 것을 의미합니다. 이것은 거꾸로 달린다는 것을 의미합니다. 1초에 0으로 돌아가는 완전한 회전. 이것은 다소 재미있을 수 있지만 그것은 내가 원했던 것이 아닙니다(또는 누구나 시계에서 기대할 것입니다).

또한 여기에는 또 다른 작은 문제가 있습니다. 손이 1초 뒤에 있습니다. 생각해보세요:
한 위치에서 다른 위치로 점프할 때 손은 즉시 목표 위치(현재 초의 위치)에 있습니다. 1초 이내에 전환하면 이전 초에서 시작하여 현재 위치에 도달하는 데 1초가 걸립니다. 어쨌든 이것은 time.getSeconds() 에 1을 추가하여 쉽게 해결할 수 있습니다.

하지만 우리는 더 잘할 수 있습니다.

솔루션에 대한 생각



마음에 떠오른 첫 번째 솔루션으로 점프한 후 이를 수행할 수 있는 다른 가능한 방법에 대해 생각하기 시작했습니다.
time.getSeconds()를 사용하지 않고 Date.now()만 사용하면 어떻게 됩니까? 그것은 1970년 1월 1일 이후의 밀리초를 얻을 것입니다. 그것을 1000으로 나누고 그것을 사용하는 것이 트릭을 할 것입니다.

불행히도 그렇게 쉬운 일이 아닙니다. 생성된 div는 다음과 같습니다.

<div class="sc-cxabCf bIucpB" style="transform: rotateZ(9.96329e+09deg);"></div>


숫자가 너무 컸습니다. 또한 시작하기 위해 9963290976도와 같은 것을 사용하는 것이 좋은 습관인지 확실하지 않습니다.

내가 생각한 다른 솔루션은 다음과 같습니다.
  • 24시간마다 값을 0으로 재설정하는 모듈로 86400을 추가합니다. 그런 다음 손의 반전은 자정에 하루에 한 번만 발생합니다. 더 좋지만 반전을 전혀 원하지 않았습니다.
  • 시작 순간을 저장하고 지금과 시작 사이의 차이를 계산합니다( Date.now() - start ). 이 솔루션은 합리적인 것 같아서 계속 시도했습니다.

  • 시간 델타 사용



    시작 시간과 지금 사이의 차이를 계산하려면 시작 지점을 어딘가에 저장해야 했습니다. 이것은 전혀 변경되어서는 안되며 재렌더링을 다시 트리거하지 않아야 하므로 이를 위해 ref를 사용하고 있습니다.

    const DemoClock: React.FC = () => {
      const [time, setTime] = useState(() => new Date())
      const start = useRef(time)
    
      useEffect(() => {
        const interval = setInterval(() => {
          const now = new Date()
          if (time.getSeconds() !== now.getSeconds()) {
            setTime(now)
          }
        }, 250)
    
        return () => clearInterval(interval)
      }, [time])
    
      const seconds = Math.floor((time.getTime() - start.current.getTime()) / 1000) 
        + start.current.getSeconds() + 1
    
      return (
        <Clock>
          <Hours time={time} />
          <Minutes time={time} />
          <Seconds seconds={seconds} />
        </Clock>
      )
    }
    


    이것은 잘 작동합니다. 하지만 한 가지는 여전히 저를 괴롭혔습니다. 학위는 점점 더 커지고 있습니다. 이것이 문제라고 생각하지 않지만 이러한 큰 숫자가 필요하지 않은 솔루션을 시도하고 싶었습니다.

    웹 애니메이션 API 사용



    나는 이것을 끝내었다 :

    const DemoClock: React.FC = () => {
      const [time, setTime] = useState(() => new Date())
    
      const secondsRef = useRef<HTMLDivElement>(null)
    
      useEffect(() => {
        const interval = setInterval(() => {
          const now = new Date()
          if (time.getSeconds() !== now.getSeconds()) {
            setTime(now)
          }
        }, 250)
    
        return () => clearInterval(interval)
      }, [time])
    
      useLayoutEffect(() => {
        if (!secondsRef.current) {
          return
        }
    
        secondsRef.current.animate(
          [
            {
              transform: `rotateZ(${(time.getSeconds() * 6}deg)`,
            },
            {
              transform: `rotateZ(${(time.getSeconds() + 1 * 6}deg)`,
            },
          ],
          {
            duration: 1000,
            fill: 'both',
            iterations: 1,
          }
        )
      }, [time])
    
      return (
        <Clock>
          <Hours time={time} />
          <Minutes time={time} />
          <Seconds time={time}  ref={secondsRef} />
        </Clock>
      )
    }
    


    각 단계의 시작과 끝을 정의함으로써 알 수 있듯이 우리는 결코 뒤로 물러서야 하는 상황에 빠지지 않습니다. 또한 손을 미치광이처럼 돌리지 않고 언제든지 시간을 변경할 수 있으며 360도보다 큰 각도를 사용할 필요가 없습니다. 저에게는 이것이 이상적인 솔루션처럼 느껴졌습니다.

    당신의 해결책은 무엇입니까? 나는 명백한 것을 놓쳤습니까? 알려줘요.


    사진 제공: Ocean Ng on Unsplash

    좋은 웹페이지 즐겨찾기