之前用html+css+JavaScript實現了一個簡單鐘表,但還是有一些問題,主要是一些css屬性不同浏覽器支持效果不一樣,所以嘗試用 canvas實現了一個簡單的鐘表,效果在下方,當然了,采用canvas同樣會有一些浏覽器不支持。。。 這裡只討論canvas的實現方式。^_^
html部分很簡單,寫入canvas標簽,其id設置為“canvas”,用css設置成居中顯示,代碼如下:
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<style>
canvas{display:block;margin:5px auto;}
</style>
<title>clock</title>
</head>
<body>
<canvas id='canvas'>您的浏覽器不支持canvas標簽</canvas>
<script type='text/javascript' src="外部JavaScript文件路徑"></script>
</body>
</html>
備注:需要通過script標簽的src屬性引入外部JavaScript文件
可以通過js獲取Canvas對象,設置畫布的寬高,不過繪圖工作是通過操作CanvasRenderingContext2D對象來操作的,獲取此對象的方法:
var canvas = document.getElementById('canvas'); // Canvas對象
canvas.width = 450; // 寬
canvas.height = 450; // 高
var context = canvas.getContext('2d'); // 獲取繪圖上下文環境
獲取到繪圖上下文context以後即可繪制鐘表了,整體思路是這樣的:定義一個全局變量記錄當前系統時間信息,用setInterval方法重復執行,獲取時間信息,並繪制圖像,代碼如下:
var currentDateObj; // 記錄當前時間信息
setInterval(function(){
update(); // 修改currentDateObj的內容
draw(); // 根據currentDateObj的內容繪制圖像
}, 500);
本文實現重點在於繪制圖像,關於update方法的實現,可以直接查看後邊的整體js代碼,draw方法中,主要涉及到的一些內容有:線段和圓的繪制、canvas文字渲染、canvas陰影實現、canvas圖形變換等內容
繪制線段很簡單,只需調用moveTo、lineTo方法設置線段路徑,調用stroke方法完成繪制
context.moveTo(20, 70); context.lineTo(140, 70); context.lineTo(140, 40); context.lineTo(180, 80); context.lineTo(140, 120); context.lineTo(140, 90); context.lineTo(20, 90); context.lineWidth = 2; // 設置線段寬度 context.strokeStyle = "#058"; // 設置顏色 context.stroke(); // 繪制到畫布上
運行結果:
繪制圓需要調用arc方法,然後調用stroke完成繪制,arc有5個參數,分別表示圓弧圓心的x坐標、y坐標、圓弧半徑、起始角、結束角,最後一個參數是一個布爾值,false表示順時針,true表示逆時針,下面是一些示例
for(var i = 0; i < 2; i++) {
for(var j = 0; j < 4; j++){
var b = (i == 0) ? false : true;
context.beginPath();
context.lineWidth = 2;
context.strokeStyle = '#058';
context.arc(150 * j + 75, 150 * i + 75, 45, 0, (j + 1) * Math.PI / 2, b);
context.stroke();
}
}
運行結果:
通過moveTo、lineTo或者arc方法設置好圖形路徑以後,調用stroke方法完成圖形繪制,如果需要繪制一個區域,需要調用fill方法,如上例中,可以將stroke改成fill代碼如下:
for(var i = 0; i < 2; i++) {
for(var j = 0; j < 4; j++){
var b = (i == 0) ? false : true;
context.beginPath();
context.fillStyle = '#058';
context.arc(150 * j + 75, 150 * i + 75, 45, 0, (j + 1) * Math.PI / 2, b);
context.fill();
}
}
運行結果如下:
在canvas畫布上寫段文字很簡單,基本分兩步:1.通過設置context的font屬性設置文本的樣式;2. 調用fillText或strokeText方法渲染文字
context.font = "bold 40px Arial";
context.fillStyle = '#058';
context.fillText("Canvas制作簡單鐘表", 50, 50);
context.strokeStyle = '#058';
context.strokeText("Canvas制作簡單鐘表", 50, 150);
運行結果如下:
可以設置textAlign和textBaseline屬性分別設置文本水平和垂直方向的對齊方式
canvas設置陰影主要涉及到的屬性有:shadowColor設置陰影顏色,shadowOffsetX和shadowOffsetY分別設置陰影在x和y方向上的偏移量,shadowBlur設置陰影的模糊程度
context.shadowColor = "#444"; // 陰影顏色 context.shadowOffsetX = 10; // 陰影在x方向偏移量 負數為反方向 context.shadowOffsetY = 10; // 陰影在y方向偏移量 負數為反方向 context.shadowBlur = 10; // 陰影模糊程度 context.fillStyle = "#058"; context.fillRect(100, 50, 300, 100);
運行結果如下:
canvas中最基本的圖形變換涉及到的方法是:位移 translate(x, y),旋轉 rotate(deg),縮放 scale(sx, sy),我們知道默認的坐標原點(0, 0)在canvas的左上角,但是對於我們的鐘表來說,如果能將坐標原點移動到表盤的圓心位置,處理起來將很方便,rotate將圖像旋轉制定的角度,旋 轉中心點即坐標原點,scale將圖像按制定的比例在x和y方向上進行縮放。需要注意的是,cavnas是基於狀態的,如果通過調用 translate(100, 100)方法,將坐標原點移動到(100, 100)的位置以後,如果再次調用,那麼坐標原點將在當前基礎上累加,所以在實際調用時,需要掉用save方法保存當前狀態,完成繪制以後調用 restore方法還原
本例中多次應用translate方法,設置表盤圓心為坐標原點,用rotate方法旋轉圖像,如刻度的繪制,只是繪制水平方向的一條線段,通過旋轉不同角度來達到效果
clearRect方法用於清空一個矩形空間站的圖像,語法為:context.clearRect(x, y, width, height); 第一個參數x 表示要清除的矩形左上角的x坐標,第二個參數y 表示要清除的矩形左上角的y坐標,width參數表示要清除的矩形的寬度,height參數表示要清除的矩形的高度。
在本例中,draw方法的第一步就是調用此方法,清空整個canvas畫布的內容,然後才根據currentDateObj中的內容繪制圖像。
准備工作基本做完了,下面是本例中完整的JavaScript代碼:
(function(){
var config = {
canvas : {
width : 420, // 設置canvas的寬
height : 420, // 設置canvas的高
},
clock : {
radius : 200, // 設置表盤半徑
borderWidth : 10, // 表盤邊框寬度
origin : {
radius : 8, // 中心點 半徑
color : '#333' // 中心點 顏色
},
hand : {
hour : {width : 5, length : 80}, // 時針寬度和長度
minute : {width : 2, length : 110}, // 分針的寬度和長度
second : {length : 160} // 秒針長度
}
}
};
var canvas = document.getElementById('canvas');
canvas.width = config.canvas.width;
canvas.height = config.canvas.height;
var context = canvas.getContext('2d');
var currentDateObj; // 保存當前時間
setInterval(function(){
update();
draw();
}, 500);
function update(){
currentDateObj = getDateObj();
function getDateObj() {
var week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
var d = new Date();
var year = d.getFullYear();
var month = d.getMonth() + 1;
var date = d.getDate();
var day = d.getDay();
var hour = d.getHours();
var minute = d.getMinutes();
var second = d.getSeconds();
month = (month < 9) ? '0' + month : '' + month;
date = (date < 9) ? '0' + date : '' + date;
hour = (hour < 9) ? '0' + hour : '' + hour;
minute = (minute < 9) ? '0' + minute : '' + minute;
second = (second < 9) ? '0' + second : '' + second;
var time = [hour, minute, second];
var str = year + '-' + month + '-' + date + ' ' + week[day];
return {
dateStr: str,
dateTime: time
}
}
}
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
drawBorder();
drawScale();
drawNumbers();
drawTime();
drawHand();
drawOrigin();
/**
* 繪制表盤邊框
*/
function drawBorder() {
context.save();
context.beginPath();
context.arc(canvas.width / 2, canvas.height / 2, config.clock.radius, 0, 2 * Math.PI, false);
context.arc(canvas.width / 2, canvas.height / 2, config.clock.radius - config.clock.borderWidth, 0, 2 * Math.PI, true);
context.fillStyle = "#333";
context.shadowColor = "#444";
context.shadowBlur = 10;
context.closePath();
context.fill();
context.restore();
}
/**
* 繪制中心圓點
*/
function drawOrigin() {
context.save();
context.beginPath();
context.arc(canvas.width / 2, canvas.height / 2, config.clock.origin.radius, 0, 2 * Math.PI, false);
context.fillStyle = config.clock.origin.color;
context.shadowColor = "#444";
context.shadowBlur = 3;
context.closePath();
context.fill();
context.restore();
}
/**
* 繪制表盤刻度
*/
function drawScale() {
for(var i = 0; i < 60; i++) {
var obj = {
sx : config.clock.radius - 15 - config.clock.borderWidth,
sy : 0,
ex : config.clock.radius - config.clock.borderWidth - 5,
ey : 0,
color : "#333",
width : 1
};
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(i * 6 * Math.PI / 180);
context.beginPath();
if(i % 5 == 0) {
obj.width = 3;
obj.color = "#000";
}
if(i % 15 == 0) {
obj.sx = config.clock.radius - 20 - config.clock.borderWidth;
}
context.moveTo(obj.sx, obj.sy);
context.lineTo(obj.ex, obj.ey);
context.strokeStyle = obj.color;
context.lineWidth = obj.width;
context.closePath();
context.stroke();
context.restore();
}
}
/**
* 獲取1-12數字
*/
function drawNumbers() {
var radius = config.clock.radius - config.clock.borderWidth - 40;
for(var i = 0; i < 12; i++) {
context.save();
context.beginPath();
context.translate(canvas.width / 2, canvas.height / 2);
context.font = "normal 28px Arial";
if((i + 1) % 3 == 0) {
context.font = "normal 36px Arial";
}
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText(i + 1, -radius * Math.cos((-i * 30 - 120) * Math.PI / 180), radius * Math.sin((-i * 30 - 120) * Math.PI / 180));
context.closePath();
context.restore();
}
}
/**
* 繪制下方的文字
*/
function drawTime() {
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
drawDateStr();
drawTimeBox();
drawTime();
context.restore();
/**
* 繪制 年月日星期信息
*/
function drawDateStr() {
context.beginPath();
context.font = "bold 14px Arial";
context.textAlign = 'center';
context.textBaseline = 'middle';
context.shadowColor = '#ccc';
context.shadowBlur = 2;
context.closePath();
context.fillText(currentDateObj.dateStr, 0, 40);
}
/**
* 繪制顯示時分秒的背景盒子
*/
function drawTimeBox() {
context.beginPath();
context.fillStyle = "#555";
context.shadowColor = "#444";
context.shadowBlur = 3;
context.closePath();
context.fillRect(-47, 60, 30, 30);
context.fillRect(-15, 60, 30, 30);
context.fillRect(17, 60, 30, 30);
}
/**
* 繪制時分秒數字
*/
function drawTime() {
context.beginPath();
context.font = "normal 14px Arial";
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillStyle = "#fff";
context.closePath();
context.fillText(currentDateObj.dateTime[0], -32, 75);
context.fillText(currentDateObj.dateTime[1], 0, 75);
context.fillText(currentDateObj.dateTime[2], 32, 75);
}
}
/**
* 繪制時分秒針
*/
function drawHand() {
var _hour = currentDateObj.dateTime[0] % 12;
var _minute = currentDateObj.dateTime[1];
var _second = currentDateObj.dateTime[2];
drawHourHand();
drawMinuteHand();
drawSecondHand();
// 時針
function drawHourHand() {
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(((_hour + _minute / 60) - 3) * 30 * Math.PI / 180);
context.beginPath();
context.moveTo(-12, 0);
context.lineTo(config.clock.hand.hour.length, 0);
context.lineWidth = config.clock.hand.hour.width;
context.strokeStyle = config.clock.origin.color;
context.lineCap = "round";
context.shadowColor = "#999";
context.shadowBlur = 5;
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.stroke();
context.stroke();
context.closePath();
context.restore();
}
// 分針
function drawMinuteHand() {
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate((_minute - 15) * 6 * Math.PI / 180);
context.beginPath();
context.moveTo(-18, 0);
context.lineTo(config.clock.hand.minute.length, 0);
context.lineWidth = config.clock.hand.minute.width;
context.strokeStyle = config.clock.origin.color;
context.lineCap = "round";
context.shadowColor = "#999";
context.shadowBlur = 5;
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.stroke();
context.closePath();
context.restore();
}
// 秒針
function drawSecondHand() {
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate((_second - 15) * 6 * Math.PI / 180);
context.beginPath();
context.moveTo(-35, 1.5);
context.lineTo(0, 1.5);
context.lineTo(config.clock.hand.second.length, 0.5);
context.lineTo(config.clock.hand.second.length, -0.5);
context.lineTo(0, -1.5);
context.lineTo(-35, -1.5);
context.closePath();
context.fillStyle = "#f60";
context.shadowColor = "#999";
context.shadowBlur = 5;
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.fill();
context.restore();
}
}
}
}());