clock

Vue2.6x+ Composition-API +Typescript emulated clock

The source code

The online preview

Template template

<template>
  <main class="canvas-clock">
    <canvas ref="canvasBgRef" />
    <canvas ref="canvasHourRef" />
    <canvas ref="canvasMinuteRef" />
    <canvas ref="canvasSecondRef" />
  </main>
</template>
Copy the code

Draw the logic

In fact, the drawing is simple, the initial state is 0, the difficulty is the conversion of time and Angle, that is, on the basis of 00:00:00, rotation with the origin of the center of the circle

process

  • Draw the backgrounddrawCircle
  • Drawing NumbersdrawNumber
  • Draw a pointerdrawHand
<script lang="ts">
import {
  defineComponent,
  onMounted,
  ref,
  onUnmounted,
  reactive
} from "@vue/composition-api";

const themeConfig = {
  text: "#2c3e50".bg: "#f6f6f6".hangBg: "#2c3e50".handStrokeStyle: "#eee"
};

// Simulate the clock
export default defineComponent({
  name: "Clock",
  setup() {
    const bigFont = "14pt Arial";
    const smallFont = "9pt Arial";
    const color = reactive(themeConfig);
    const canvasWidth = ref(0);
    const canvasBgRef = ref<HTMLCanvasElement>(null);
    const canvasHourRef = ref<HTMLCanvasElement>(null);
    const canvasMinuteRef = ref<HTMLCanvasElement>(null);
    const canvasSecondRef = ref<HTMLCanvasElement>(null);
    const timer = ref(0);

    onMounted((a)= > {
      drawInit();
      timer.value = setInterval(drawInit, 1000);
    });

    onUnmounted((a)= > {
      clearInterval(timer.value);
    });

    const drawInit = (a)= >{ canvasWidth.value = canvasBgRef.value! .clientWidth;const wh = window.innerWidth > 200 ? 200 : window.innerWidth;
      const pixelRatio = window.devicePixelRatio;

      const canvasList = [
        canvasBgRef,
        canvasHourRef,
        canvasMinuteRef,
        canvasSecondRef
      ];
      canvasList.forEach(canvas= >{ canvas.value! .height = wh * pixelRatio; canvas.value! .width = wh * pixelRatio;constcanvasCtx: CanvasRenderingContext2D = canvas.value! .getContext("2d")! ; canvasCtx.clearRect(0.0, canvasWidth.value, canvasWidth.value);
        canvasCtx.scale(pixelRatio, pixelRatio);
      });

      constcanvasBgCtx: CanvasRenderingContext2D = canvasBgRef.value! .getContext("2d")! ; drawCircle(canvasBgCtx); drawNumber(canvasBgCtx); drawHand(); };/ / draw circles
    const drawCircle = (canvasBgCtx: CanvasRenderingContext2D) = > {
      // ...
    };

    / / drawing number
    const drawNumber = (canvasBgCtx: CanvasRenderingContext2D) = > {
      // ...
    };

    / / pointer
    const drawHand = (a)= > {
      // ...
    };

    // Angle conversion of hours/minutes/seconds
    const getAngle = (rate: number) = > (rate / 60) * 360 * (Math.PI / 180);

    / / the shadow
    const addShadow = (ctx: CanvasRenderingContext2D) = > {
      ctx.shadowBlur = 6;
      ctx.shadowColor = "Rgba (100100100,0.6)";
      ctx.shadowOffsetX = 1;
      ctx.shadowOffsetY = 2;
    };

    // The pointer draws the style logic
    const drawHandler = (
      ctx: CanvasRenderingContext2D,
      angle: number,
      ctxDrawSelf: (a)= > void) = > {// ...
    };

    // Get the time
    // const [hour, munite, second] = getTime();
    const getTime = (a)= > {
      const time = new Date()
        .toTimeString()
        .split("") [0]
        .split(":");
      return time.map(i= > Number(i));
    };

    return{ canvasBgRef, canvasHourRef, canvasMinuteRef, canvasSecondRef }; }});</script>
Copy the code

Draw the background

/ / draw circles
const drawCircle = (canvasBgCtx: CanvasRenderingContext2D) = > {
  const cWidth = canvasWidth.value;
  / / the radius: the radius
  const drawArc = (radius: number) = >
    canvasBgCtx.arc(cWidth / 2, cWidth / 2, radius < 0 ? 0 : radius, 0.4 * Math.PI);

  / / outer ring 1
  canvasBgCtx.fillStyle = color.bg;
  canvasBgCtx.beginPath();
  drawArc(cWidth / 2 - 10);
  canvasBgCtx.fill('nonzero');
  canvasBgCtx.shadowBlur = 4;
  canvasBgCtx.shadowColor = 'rgba (100100100,0.2)';
  canvasBgCtx.shadowOffsetX = 1;
  canvasBgCtx.shadowOffsetY = 1;
  canvasBgCtx.closePath();

  / / outer ring 2
  canvasBgCtx.fillStyle = '#eee';
  canvasBgCtx.beginPath();
  drawArc(cWidth / 2 - 20);
  canvasBgCtx.fill('nonzero');
  canvasBgCtx.shadowBlur = 2;
  canvasBgCtx.closePath();

  1 / / inner circle
  canvasBgCtx.fillStyle = '#ddd';
  canvasBgCtx.beginPath();
  drawArc(25);
  canvasBgCtx.fill('nonzero');
  canvasBgCtx.closePath();

  2 / / inner circle
  canvasBgCtx.beginPath();
  drawArc(5);
  canvasBgCtx.strokeStyle = color.hangBg;
  canvasBgCtx.stroke();
  canvasBgCtx.closePath();
};
Copy the code

