1、100%原创罗盘时钟
2、设计灵感
2.1 有一天早上在抖音上看了一个类似的时钟效果,国风,感觉超级炫酷, 决定自己也写一个
3、 案例实现的思路
3.1 利用canvas 绘制 年月日时分秒的 圆环形数据
3.1.1 用360度除以 步长 得到角度 步长(月=12, 日=28/30/31 时=12 分=60 秒=60)
3.1.2 绘制旋转的文字前后 需要 使用canvs的状态保存和回复 ctx.save() 和 ctx.restore();
3.2 工具函数
3.2.1 角度转弧度: 弧度 = 角度 * Math.PI /180
3.2.2 数字转中文: 通过中文和数字映射 进行转换
3.3 封装绘制圆环数据的逻辑
3.3.1 由于绘制月、日、时、分、秒 的逻辑相同, 只有 每次绘制的个数 和圆环的直径不同, 所以变化的部分提取成 函数参数即可
3.3.2 调用函数 通过不同的入参 即可完成 所有单个圆盘的绘制
3.4 性能优化:
3.4.1 离屏渲染 和 图像合成
由于 每个圆环绘制完成后,只进行旋转操作,数据不会更新, 所以将每个环的图像 分别绘制在不同的 离屏canvas上, 在每次需要旋转时,只需将图像合成即可, 避免大量的重复绘制
3.4.2 减少不必要的计算
如、分-60秒更新一次数据、时-3600秒更新一次数据 、天-超级长... 所以动画每次轮循时, 只需检测数据有无更新,没有更新的话,就不用执行对应的逻辑,大大降低执行频率
3.4.3 在合适的时候调用执行代码
使用requestAnimationFrame()来执行动画,浏览器会让动画在浏览器空闲时执行,有助于提升性能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
background-color: #000;
overflow: hidden;
}
canvas {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: -1;
display: none;
}
#canvas {
display: block;
z-index: 1;
}
.p-box {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
transform-style: preserve-3d;
perspective: 500px;
}
.p-box .p {
position: absolute;
left: 50%;
top: 50%;
margin-left: -75px;
margin-top: -50px;
width: 150px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
color: #cc3f57;
font-size: 50px;
transform: scale(0.1);
transform-origin: center center;
}
.upup {
animation: up 2s;
}
.zan {
position: absolute;
left: 10px;
top: 20px;
font-size: 20px;
animation: resize 0.5s infinite alternate;
}
@keyframes resize {
0% {}
100% {
transform: scale(1.5);
}
}
@keyframes up {
0% {
transform: translateZ(0px);
opacity: 0.5;
scale: 0.1;
}
100% {
transform: translateZ(600px);
opacity: 0.1;
scale: 1;
}
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div class="p-box">
<div class="p"></div>
<div class="p"></div>
<div class="p"></div>
<div class="p"></div>
</div>
<div class="zan">👍</div>
<script>
let date = new Date();
let y = date.getFullYear();
let m = date.getMonth() + 1;
let d = date.getDate();
let h = date.getHours();
let min = date.getMinutes();
let s = date.getSeconds();
const colors = ["#daa520", "#FFAE57", "#FF7853", "#EA5151", "#CC3F57"];
const bgColor = "#2E2733";
let types = {
month: {
length: 12,
color: colors[0],
unit: "月",
w: 300,
},
date: {
length: new Date(
new Date(`${y}-${m + 1}-1`) - 24 * 3600 * 1000
).getDate(),
color: colors[1],
unit: "日",
w: 450,
},
hour: {
length: 24,
color: colors[2],
unit: "时",
w: 600,
},
minute: {
length: 60,
color: colors[3],
unit: "分",
w: 750,
},
second: {
length: 60,
color: colors[4],
unit: "秒",
w: 900,
},
};
let data = {
m: {
num: 0,
st: 30,
cRadain: 0,
tRadain: 0,
},
d: {
num: 0,
st: 360 / types.date.length,
cRadain: 0,
tRadain: 0,
},
h: {
num: 0,
st: 360 / 24,
cRadain: 0,
tRadain: 0,
},
min: {
num: 0,
st: 6,
cRadain: 0,
tRadain: 0,
},
s: {
num: 0,
st: 6,
cRadain: 0,
tRadain: 0,
},
};
const getRadian = function (angle) {
return (angle * Math.PI) / 180;
};
const nth = (number) => {
let numReg = /\D/;
if (numReg.test(number) || number.length >= 14) return;
let zhArray = [
"零",
"一",
"二",
"三",
"四",
"五",
"六",
"七",
"八",
"九",
"十",
];
let baseArray = [
"",
"十",
"百",
"千",
"万",
"十",
"百",
"千",
"亿",
"十",
"百",
"千",
"万",
];
let string = String(number)
.split("")
.reverse()
.map((item, index) => {
item =
Number(item) == 0
? zhArray[Number(item)]
: zhArray[Number(item)] + baseArray[index];
return item;
})
.reverse()
.join("");
string = string.replace(/^一十/, "十");
string = string.replace(/零+/, "零");
string = string.replace(/(.+)(零)$/, "$1");
return string;
};
function createCs(w, h) {
let cs = document.createElement("canvas");
cs.width = w;
cs.height = h;
const ctx = cs.getContext("2d");
let center = {
x: cs.width / 2,
y: cs.height / 2,
};
document.body.appendChild(cs);
return {
cs,
ctx,
center,
w,
};
}
function createMark(
{ cs, ctx, center, w },
{ length, unit, color = "#fff" }
) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.arc(center.x, center.y, w / 2, 0, Math.PI * 2);
ctx.stroke();
console.log("w", w);
const agStep = 360 / length;
let radio = center.x > center.y ? center.y - 10 : center.x - 10;
ctx.beginPath();
ctx.font = "14px 'Microsoft Yahei'";
ctx.textAlign = "right";
ctx.fillStyle = color;
for (let i = 0; i < length; i++) {
ctx.save();
let r = getRadian(i * agStep);
ctx.translate(center.x, center.y);
ctx.rotate(r);
ctx.fillText(`${nth(i + 1)}${unit}`, radio, 0);
ctx.restore();
}
return cs;
}
function createImg(type) {
const { w } = types[type];
let csInfo = createCs(w, w);
return createMark(csInfo, types[type]);
}
data.m.cs = createImg("month");
data.d.cs = createImg("date");
data.h.cs = createImg("hour");
data.min.cs = createImg("minute");
data.s.cs = createImg("second");
let cs = document.querySelector("#canvas");
let ctx = cs.getContext("2d");
let baseWidth = 900;
cs.width = baseWidth;
cs.height = baseWidth;
getCsWidth(cs);
let csr = cs.width > cs.height ? cs.height : cs.width;
let center = {
x: cs.width / 2,
y: cs.height / 2,
};
let ps = document.querySelectorAll(".p");
function getCsWidth(cs) {
const ww = document.body.clientWidth;
const wh = document.body.clientHeight;
const w = ww > wh ? wh : ww;
const scale = w / 900;
}
function showMark(mark) {
ctx.save();
const { width, height } = mark.cs;
ctx.translate(center.x, center.y);
if (mark.cRadain < mark.tRadain) {
mark.cRadain += mark.step;
}
ctx.rotate(-getRadian(mark.cRadain));
const sx = -width / 2;
const sy = -height / 2;
ctx.drawImage(mark.cs, 0, 0, width, height, sx, sy, width, height);
ctx.restore();
}
function createTitle() {
ctx.beginPath();
ctx.fillStyle = "#FFAE57";
ctx.font = "20px 'Microsoft Yahei' ";
ctx.textAlign = "center";
ctx.fillText(`${y}年`, center.x, center.y);
ctx.strokeStyle = "#FFAE57";
ctx.beginPath;
ctx.lineWidth = 1;
ctx.moveTo(center.x, center.y + 10);
ctx.lineTo(center.x + 440, center.y + 10);
ctx.stroke();
}
function update(type, data, now) {
now = now - 1;
if (now !== data[type].num) {
let dis = now - data[type].num;
data[type].tRadain = now * data[type].st;
data[type].cRadain = data[type].num * data[type].st;
data[type].step = (dis * data[type].st) / 15;
data[type].num = now;
}
}
let lastS ;
function step() {
date = new Date();
y = date.getFullYear();
m = date.getMonth() + 1;
d = date.getDate();
h = date.getHours();
min = date.getMinutes();
s = date.getSeconds();
types.date.length = new Date(
new Date(`${y}-${m + 1}-1`) - 24 * 3600 * 1000
).getDate();
update("s", data, s);
update("min", data, min);
update("h", data, h);
update("d", data, d);
update("m", data, m);
ctx.clearRect(0, 0, cs.width, cs.height);
createTitle();
showMark(data.s);
showMark(data.min);
showMark(data.h);
showMark(data.d);
showMark(data.m);
console.log('ssss->:', s);
if (!lastS || lastS !== s) {
lastS = s;
let pindex = s % 4;
ps[pindex].innerHTML = nth(s);
ps[pindex].classList.remove('upup');
setTimeout(()=> {
ps[pindex].classList.add('upup');
})
}
requestAnimationFrame(step);
}
requestAnimationFrame(step);
window.onresize = function () {
getCsWidth(cs);
};
</script>
</body>
</html>