Svg Clock

SvgClock.ml

module R = Reaml
module S = R.Svg

module Clock = struct
  let utcNow () =
    Js.Date.now () -. (Js.Date.getTimezoneOffset (Js.Date.make ()) *. 60.0 *. 1000.0)

  let[@reaml.component "Clock"] make () =
    let[@reaml] isRunning, setIsRunning = R.useState true in
    let[@reaml] now, setNow = R.useState (Js.Date.now ()) in
    let[@reaml] () = AnimationFrame.use (isRunning, fun _ -> setNow (utcNow ())) in
    let circle =
      S.circle [ S.cx "100"; S.cy "100"; S.r "98"; S.fill "none"; S.stroke "#1a202c" ] []
    in
    let line rotate stroke strokeWidth height =
      S.line
        [
          S.x1 "100";
          S.y1 "100";
          S.x2 (Js.Int.toString (100 - height));
          S.y2 "100";
          S.stroke stroke;
          S.strokeWidth (Js.Int.toString strokeWidth);
          S.strokeLinecap "round";
          S.transform
            ("rotate("
            ^ Js.Float.toString (Js.Math.round (rotate *. 10.0) /. 10.0)
            ^ " 100 100)");
        ]
        []
    in
    let s = now /. 1000.0 in
    let subSecondRotate = 90.0 +. (mod_float s 1.0 *. 360.0) in
    let secondRotate = 90.0 +. (mod_float s 60.0 *. 360.0 /. 60.0) in
    let minuteRotate = 90.0 +. (mod_float (s /. 60.0) 60.0 *. 360.0 /. 60.0) in
    let hourRotate = 90.0 +. (mod_float (s /. 60.0 /. 60.0) 12.0 *. 360.0 /. 12.0) in
    R.h2
      R.Style.[ display "flex"; alignItems "center"; flexDirection "column" ]
      [
        S.svg
          [ S.width "400"; S.height "400"; S.viewBox "0 0 200 200" ]
          [
            circle;
            line subSecondRotate "#e2e8f0" 10 90;
            line hourRotate "#2d3748" 4 50;
            line minuteRotate "#2d3748" 3 70;
            line secondRotate "#e53e3e" 2 90;
          ];
        R.div []
          [
            R.button
              [ R.onClick (fun _ -> setIsRunning (not isRunning)) ]
              [ R.string (if isRunning then "Stop" else "Start") ];
          ];
      ]
end

let () = Clock.make () |> R.renderTo "main"

AnimationFrame.ml

module R = Reaml

let[@reaml.hook] use (enabled, callback) =
  let[@reaml] callbackRef = R.useRef callback in
  let[@reaml] () =
    R.useEffect
      (fun () ->
        R.Ref.write callbackRef callback;
        None)
      (R._1 callback)
  in
  let[@reaml] () =
    R.useEffect
      (fun () ->
        let isRunning = ref enabled in
        let rec loop t =
          if !isRunning
          then (
            (R.Ref.read callbackRef) t;
            Webapi.requestAnimationFrame loop)
          else ()
        in
        Webapi.requestAnimationFrame loop;
        Some (fun () -> isRunning := false))
      (R._1 enabled)
  in
  ()