Drawing Numbers

/ / drawing number
const drawNumber = (canvasBgCtx: CanvasRenderingContext2D) = > {
  const cWidth = canvasWidth.value;
  canvasBgCtx.fillStyle = color.text;
  canvasBgCtx.font = bigFont;
  canvasBgCtx.fillText('12', cWidth / 2 - 10.38);

  canvasBgCtx.font = smallFont;
  canvasBgCtx.fillText('1', cWidth - 68, cWidth / 5 + 5);
  canvasBgCtx.fillText('2', cWidth - 45, cWidth / 3 + 5);

  canvasBgCtx.font = bigFont;
  canvasBgCtx.fillText('3', cWidth - 35, cWidth / 2 + 5);

  canvasBgCtx.font = smallFont;
  canvasBgCtx.fillText('4', cWidth - 45, (cWidth * 2) / 3 + 5);
  canvasBgCtx.fillText('5', cWidth - 68, (cWidth * 4) / 5 + 5);

  canvasBgCtx.font = bigFont;
  canvasBgCtx.fillText('6', cWidth / 2 - 4, cWidth - 25);

  canvasBgCtx.font = smallFont;
  canvasBgCtx.fillText('7', cWidth / 3 - 4, (cWidth * 4) / 5 + 5);
  canvasBgCtx.fillText('8', cWidth / 5 - 4, (cWidth * 2) / 3 + 5);

  canvasBgCtx.font = bigFont;
  canvasBgCtx.fillText('9'.25, cWidth / 2 + 5);

  canvasBgCtx.font = smallFont;
  canvasBgCtx.fillText('10', cWidth / 5 - 4, cWidth / 3 + 5);
  canvasBgCtx.fillText('11', cWidth / 3 - 4, cWidth / 5 + 5);
};
Copy the code

Draw a pointer

Rotate with the center of the circle as the origin

Before the rotation, translate moves down and to the right, then back again

ctx.translate(cWidth / 2, cWidth / 2);
ctx.rotate(angle);
ctx.translate(-cWidth / 2, -cWidth / 2);
Copy the code

Time and Angle conversion

Every hour, minute and second is converted according to 60

The hour hand

Angle 1 degree to radian 1 degree: math.pi / 180

The hour hand has 12 large scales, and if you subdivide it, it actually has 60 small scales, so hours *5;

And then a big scale is equal to one hour is equal to 60 minutes, so minutes times 5/60 is a small Angle

Hours + minutes = total

The total scale / 60 * 360 = the total Angle, then convert to radians and rotate with the Rotate method

const cWidth = canvasWidth.value; const [hour, minute, second] = getTime(); // clockwise const canvasHourCtx = canvashourRef.value! .getContext('2d')! ; const hourPoint = { x: cWidth / 2 - 4, y: cWidth / 2 - 8 }; addShadow(canvasHourCtx); drawHandler(canvasHourCtx, getAngle(hour * 5 + minute * (5 / 60)), () => { canvasHourCtx.moveTo(hourPoint.x, hourPoint.y); canvasHourCtx.lineTo(hourPoint.x, hourPoint.y - 25); canvasHourCtx.lineTo(hourPoint.x + 8, hourPoint.y - 25); canvasHourCtx.lineTo(hourPoint.x + 8, hourPoint.y); canvasHourCtx.lineTo(hourPoint.x, hourPoint.y); }); Const getAngle = (rate: number) => (rate / 60) * 360 * (math.pi / 180);Copy the code

Minute hand

The Angle of the minute hand is relatively simple, minutes + seconds / 60 = total scale

const cWidth = canvasWidth.value;
const [hour, minute, second] = getTime();

/ / the minute hand
constcanvasMinuteCtx = canvasMinuteRef.value! .getContext('2d')! ;const munitePoint = { x: cWidth / 2 - 2.y: cWidth / 2 - 8 };
addShadow(canvasMinuteCtx);
drawHandler(canvasMinuteCtx, getAngle(minute + second / 60), () => {
  canvasMinuteCtx.moveTo(munitePoint.x, munitePoint.y);
  canvasMinuteCtx.lineTo(munitePoint.x, munitePoint.y - 35);
  canvasMinuteCtx.lineTo(munitePoint.x + 4, munitePoint.y - 35);
  canvasMinuteCtx.lineTo(munitePoint.x + 4, munitePoint.y);
  canvasMinuteCtx.lineTo(munitePoint.x, munitePoint.y);
});
Copy the code

