Commit ff6945a3 authored by hucy's avatar hucy

canvas绘制时钟

parent 332e1ad4
......@@ -2,198 +2,378 @@
* canvas学习
-->
<script setup lang="ts">
import { ref, onMounted } from 'vue';
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;
}
});
const transX = ref(-900);
const transY = ref(0);
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();
let canvas2: any = document.getElementById('canvas2');
let pen2 = canvas2.getContext('2d');
pen2.save();
pen2.beginPath();
pen2.beginPath();
// drawGrid('green', 1200, 600, pen2);
pen2.restore();
// const width = 600; // canvas宽
// const angle = 30; // 角度值
// const arc = (angle * Math.PI) / 180; // 弧度值
// 中心点坐标
// const center = {
// x: 300,
// y: 300,
// };
// 绘制网格
function drawGrid(color: string, w: number, h: number, pen: any) {
const step = 100;
const w_l = w / step;
const h_l = h / step;
// 横着的线
for (let i = 0; i <= h_l; i++) {
pen.save();
// 大刻度
for (let i = 0; i < 12; i++) {
pen.beginPath();
pen.strokeStyle = color;
pen.moveTo(0, i * step);
pen.lineTo(w, i * step);
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();
}
// 竖着的线
for (let i = 0; i <= w_l; i++) {
pen.restore();
// 小刻度
pen.save();
for (let i = 0; i < 60; i++) {
pen.beginPath();
pen.moveTo(i * step, 0);
pen.lineTo(i * step, h);
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();
drawGrid('#ca3e47', 1200, 600, pen);
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();
// 正弦曲线
// 圆心坐标
const center = {
x: 100,
y: 300,
};
const r = 100; // 半径
pen.save();
pen.beginPath();
pen.arc(center.x, center.y, r, 0, 2 * Math.PI);
pen.strokeStyle = '#ffb549';
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, center.y);
pen.lineTo(2 * r, center.y);
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();
// 画圆弧
function drawArc(arc: number) {
pen.save();
pen.beginPath();
const a_side1 = Math.sin(arc) * r;
const b_side1 = Math.cos(arc) * r;
const x1 = center.x + b_side1;
const y1 = center.y + a_side1;
const abs_step = 2 * r - x1;
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();
}
pen.save();
// 绘制网格
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.moveTo(x1, y1);
pen.lineTo(300 + abs_step, y1);
pen.strokeStyle = 'blue';
pen.strokeStyle = color;
pen.moveTo(0, i * step);
pen.lineTo(w, i * step);
pen.stroke();
pen.restore();
// 正弦点
pen2.beginPath();
pen2.arc(300 - transX.value, y1, 10, 0, 2 * Math.PI);
pen2.stroke();
// 移动
transX.value += 10;
// 能够显示的区域长度
const zoneLenth = 200;
// (0-900)是初始的transX.value
const sideP = 0 - 900 + zoneLenth;
if (transX.value > sideP) {
const lens1 = transX.value - sideP;
// 1200是pen2画布的宽度
const clearX = 1200 - lens1;
// 600是pen2画布的高度
pen2.clearRect(clearX, 0, lens1, 600);
}
}
// 竖着的线
for (let i = 0; i <= w_l; i++) {
pen.beginPath();
pen.moveTo(i * step, 0);
pen.lineTo(i * step, h);
pen.stroke();
}
pen.restore();
}
return {
x: x1,
y: y1,
};
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);
}
}
// let i = 24;
// setInterval(() => {
// if (i < 0) {
// i = 23;
// }
// drawArc((i * 15 * Math.PI) / 180);
// i--;
// if (transX.value > 300) {
// transX.value = 0;
// pen2.clearRect(0, 0, 1200, 600);
// }
// }, 1000);
drawArc((360 * Math.PI) / 180);
drawArc((345 * Math.PI) / 180);
drawArc((330 * Math.PI) / 180);
drawArc((315 * Math.PI) / 180);
// drawArc((300 * Math.PI) / 180);
// drawArc((285 * Math.PI) / 180);
// drawArc((270 * Math.PI) / 180);
// drawArc((255 * Math.PI) / 180);
// drawArc((240 * Math.PI) / 180);
// drawArc((225 * Math.PI) / 180);
// drawArc((210 * Math.PI) / 180);
// drawArc((195 * Math.PI) / 180);
// drawArc((180 * Math.PI) / 180);
// drawArc((165 * Math.PI) / 180);
// drawArc((150 * Math.PI) / 180);
// drawArc((135 * Math.PI) / 180);
// drawArc((120 * Math.PI) / 180);
// drawArc((105 * Math.PI) / 180);
// drawArc((90 * Math.PI) / 180);
// drawArc((75 * Math.PI) / 180);
// drawArc((60 * Math.PI) / 180);
// drawArc((45 * Math.PI) / 180);
// drawArc((30 * Math.PI) / 180);
// drawArc((15 * Math.PI) / 180);
// drawArc((0 * Math.PI) / 180);
// drawArc((345 * Math.PI) / 180);
// drawArc((330 * Math.PI) / 180);
// drawArc((315 * Math.PI) / 180);
// drawArc((300 * Math.PI) / 180);
// drawArc((285 * Math.PI) / 180);
});
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="1200"
width="600"
height="600"
style="border: 1px solid red; position: absolute; top: 0; left: 0"
></canvas>
<canvas
id="canvas2"
width="1200"
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 green',
position: 'absolute',
top: 0,
left: 0,
transform: `translate(${transX}px, ${transY}px)`,
}"
style="
border: 1px solid transparent;
position: absolute;
top: 0;
left: 0;
"
></canvas>
</div>
</div>
</template>
<style lang="scss" scoped>
.box {
transform: translate(100px, 100px);
}
</style>
<style lang="scss" scoped></style>
<!--
* canvas学习-正弦曲线
-->
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const transX = ref(-900);
const transY = ref(0);
onMounted(() => {
sineCurve();
});
// 正弦曲线
function sineCurve() {
let canvas: any = document.getElementById('canvas');
let pen = canvas.getContext('2d');
let canvas2: any = document.getElementById('canvas2');
let pen2 = canvas2.getContext('2d');
pen2.save();
pen2.beginPath();
pen2.beginPath();
// drawGrid('green', 1200, 600, pen2);
pen2.restore();
// const width = 600; // canvas宽
// const angle = 30; // 角度值
// const arc = (angle * Math.PI) / 180; // 弧度值
// 中心点坐标
// const center = {
// x: 300,
// y: 300,
// };
// 绘制网格
function drawGrid(color: string, w: number, h: number, pen: any) {
const step = 100;
const w_l = w / step;
const h_l = h / step;
// 横着的线
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.save();
drawGrid('#ca3e47', 1200, 600, pen);
pen.restore();
// 正弦曲线
// 圆心坐标
const center = {
x: 100,
y: 300,
};
const r = 100; // 半径
pen.save();
pen.beginPath();
pen.arc(center.x, center.y, r, 0, 2 * Math.PI);
pen.strokeStyle = '#ffb549';
pen.stroke();
// 指示线
pen.beginPath();
pen.moveTo(center.x, center.y);
pen.lineTo(2 * r, center.y);
pen.lineWidth = 2;
pen.stroke();
pen.restore();
// 画圆弧
function drawArc(arc: number) {
pen.save();
pen.beginPath();
const a_side1 = Math.sin(arc) * r;
const b_side1 = Math.cos(arc) * r;
const x1 = center.x + b_side1;
const y1 = center.y + a_side1;
pen.restore();
pen.save();
pen.beginPath();
pen.moveTo(x1, y1);
pen.lineTo(300, y1);
pen.strokeStyle = 'blue';
pen.stroke();
pen.restore();
// 正弦点
pen2.beginPath();
pen2.arc(300 - transX.value, y1, 10, 0, 2 * Math.PI);
pen2.stroke();
// 移动
transX.value += 10;
// 能够显示的区域长度
const zoneLenth = 200;
// (0-900)是初始的transX.value
const sideP = 0 - 900 + zoneLenth;
if (transX.value > sideP) {
const lens1 = transX.value - sideP;
// 1200是pen2画布的宽度
const clearX = 1200 - lens1;
// 600是pen2画布的高度
pen2.clearRect(clearX, 0, lens1, 600);
}
return {
x: x1,
y: y1,
};
}
// let i = 24;
// setInterval(() => {
// if (i < 0) {
// i = 23;
// }
// drawArc((i * 15 * Math.PI) / 180);
// i--;
// if (transX.value > 300) {
// transX.value = 0;
// pen2.clearRect(0, 0, 1200, 600);
// }
// }, 1000);
drawArc((360 * Math.PI) / 180);
drawArc((345 * Math.PI) / 180);
drawArc((330 * Math.PI) / 180);
drawArc((315 * Math.PI) / 180);
drawArc((300 * Math.PI) / 180);
// drawArc((285 * Math.PI) / 180);
// drawArc((270 * Math.PI) / 180);
// drawArc((255 * Math.PI) / 180);
// drawArc((240 * Math.PI) / 180);
// drawArc((225 * Math.PI) / 180);
// drawArc((210 * Math.PI) / 180);
// drawArc((195 * Math.PI) / 180);
// drawArc((180 * Math.PI) / 180);
// drawArc((165 * Math.PI) / 180);
// drawArc((150 * Math.PI) / 180);
// drawArc((135 * Math.PI) / 180);
// drawArc((120 * Math.PI) / 180);
// drawArc((105 * Math.PI) / 180);
// drawArc((90 * Math.PI) / 180);
// drawArc((75 * Math.PI) / 180);
// drawArc((60 * Math.PI) / 180);
// drawArc((45 * Math.PI) / 180);
// drawArc((30 * Math.PI) / 180);
// drawArc((15 * Math.PI) / 180);
// drawArc((0 * Math.PI) / 180);
// drawArc((345 * Math.PI) / 180);
// drawArc((330 * Math.PI) / 180);
// drawArc((315 * Math.PI) / 180);
// drawArc((300 * Math.PI) / 180);
// drawArc((285 * Math.PI) / 180);
}
</script>
<template>
<div class="fit">
<div>canvas</div>
<div class="relative-position">
<canvas
id="canvas"
width="1200"
height="600"
style="border: 1px solid red; position: absolute; top: 0; left: 0"
></canvas>
<canvas
id="canvas2"
width="1200"
height="600"
:style="{
border: '1px solid green',
position: 'absolute',
top: 0,
left: 0,
transform: `translate(${transX}px, ${transY}px)`,
}"
></canvas>
</div>
</div>
</template>
<style lang="scss" scoped>
.box {
transform: translate(100px, 100px);
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment