学习用 JS/CSS 画一个时钟
看到某君的时钟 Clock 代码,想学习怎么画一个时钟,逐重构之,把里面不合理的地方改进(例如用 js 写 css,那肯定不好)。
全部代码如下:
<html> <head> <meta charset="utf-8" /> <title>js时钟</title> <style type="text/css"> #clock { height: 300px; width: 300px; border: 1px solid #ccc; position: relative; } .point { line-height: 1px; background-color: #000; font-size: 1px; position: absolute; height: 3px; width: 3px; } .hourone { height: 8px; width: 8px; } .minuteone { height: 6px; width: 6px; background-color: #888; } .secondone { background-color: #f00; } </style> </head> <body> <div id="clock"></div> <script> var fn_sin = (angle, raduis) => Math.round(Math.sin(angle * Math.PI / 180) * raduis); var fn_cos = (angle, raduis) => Math.round(Math.cos(angle * Math.PI / 180) * raduis);var calhour = (h, m) => ((h * 60 + m) / (12 * 60)) * 360 - 90; var calaa = (m) => (m / 60) * 360 - 90; function Clock(box) { var bh = box.clientHeight, bw = box.clientWidth, centerX = bw / 2, centerY = bh / 2; this.raduis = (centerY >= centerX ? centerY : centerX) - 10; this.centerX = centerX, this.centerY = centerY; this.box = box; this.createPoint(); var hour = this.createEl(30, 'hourone'), minute = this.createEl(30, 'minuteone'), second = this.createEl(40, 'secondone'); var update = (function () { var d = new Date(); var h = Math.abs(d.getHours()), m = d.getUTCMinutes(), s = d.getSeconds(); this.set(hour, calhour(h, m), 0.5); this.set(minute, calaa(m), 0.6); this.set(second, calaa(s), 0.8); }).bind(this); update(); this.timer = setInterval(update, 1000); } Clock.prototype = { createPoint() { var _el = document.createElement('span'); _el.className = "point"; for (var angle = 0; angle < 360; angle += 30) { var el = _el.cloneNode(true); console.log(fn_cos(angle, this.raduis) ) // 角度 换算 弧度 el.style.left = this.centerX + fn_cos(angle, this.raduis) + 'px'; el.style.top = this.centerY + fn_sin(angle, this.raduis) + 'px'; this.box.appendChild(el); } }, createEl(amount, clsName) { var arr = [], _el = document.createElement('span'); _el.className = "point " + clsName; for (var i = 0; i < amount; i++) { var el = _el.cloneNode(true); this.box.appendChild(el); arr.push(el); } return arr; }, set(arr, angle, offset) { for (var i = 0, len = arr.length; i < len; i++) { var raduis = (i / len) * (this.raduis * offset); arr[i].style.left = this.centerX + fn_cos(angle, raduis) + "px"; arr[i].style.top = this.centerY + fn_sin(angle, raduis) + "px"; } } }; new Clock(document.getElementById("clock")); </script> </body> </html>
初始化变量
首先读者要懂得 CSS relative/absoulte 布局:容器定义了为 relative 布局后,自建一个内部的坐标系,我们就可以在里面把 absulte 的元素通过定义 top/left 分配坐标。
然后看看 Clock 这个类。构造参数 box 是个元素,读取其 clientHeight/clientWidth 获取高宽,分配给 bh、bw。bh/2、bw/2 自然就是中间的坐标,即变量 centerX、Y。我们还要将变量分配给当前对象一份引用,以便后面的方法读取它们,即 this.centerX = centerX, this.centerY = centerY;, box 也是如此。raduis 是半径,我们也分配给 this,即 this.raduis = (centerY >= centerX ? centerY : centerX) - 10; 减去 10 是我们希望这个时钟范围小一点,相当于内边距 padding。
绘制12刻度
目标是把12个点平均分配在圆中,如下图所示。
createPoint() 绘制了这十二个点,如下所示。
createPoint() { var _el = document.createElement('span'); _el.className = "point"; for (var angle = 0; angle < 360; angle += 30) { var el = _el.cloneNode(true); console.log(fn_cos(angle, this.raduis) ) // 角度 换算 弧度 el.style.left = this.centerX + fn_cos(angle, this.raduis) + 'px'; el.style.top = this.centerY + fn_sin(angle, this.raduis) + 'px'; this.box.appendChild(el); } },
createPoint() 中,首先创建 span 元素,这元素是 absolute 定位,通过 left/top 可指定元素坐标。怎么确定坐标呢?首先一个圆有 360 度,被刻画为 12 点,即平均划分十二份,点与点之间的夹角是 360/12 = 30 度。于是,我们写出一个 for 循环,从零度开始到 360 度,步进是 30。然后,复制 12 个点,即 _el.cloneNode(true);
因为已知半径和夹角角度,所以可以通过三角函数 sin/cos 即可求得对边和邻边长度,对应就是 x、y 的距离。不过 JS 的 Math.sin/cos 函数要求传入的参数是弧度,所以我们要转换一下角度到弧度。最后因为圆心坐标是 centerX、centerY 所以最终刻点坐标还是加起来。
var fn_sin = (angle, raduis) => Math.round(Math.sin(angle * Math.PI / 180) * raduis); var fn_cos = (angle, raduis) => Math.round(Math.cos(angle * Math.PI / 180) * raduis);
Math.round 的作用是取整。
绘制刻针
绘制黑色的时针、灰色的分针和红色的秒针,结果如下图。
虽然看上去是直线,但是仔细看还是有不少锯齿的,这是因为用一个个点构成直线的缘故,遇到有一定的角度如果点数量不够多的话,就容易出现锯齿。这里,一个点就是一个 span 元素,当然也是通过 left/top 定位。
首先是画出刻针 createEl(amount, clsName),指定 span 数量和样式类即可。
var hour = this.createEl(30, 'hourone'), minute = this.createEl(30, 'minuteone'), second = this.createEl(40, 'secondone'); …… createEl(amount, clsName) { var arr = [], _el = document.createElement('span'); _el.className = "point " + clsName; for (var i = 0; i < amount; i++) { var el = _el.cloneNode(true); this.box.appendChild(el); arr.push(el); } return arr; },
可见 createEl() 过程比较简单的,只是 for 一下然后返回元素的数组即可。
计算刻针形状
时针目前的需求是:
- 获取当前的最新时间刷新
- 刷新频率为一秒钟。每一秒钟移动三种刻针,产生的角度均不同,当然最明显发生移动变化的是秒针,一秒钟移动一次;分针也在小幅移动,不过不明显
- 三种刻针长度不一,也就是半径不一
下面结合我们的 JS 程序,解决思路如下。
- 返回最新时间,包括时分秒,简单:
var d = new Date(); var h = Math.abs(d.getHours()), m = d.getUTCMinutes(), s = d.getSeconds();
- 一秒刷新一次,也就是定时器 setInterval,简单;求出移动角度的话,通过下面函数。
var calhour = (h, m) => ((h * 60 + m) / (12 * 60)) * 360 - 90; // 时针 var calaa = (m) => (m / 60) * 360 - 90; // 分针、秒针
相比之下,求出分针的角度比较简单的,我们先说一下(即 calaa())。一小时 60 分钟,表示我们在圆上等分 60 份,即有了 m 分钟即 m * (360/60) 角度。又因刻针相对 12 点的余角的角度,故减去 90 度,得出分针之角度。一分钟等于 60 秒,于是秒针亦是同理。
相比之下,求出时针较复杂。十二个小时,圆被等分 360/60,这个没问题。然后指针的角度产生的顶点不一定是落在 1、2、3 整数上,例如一点半(1:30),指针应指向 1 点与 2 点之间的中间位置上。于是 calhour 函数除 hour 参数外还需要 min 分的参数。转化为小数 (60 h + m)/60 再乘以 360/60,最后仍要减去 90,便最后得出时分的角度。注意 calhour 括号优先级不同,不影响结果。
角度确定了,我们接着就要在顶点与圆心之间画一条直线。之前我们不是弄了点的数组么,我们此时此刻就要将它们排列成一条线。我们令其半径不一样(角度是一样的了),所以坐标也不一样。怎么计算半径呢?与数组里面的索引 i 成比例,另外还加入一定偏移量令其更好看一点。如下 set 函数所示。
set(arr, angle, offset) { for (var i = 0, len = arr.length; i < len; i++) { var raduis = i / len * this.raduis * offset; arr[i].style.left = this.centerX + fn_cos(angle, raduis) + "px"; arr[i].style.top = this.centerY + fn_sin(angle, raduis) + "px"; } }
最后的 left/top 计算就是复用前面的三角函数。
最后的最后
至此一个简单的时钟完成了。用 span 元素绘图效率还是比较低,可以考虑用 canvas 或 svg 元素改进吧!
2019-8-23 Update: 基于 canvcas 制作的,可见行数很短。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>HTML5 Canvas 圆形时钟动画</title> <style type="text/css"> #myCanvas { display: block; margin: 10px auto; } </style> </head> <body> <canvas id="myCanvas" width="400" height="400"></canvas> <script type="text/javascript"> var myCanvas = document.getElementById('myCanvas'); var c = myCanvas.getContext('2d'); function clock() { c.clearRect(0, 0, 400, 400); var data = new Date(); var sec = data.getSeconds(); var min = data.getMinutes(); var hour = data.getHours(); c.save(); c.translate(200, 200); c.rotate(-Math.PI / 2); //分钟刻度线 for (var i = 0; i < 60; i++) { //画12个刻度线 c.beginPath(); c.strokeStyle = "#f00"; c.lineWidth = 5; c.moveTo(117, 0); c.lineTo(120, 0); c.stroke(); c.rotate(Math.PI / 30); //每个6deg画一个时钟刻度线 c.closePath(); } //时钟刻度线 for (var i = 0; i < 12; i++) { //画12个刻度线 c.beginPath(); c.strokeStyle = "#000"; c.lineWidth = 8; c.moveTo(100, 0); c.lineTo(120, 0); c.stroke(); c.rotate(Math.PI / 6); //每个30deg画一个时钟刻度线 c.closePath(); } //外表盘 c.beginPath(); c.strokeStyle = "pink"; c.arc(0, 0, 145, 0, Math.PI * 2); c.lineWidth = 12; c.stroke(); c.closePath(); //画时针 hour = hour > 12 ? hour - 12 : hour; //console.log(hour); c.beginPath(); c.save(); c.rotate(Math.PI / 6 * hour + Math.PI / 6 * min / 60 + Math.PI / 6 * sec / 3600); c.strokeStyle = "yellowgreen"; c.lineWidth = 4; c.moveTo(-20, 0); c.lineTo(50, 0); c.stroke(); c.restore(); c.closePath(); //画分针 //console.log(min); c.beginPath(); c.save(); c.rotate(Math.PI / 30 * min + Math.PI / 30 * sec / 60); c.strokeStyle = "springgreen"; c.lineWidth = 3; c.moveTo(-30, 0); c.lineTo(70, 0); c.stroke(); c.restore(); c.closePath(); //画秒针 c.beginPath(); c.save(); c.rotate(Math.PI / 30 * sec); c.strokeStyle = "red"; c.lineWidth = 2; c.moveTo(-40, 0); c.lineTo(120, 0); c.stroke(); c.restore(); c.closePath(); c.restore(); } clock(); setInterval(clock, 1000); </script> </body> </html>
- 修改的一个Css + JS制作的数字时钟
- 实验楼一个很好的IT学习平台,包括JAVA,LINUX,HTML,C,CSS,JS等适合初学者
- JS入门学习,写一个时钟~
- 一个不会网页美工的人,学习CSS和JS有没有用?迷茫待解惑
- 一个学习html(dom),js,css,xml等所有web技术的好网站
- Three.JS学习 7:使用Canvas画一个时钟
- 一个JS写的WEB游戏(学习的好资源) -- 连连看
- 一个用JS作的简单的画图程序,可改画线粗细和顡色,觉得挺有意思的,所以放上来,以供相互学习
- 通过制作一个简单的时钟学习WPF中DispatcherTimer的使用 Level 100
- 全站的所有资源通用一个css 样式 边框大小 css使用js
- JS在页面中显示一个时钟
- 学习用js, ajax, php做一个简单的小黄鸡页面(调用simsimi API)
- 把JS与CSS写在同一个文件里的书写方法
- 一个CSS+JS的菜单栏DEMO(网上找的)
- IE6下js通过css隐藏select的一个bug
- CSS_DIV学习记录2(用背景颜色实现一个网页的完整布局)
- 韩顺平_轻松搞定网页设计(html+css+javascript)_第29讲_二维数组转置_js面向对象编程介绍 类(原型对象)和对象_学习笔记_源代码图解_PPT文档整理
- 学习用js, ajax, php做一个简单的小黄鸡页面(调用simsimi API)
- 学习及应用html,css,js,jqurey 的总结