search

Home  >  Q&A  >  body text

How to make the second hand on a clock only move forward?

I built an analog clock using React, but I have a problem with the second hand. The second hand starts in a clockwise direction, but once it reaches the 59-second mark, it moves counter-clockwise until it reaches the 1-second mark. After that it moves clockwise again. How to make it move continuously clockwise?

import React, { useEffect, useState } from 'react';
import './AnalogClock.css';

const AnalogClock = () => {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const interval = setInterval(() => {
      const newTime = new Date();
      setTime(newTime);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  const getRotation = (unit, max) => {
    const value = time[`get${unit}`]();
    let rotation = (value * 360) / max;

    if (unit === 'Seconds') {
      const seconds = value + time.getMilliseconds() / 1000;
      rotation = (seconds * 6) % 360;

      if (rotation < 0) {
        rotation += 360;
      }
    }

    return {
      transform: `translate(-50%, -100%) rotate(${rotation}deg)`,
    };
  };

  const renderNumbers = () => {
    const numbers = [];

    for (let i = 1; i <= 12; i++) {
      const angle = (i * 30) * (Math.PI / 180);
      const numberStyle = {
        left: `calc(50% + ${Math.sin(angle) * 140}px)`,
        top: `calc(50% - ${Math.cos(angle) * 140}px)`,
      };
      numbers.push(
        <div key={i} className="number" style={numberStyle}>
          {i}
        </div>
      );
    }

    return numbers;
  };

  return (
    <div className="clock">
      {renderNumbers()}
      <div className="hour-hand" style={getRotation('Hours', 12)}></div>
      <div className="minute-hand" style={getRotation('Minutes', 60)}></div>
      <div
        className="second-hand"
        style={
          time.getSeconds() === 59
            ? { ...getRotation('Seconds', 60), transition: 'none' }
            : getRotation('Seconds', 60)
        }
      ></div>
      <div className="center-circle"></div>
    </div>
  );
};

export default AnalogClock;

Not sure if the css helps, but here it is.

.clock {
    position: relative;
    width: 300px;
    height: 300px;
    border-radius: 50%;
    background-color: #e0e0e0;
    box-shadow: 20px 20px 40px rgba(0, 0, 0, 0.2), -20px -20px 40px rgba(255, 255, 255, 0.7);
    padding: 20px;
  }
  
  .hour-hand,
  .minute-hand,
  .second-hand {
    position: absolute;
    background-color: #333;
    transform-origin: bottom center;
    transition: transform 0.5s ease-in-out;
    box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.2), -4px -4px 8px rgba(255, 255, 255, 0.7);
  }
  
  .hour-hand {
    width: 8px;
    height: 90px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -100%) translateX(-1px);
    border-radius: 8px 8px 0 0;
  }
  
  .minute-hand {
    width: 6px;
    height: 120px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -100%) translateX(-1px);
    border-radius: 6px 6px 0 0;
  }
  
  .second-hand {
    width: 4px;
    height: 130px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -100%) translateX(-1px);
    border-radius: 4px 4px 0 0;
    background-color: #ff0000;
  }
  
  .center-circle {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 16px;
    height: 16px;
    background-color: #333;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2), -2px -2px 4px rgba(255, 255, 255, 0.7);
  }
  .number {
    position: absolute;
    font-size: 29px;
    font-weight: bold;
    color: #333;
    transform: translate(-50%, -50%);
  }

P粉952365143P粉952365143435 days ago589

reply all(1)I'll reply

  • P粉221046425

    P粉2210464252023-09-07 13:12:52

    When you try to inject js in css to achieve your goals, I recommend you to use styled components to get some benefits, take it to the next level and use js variables in css

    The main limitation of the code is that you cannot specify that the animation will continue forever, so that it restarts when 360 degrees is reached

    This can be achieved through css keyframes and animations, but without using styled components it is difficult to pass some necessary parameters to keyframes and animations, such as the initial degree and the number of seconds to complete the loop

    This is more or less how to style components

    import React, { useEffect, useState } from 'react';
    import styled, { keyframes } from 'styled-components';
    import './styles.css';
    
    const rotate = (degree) => keyframes`
      from {
        transform: translate(-50%, -100%) rotate(${degree}deg);
      }
      to {
        transform: translate(-50%, -100%) rotate(${degree+360}deg);
      }
    `
    
    const ClockStick = styled.div`
      animation: ${(props) => rotate(props.degree)} ${(props) => props.seconds}s linear;
    `
    
    const App = () => {
      const [degrees, setDegrees] = useState({});
    
      useEffect(() => {
        setDegrees({
          hour: getRotation('Hours', 12),
          minute: getRotation('Minutes', 60),
          second: getRotation('Seconds', 60)
        })
      }, []);
    
      const getRotation = (unit, max) => {
        const time = new Date();
        const value = time[`get${unit}`]();
        const rotation = (value * 360) / max;
        return rotation;
      };
    
      const renderNumbers = () => {
        const numbers = [];
    
        for (let i = 1; i <= 12; i++) {
          const angle = (i * 30) * (Math.PI / 180);
          const numberStyle = {
            left: `calc(50% + ${Math.sin(angle) * 140}px)`,
            top: `calc(50% - ${Math.cos(angle) * 140}px)`,
          };
          numbers.push(
            <div key={i} className="number" style={numberStyle}>
              {i}
            </div>
          );
        }
    
        return numbers;
      };
    
      return (
        <div className="clock">
          {renderNumbers()}
          <ClockStick className="hour-hand" seconds={216000} degree={degrees.hour}></ClockStick>
          <ClockStick className="minute-hand" seconds={3600} degree={degrees.minute}></ClockStick>
          <ClockStick
            className="second-hand"
            seconds={60}
            degree={degrees.second}
          ></ClockStick>
          <div className="center-circle"></div>
        </div>
      );
    };
    
    export default App;
    

    I removed some unnecessary logic and I will explain some of the things I added

    • Keyframes are essential to achieve this goal as they will define how the animation rotates (from initial rotation to initial rotation 360) and can also be used with animation properties, which can be helpful with ClockStick
    • ClockStick is a style component that will receive the initial degree and the number of seconds it takes for the animation to complete a loop
    • I only declared a state whose initial degree corresponds to the specific time the user ran the application
    • I then pass the number of seconds it takes for each stick (ClockStick) to complete the loop (216000 => for hours, 3600 => for minutes, 60 => for seconds)

    This is the result

    https://codesandbox.io/s/dawn-rgb-qn58t6

    Some links related to answers

    reply
    0
  • Cancelreply