CQU Web开发实验1:Web时钟的实现

1、准备工作

1.1、安装Node.js高版本

1.2、更换npm源

npm config set registry https://mirrors.huaweicloud.com/repository/npm/
npm config get registry 看是否更换成功

1.3、在项目目录安装vue脚手架

npm create vue@latest

cd vue-project1

npm install

2、实现流程

2.1、创建时钟组件

在 src/components 目录下创建一个名为 Clock.vue 的文件,并添加以下代码:

<template>
  <div class="clock">
    <!-- 画布,用于绘制时钟 -->
    <canvas ref="canvas" width="400" height="400"></canvas>
    <!-- 显示北京时间 -->
    <p>北京时间: {{ time }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';

// 定义响应式变量来存储时间和画布引用
const time = ref('');
const canvas = ref<HTMLCanvasElement | null>(null);

// 绘制时钟的函数
const drawClock = (ctx: CanvasRenderingContext2D, x0: number, y0: number) => {
  // 绘制表盘的时针刻度
  const drawHoursScale = (ctx: CanvasRenderingContext2D, x0: number, y0: number, scaleNum: number, scaleW: number, maxL: number, minL: number) => {
    for (let i = 0; i < scaleNum; i++) {
      let angle = -90 + i * (360 / scaleNum); // 角度
      let [x1, y1] = [x0 + Math.cos(angle * Math.PI / 180) * maxL, y0 + Math.sin(angle * Math.PI / 180) * maxL];
      let [x2, y2] = [x0 + Math.cos(angle * Math.PI / 180) * minL, y0 + Math.sin(angle * Math.PI / 180) * minL];
      ctx.save();
      ctx.beginPath();
      ctx.lineWidth = scaleW;
      ctx.lineCap = "round";
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
      ctx.stroke();
      ctx.closePath();
      ctx.restore();
    }
  };

  // 绘制表盘的分针刻度
  const drawMinutesScale = (ctx: CanvasRenderingContext2D, x0: number, y0: number, scaleNum: number, scaleW: number, maxL: number, minL: number) => {
    for (let i = 0; i < scaleNum; i++) {
      let angle = -90 + i * (360 / scaleNum); // 角度
      let [x1, y1] = [x0 + Math.cos(angle * Math.PI / 180) * maxL, y0 + Math.sin(angle * Math.PI / 180) * maxL];
      let [x2, y2] = [x0 + Math.cos(angle * Math.PI / 180) * minL, y0 + Math.sin(angle * Math.PI / 180) * minL];
      ctx.save();
      ctx.beginPath();
      ctx.lineWidth = scaleW;
      ctx.lineCap = "round";
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
      ctx.stroke();
      ctx.closePath();
      ctx.restore();
    }
  };

  // 绘制表盘数字刻度
  const drawNumbers = (ctx: CanvasRenderingContext2D, x0: number, y0: number, radius: number) => {
    ctx.font = "24px Arial";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    for (let i = 1; i <= 12; i++) {
      let angle = -90 + i * (360 / 12); // 角度
      let x = x0 + Math.cos(angle * Math.PI / 180) * (radius - 30);
      let y = y0 + Math.sin(angle * Math.PI / 180) * (radius - 30);
      ctx.fillText(i.toString(), x, y);
    }
  };

  // 绘制时针、分针、秒针
  const drawTimeNeedle = (ctx: CanvasRenderingContext2D, x0: number, y0: number, lineW: number, L: number, angle: number, color: string = '#000') => {
    // 计算指针终点坐标
    let [x, y] = [x0 + Math.cos(angle * Math.PI / 180) * L, y0 + Math.sin(angle * Math.PI / 180) * L];
    ctx.save();
    ctx.beginPath();
    ctx.strokeStyle = color;
    ctx.lineWidth = lineW;
    ctx.lineCap = "round";
    ctx.moveTo(x0, y0); // 指针起点
    ctx.lineTo(x, y); // 指针终点
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
  };

  // 绘制中心点
  const drawCenter = (ctx: CanvasRenderingContext2D, x0: number, y0: number) => {
    ctx.save();
    ctx.beginPath();
    ctx.arc(x0, y0, 5, 0, 2 * Math.PI);
    ctx.fillStyle = '#000';
    ctx.fill();
    ctx.closePath();
    ctx.restore();
  };

  // 绘制秒针的尾部
  const drawSecondTail = (ctx: CanvasRenderingContext2D, x0: number, y0: number, L: number, angle: number) => {
    // 计算秒针尾部终点坐标
    let [x, y] = [x0 + Math.cos(angle * Math.PI / 180) * L, y0 + Math.sin(angle * Math.PI / 180) * L];
    ctx.save();
    ctx.beginPath();
    ctx.strokeStyle = 'red';
    ctx.lineWidth = 2;
    ctx.lineCap = "round";
    ctx.moveTo(x0, y0); // 秒针尾部起点
    ctx.lineTo(x, y); // 秒针尾部终点
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
  };

  // 获取当前时间并绘制指针
  const drawTime = (ctx: CanvasRenderingContext2D, x0: number, y0: number) => {
    const now = new Date();
    const hours = now.getHours();
    const minutes = now.getMinutes();
    const seconds = now.getSeconds();
    const milliseconds = now.getMilliseconds();

    // 计算时针、分针、秒针的角度
    const hourAngle = -90 + (hours % 12) * 30 + minutes / 2;
    const minuteAngle = -90 + minutes * 6;
    const secondAngle = -90 + seconds * 6 + milliseconds * 0.006;

    // 绘制时针
    drawTimeNeedle(ctx, x0, y0, 8, 80, hourAngle, '#000');
    // 绘制分针
    drawTimeNeedle(ctx, x0, y0, 6, 120, minuteAngle, '#000');
    // 绘制秒针
    drawTimeNeedle(ctx, x0, y0, 4, 140, secondAngle, 'red');

    // 绘制秒针的尾部
    drawSecondTail(ctx, x0, y0, -20, secondAngle + 180);
  };

  // 清除画布
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

  // 绘制表盘
  drawHoursScale(ctx, x0, y0, 12, 4, 150, 130);
  drawMinutesScale(ctx, x0, y0, 60, 2, 150, 140);
  drawNumbers(ctx, x0, y0, 150);
  drawCenter(ctx, x0, y0);
  drawTime(ctx, x0, y0);
};

// 更新时间的函数
const updateTime = () => {
  const now = new Date();
  const options: Intl.DateTimeFormatOptions = {
    timeZone: 'Asia/Shanghai',
    hour12: false,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  };
  time.value = new Intl.DateTimeFormat('zh-CN', options).format(now);
};

// 组件挂载时的生命周期钩子
onMounted(() => {
  const ctx = canvas.value?.getContext('2d');
  if (ctx) {
    const x0 = ctx.canvas.width / 2;
    const y0 = ctx.canvas.height / 2;

    const render = () => {
      drawClock(ctx, x0, y0);
      updateTime();
      requestAnimationFrame(render);
    };

    render();

    onUnmounted(() => {
      cancelAnimationFrame(render);
    });
  }
});
</script>

<style scoped>
.clock {
  font-size: 2em;
  color: #2c3e50;
  text-align: center;
}

canvas {
  display: block;
  margin: 0 auto;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 50%;
}
</style>

2.2、修改App.vue

<template>
  <div id="app">
    <Clock />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import Clock from './components/Clock.vue';

export default defineComponent({
  name: 'App',
  components: {
    Clock
  }
});
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

2.3、运行

npm run dev

2.4、效果

3、代码仓库

https://github.com/mozhongzhou/vue-ts-project-total

4、参考资料

https://github.com/Mirror198829/vue-canvas

https://segmentfault.com/a/1190000017928089

本技术内容仅供学习和交流使用,如有疑问请联系qq2014160588并注明来意。请确保在使用过程中遵守相关法律法规。任何因使用本技术内容而导致的直接或间接损失,作者概不负责。用户需自行承担因使用本技术内容而产生的所有风险和责任。请勿将本技术内容用于任何非法用途。
上一篇
下一篇