The second hand

The Angle of the second hand is the simplest. The second equals the scale

const cWidth = canvasWidth.value;
const [hour, minute, second] = getTime();

/ / second hand
constcanvasSecondCtx = canvasSecondRef.value! .getContext('2d')! ;const secondPoint = { x: cWidth / 2 - 1.5.y: cWidth / 2 + 10 };
addShadow(canvasSecondCtx);
drawHandler(canvasSecondCtx, getAngle(second), () => {
  canvasSecondCtx.moveTo(secondPoint.x, secondPoint.y);
  canvasSecondCtx.lineTo(secondPoint.x, secondPoint.y - 60);
  canvasSecondCtx.lineTo(secondPoint.x + 3, secondPoint.y - 60);
  canvasSecondCtx.lineTo(secondPoint.x + 3, secondPoint.y);
  canvasSecondCtx.lineTo(secondPoint.x, secondPoint.y);
});
Copy the code

The specific logic

/ / pointer
const drawHand = (a)= > {
  const cWidth = canvasWidth.value;
  const [hour, minute, second] = getTime();
  // const hour = 11;
  // const minute = 36;
  // const second = 5;

  / / hour
  constcanvasHourCtx = canvasHourRef.value! .getContext('2d')! ;const hourPoint = { x: cWidth / 2 - 4.y: cWidth / 2 - 8 };
  addShadow(canvasHourCtx);
  drawHandler(canvasHourCtx, getAngle(hour * 5 + (5 * minute) / 60), (a)= > {
    canvasHourCtx.moveTo(hourPoint.x, hourPoint.y);
    canvasHourCtx.lineTo(hourPoint.x, hourPoint.y - 25);
    canvasHourCtx.lineTo(hourPoint.x + 8, hourPoint.y - 25);
    canvasHourCtx.lineTo(hourPoint.x + 8, hourPoint.y);
    canvasHourCtx.lineTo(hourPoint.x, hourPoint.y);
  });

  / / the minute hand
  constcanvasMinuteCtx = canvasMinuteRef.value! .getContext('2d')! ;const munitePoint = { x: cWidth / 2 - 2.y: cWidth / 2 - 8 };
  addShadow(canvasMinuteCtx);
  drawHandler(canvasMinuteCtx, getAngle(minute + second / 60), () => {
    canvasMinuteCtx.moveTo(munitePoint.x, munitePoint.y);
    canvasMinuteCtx.lineTo(munitePoint.x, munitePoint.y - 35);
    canvasMinuteCtx.lineTo(munitePoint.x + 4, munitePoint.y - 35);
    canvasMinuteCtx.lineTo(munitePoint.x + 4, munitePoint.y);
    canvasMinuteCtx.lineTo(munitePoint.x, munitePoint.y);
  });

  / / second hand
  constcanvasSecondCtx = canvasSecondRef.value! .getContext('2d')! ;const secondPoint = { x: cWidth / 2 - 1.5.y: cWidth / 2 + 10 };
  addShadow(canvasSecondCtx);
  drawHandler(canvasSecondCtx, getAngle(second), () => {
    canvasSecondCtx.moveTo(secondPoint.x, secondPoint.y);
    canvasSecondCtx.lineTo(secondPoint.x, secondPoint.y - 60);
    canvasSecondCtx.lineTo(secondPoint.x + 3, secondPoint.y - 60);
    canvasSecondCtx.lineTo(secondPoint.x + 3, secondPoint.y);
    canvasSecondCtx.lineTo(secondPoint.x, secondPoint.y);
  });
};

// Angle conversion of hours/minutes/seconds
const getAngle = (rate: number) = > (rate / 60) * 360 * (Math.PI / 180);

/ / the shadow
const addShadow = (ctx: CanvasRenderingContext2D) = > {
  ctx.shadowBlur = 6;
  ctx.shadowColor = 'rgba (100100100,0.6)';
  ctx.shadowOffsetX = 1;
  ctx.shadowOffsetY = 2;
};

// The pointer draws the style logic
const drawHandler = (
  ctx: CanvasRenderingContext2D,
  angle: number,
  ctxDrawSelf: (a)= > void,
) => {
  const cWidth = canvasWidth.value;
  ctx.clearRect(0.0, cWidth, cWidth);
  ctx.beginPath();
  ctx.fillStyle = color.hangBg;
  ctx.translate(cWidth / 2, cWidth / 2);
  ctx.rotate(angle);
  ctx.translate(-cWidth / 2, -cWidth / 2);
  ctxDrawSelf();
  ctx.closePath();
  ctx.fill('nonzero');
  ctx.strokeStyle = color.handStrokeStyle;
  ctx.stroke();
};

// Get the time
// const [hour, munite, second] = getTime();
const getTime = (a)= > {
  const time = new Date()
    .toTimeString()
    .split(' ') [0]
    .split(':');
  return time.map(i= > Number(i));
};

Copy the code