<!-- * canvas 时钟 --> <script setup lang="ts"> import { onMounted, ref, reactive, onBeforeUnmount } from 'vue'; const isShowGrid = ref(false); const state = reactive({ center: { x: 300, y: 300, }, // 圆心 r: 200, // 半径, timer: null as any, canvasSeconds: null as any, }); onBeforeUnmount(() => { if (state.timer) { clearInterval(state.timer); state.timer = null; } }); onMounted(() => { state.canvasSeconds = document.getElementById('canvas-seconds'); let canvas: any = document.getElementById('canvas'); let pen = canvas.getContext('2d'); const { center, r } = state; let img = new Image(); img.src = require('./imgs/clock-bg.jpg'); img.onload = function () { // 背景色 pen.save(); pen.fillStyle = '#2d2d2d'; pen.fillRect(0, 0, 600, 600); pen.restore(); // 绘制图片 pen.save(); pen.beginPath(); pen.arc(center.x, center.y, r, 0, 2 * Math.PI); // pen.fill(); // 将上面的区域作为剪辑区域 pen.clip(); pen.drawImage(img, 241, 68, 300, 300, 100, 100, 2 * r, 2 * r); // pen.drawImage(img, -90, 80); pen.restore(); pen.save(); // 大刻度 for (let i = 0; i < 12; i++) { pen.beginPath(); const a1 = Math.sin(toArc(i * 30)) * (r - 10); // (r - 10) 10是刻度与圆弧边线的间隙 const b1 = Math.cos(toArc(i * 30)) * (r - 10); const x0 = center.x + a1; const y0 = center.y - b1; const a2 = Math.sin(toArc(i * 30)) * 8; // 8是大刻度线的长度 const b2 = Math.cos(toArc(i * 30)) * 8; const x1 = x0 - a2; const y1 = y0 + b2; pen.moveTo(x0, y0); pen.lineTo(x1, y1); pen.lineWidth = 4; pen.lineCap = 'round'; pen.strokeStyle = '#5CD8F9'; // pen.shadowColor = 'rgba(0, 0, 0, 0.35)'; // pen.shadowBlur = 10; // pen.shadowOffsetX = -8; // pen.shadowOffsetY = 10; pen.stroke(); } pen.restore(); // 小刻度 pen.save(); for (let i = 0; i < 60; i++) { pen.beginPath(); const a1 = Math.sin(toArc(i * 6)) * (r - 10); // (r - 10) 10是刻度与圆弧边线的间隙 const b1 = Math.cos(toArc(i * 6)) * (r - 10); const x0 = center.x + a1; const y0 = center.y - b1; const a2 = Math.sin(toArc(i * 6)) * 4; // 4是大刻度线的长度 const b2 = Math.cos(toArc(i * 6)) * 4; const x1 = x0 - a2; const y1 = y0 + b2; pen.moveTo(x0, y0); pen.lineTo(x1, y1); pen.lineWidth = 1; pen.lineCap = 'round'; pen.strokeStyle = '#48E6FE'; pen.stroke(); } pen.restore(); // 绘制文本 pen.save(); for (let i = 0; i < 12; i++) { pen.beginPath(); const a1 = Math.sin(toArc(i * 30)) * (r - 40); const b1 = Math.cos(toArc(i * 30)) * (r - 40); const num = i || 12; const x0 = center.x + a1; const y0 = center.y - b1; pen.font = '26px sans-serif'; pen.textAlign = 'center'; pen.textBaseline = 'middle'; pen.fillStyle = '#5CD8F9'; pen.fillText(`${num}`, x0, y0); } pen.restore(); // 外圆 pen.save(); pen.beginPath(); pen.arc(center.x, center.y, r, 0, 2 * Math.PI); pen.lineWidth = 10; pen.strokeStyle = '#0E4A6F'; // 阴影 pen.shadowColor = 'rgba(0, 0, 0, 0.35)'; pen.shadowBlur = 20; pen.shadowOffsetX = -12; pen.shadowOffsetY = 20; pen.stroke(); pen.restore(); // 圆心 pen.save(); pen.beginPath(); pen.arc(center.x, center.y, 16, 0, 2 * Math.PI); pen.fillStyle = '#204969'; pen.shadowColor = 'rgba(0, 0, 0, 0.35)'; pen.shadowBlur = 5; pen.shadowOffsetX = -10; pen.shadowOffsetY = 10; pen.fill(); pen.restore(); state.timer = setInterval(() => { drawSeconds(); }, 1000); }; }); function drawSeconds() { let pen = state.canvasSeconds.getContext('2d'); const { center, r } = state; const date = getDate(); // console.log('---', date); const secondsDeg = date.seconds * 6 + 270; const minDeg = date.minutes * 6 + 270 + date.seconds * 0.1; const hoursDeg = date.hours * 30 + 270 + date.minutes * 0.5 + date.seconds * (1 / 120); pen.clearRect(0, 0, 600, 600); // 时间显示 pen.save(); pen.font = '26px sans-serif'; pen.textAlign = 'center'; pen.textBaseline = 'middle'; pen.fillStyle = '#5CD8F9'; const hStr = date.hours < 10 ? `0${date.hours}` : date.hours; const mStr = date.minutes < 10 ? `0${date.minutes}` : date.minutes; const sStr = date.seconds < 10 ? `0${date.seconds}` : date.seconds; const timeStr = `${hStr}:${mStr}:${sStr}`; pen.fillText(timeStr, center.x, center.y - (2 / 5) * r); pen.restore(); // 时针 pen.save(); const offseHour = rotateOriginOffset(600, center, toArc(hoursDeg)); pen.translate(offseHour.x, offseHour.y); // 改变旋转中心 pen.rotate(toArc(hoursDeg)); pen.beginPath(); pen.moveTo(center.x - 20, center.y); pen.lineTo(center.x + r - 110, center.y); pen.lineWidth = 9; pen.lineCap = 'round'; pen.strokeStyle = '#1E88D2'; pen.shadowColor = 'rgba(0, 0, 0, 0.35)'; pen.shadowBlur = 5; pen.shadowOffsetX = -10; pen.shadowOffsetY = 10; pen.stroke(); pen.restore(); pen.save(); pen.beginPath(); pen.arc(center.x, center.y, 12, 0, 2 * Math.PI); pen.fillStyle = '#71C6F7'; pen.shadowColor = 'rgba(0, 0, 0, 0.35)'; pen.shadowBlur = 2; pen.shadowOffsetX = -2; pen.shadowOffsetY = 2; pen.fill(); pen.restore(); // 分针 pen.save(); const offsetMin = rotateOriginOffset(600, center, toArc(minDeg)); pen.translate(offsetMin.x, offsetMin.y); // 改变旋转中心 pen.rotate(toArc(minDeg)); pen.beginPath(); pen.moveTo(center.x - 20, center.y); pen.lineTo(center.x + r - 80, center.y); pen.lineWidth = 5; pen.lineCap = 'round'; pen.strokeStyle = '#71C6F7'; pen.shadowColor = 'rgba(0, 0, 0, 0.35)'; pen.shadowBlur = 5; pen.shadowOffsetX = -10; pen.shadowOffsetY = 10; pen.stroke(); pen.restore(); // 秒针 pen.save(); const offset = rotateOriginOffset(600, center, toArc(secondsDeg)); pen.translate(offset.x, offset.y); // 改变旋转中心 pen.rotate(toArc(secondsDeg)); pen.beginPath(); pen.moveTo(center.x - 40, center.y); pen.lineTo(center.x + r - 10, center.y); pen.lineWidth = 2; pen.lineCap = 'round'; pen.strokeStyle = '#71C6F7'; pen.shadowColor = 'rgba(0, 0, 0, 0.35)'; pen.shadowBlur = 5; pen.shadowOffsetX = -10; pen.shadowOffsetY = 10; pen.stroke(); pen.restore(); pen.save(); pen.beginPath(); pen.arc(center.x, center.y, 5, 0, 2 * Math.PI); pen.fillStyle = '#204969'; pen.shadowColor = 'rgba(0, 0, 0, 0.35)'; pen.shadowBlur = 2; pen.shadowOffsetX = -2; pen.shadowOffsetY = 2; pen.fill(); pen.restore(); } // 绘制网格 function drawGrid(color: string, w: number, h: number, pen: any) { const step = 100; const w_l = w / step; const h_l = h / step; pen.save(); // 横着的线 for (let i = 0; i <= h_l; i++) { pen.beginPath(); pen.strokeStyle = color; pen.moveTo(0, i * step); pen.lineTo(w, i * step); pen.stroke(); } // 竖着的线 for (let i = 0; i <= w_l; i++) { pen.beginPath(); pen.moveTo(i * step, 0); pen.lineTo(i * step, h); pen.stroke(); } pen.restore(); } function showGrid() { let canvas: any = document.getElementById('canvas-grid'); let pen = canvas.getContext('2d'); isShowGrid.value = !isShowGrid.value; if (isShowGrid.value) { drawGrid('#FD7013', 600, 600, pen); // 网格 } else { pen.clearRect(0, 0, 600, 600); } } function toArc(degree: number) { return (degree * Math.PI) / 180; } function rotateOriginOffset( width: number, center: { x: number; y: number }, arc: any ) { const r1 = width - center.x; const xRes1 = Math.cos(arc) * r1; const yRes1 = Math.sin(arc) * r1; const x1 = center.x + xRes1; const y1 = center.y + yRes1; const x0 = width; const y0 = center.y; const c0 = Math.sqrt(Math.pow(x0, 2) + Math.pow(y0, 2)); const arc0 = Math.atan2(y0, x0); const arc_0 = arc0 + arc; const y2 = Math.sin(arc_0) * c0; const x2 = y2 / Math.tan(arc_0); const xLength = x1 - x2; const yLength = y1 - y2; return { x: xLength, y: yLength, }; } function getDate() { const myDate = new Date(); return { hours: myDate.getHours(), minutes: myDate.getMinutes(), seconds: myDate.getSeconds(), }; } </script> <template> <div class="fit"> <div>canvas</div> <div> <q-btn label="显示网格" color="primary" @click="showGrid" /> </div> <div class="relative-position"> <canvas id="canvas" width="600" height="600" style="border: 1px solid red; position: absolute; top: 0; left: 0" ></canvas> <canvas id="canvas-seconds" width="600" height="600" style=" border: 1px solid transparent; position: absolute; top: 0; left: 0; " ></canvas> <canvas id="canvas-grid" width="600" height="600" style=" border: 1px solid transparent; position: absolute; top: 0; left: 0; " ></canvas> </div> </div> </template> <style lang="scss" scoped></style>