JavaScript中的canvas?實(shí)現(xiàn)一個(gè)圓環(huán)漸變倒計(jì)時(shí)效果
前言
內(nèi)容:
- 效果圖
- 需求分析
- 實(shí)現(xiàn)技術(shù)
- 實(shí)現(xiàn)過程
- 全部源碼
1、效果圖展示
![(img-iSl2hFK6-1605966576712)(./images/1.png)]](http://img.jbzj.com/file_images/article/202209/2022914105853764.png)
隨著時(shí)間的減少, 圓環(huán)的紅黃色部分會(huì)慢慢的減少,圓環(huán)中的數(shù)字會(huì)變小,一直到0停止.
2、需求分析
- 可以自定義倒計(jì)時(shí)結(jié)束的時(shí)間
- 圓環(huán)的顏色是漸變的
- 倒計(jì)時(shí)的動(dòng)畫在視覺上是流暢運(yùn)行, 而不是一格一格的減少
3、實(shí)現(xiàn)的技術(shù)
| 語言 | 框架 |
|---|---|
| HTML5 | 無 |
| CSS3 | BootStrap SASS |
| JavaScript | 無 |
4、實(shí)現(xiàn)的過程
1. HTML 部分
主要用到的是 HTML5 中的畫布元素 canvas, 一共4個(gè) canvas 元素拍成一排, 每個(gè)元素都是高144px, 寬144px的正方形, 兩個(gè)圓環(huán)中的兩個(gè)小點(diǎn)部分可以使用偽元素 after 和 before 來實(shí)現(xiàn)
源碼如下:
<div class="count-down">
<div class="container">
<div class="row">
<div class="days col-lg-3 col-md-3 col-sm-3 col-xs-6">
<div class="colck">
<canvas id="days" width="144" height="144"></canvas>
</div>
</div>
<div class="hours col-lg-3 col-md-3 col-sm-3 col-xs-6">
<div class="colck">
<canvas id="hours" width="144" height="144"></canvas>
</div>
</div>
<div class="minutes col-lg-3 col-md-3 col-sm-3 col-xs-6">
<div class="colck">
<canvas id="minutes" width="144" height="144"></canvas>
</div>
</div>
<div class="seconds col-lg-3 col-md-3 col-sm-3 col-xs-6">
<div class="colck">
<canvas id="seconds" width="144" height="144"></canvas>
</div>
</div>
</div>
</div>
</div>用 BootStrap 提供的柵格布局快速的把 canvas 元素排成一排 為每個(gè) cnavas 元素設(shè)置對(duì)應(yīng)的 id, 方便用 JavaScript 獲取到該元素.
2. SASS部分
css 的部分是用 Sass 來寫的, Sass 是一個(gè) css 的 擴(kuò)展語言, html 文件中引用的是寫好的 Sass 文件編譯生成 css 文件.
源碼如下:
.count-down {
width: 100%;
.row {
padding: 0 145px;
}
.col-lg-3 {
padding: 0;
position: relative;
.colck {
width: 144px;
height: 144px;
background-color: #fff;
margin: 0 auto;
}
}
.days, .hours, .minutes {
&:after, &:before {
content: "";
display: block;
width: 12px;
height: 12px;
background-color: #fff;
position: absolute;
right: -6px;
}
&:after {
bottom: 45px;
}
&:before {
top: 45px;
}
}
}控制圓環(huán)的運(yùn)動(dòng)主要靠 JavaScript 代碼, 這里 sass 的作用是用來把把元素的位置排列好, 把兩個(gè)圓環(huán)中間的點(diǎn)畫出來,
因?yàn)楸尘吧呛谏?為了看的更加直觀, 我先把包裹 canvas 的元素背景色設(shè)為白色,
此時(shí)的效果:

3. JavaScript部分
最終的運(yùn)動(dòng)效果靠 js 代碼來實(shí)現(xiàn):
- 我們先來獲取到這4個(gè) canvas 元素
// 獲取4個(gè) canvas 元素
var days_canvas = document.getElementById('days');
var hours_canvas = document.getElementById('hours');
var minutes_canvas = document.getElementById('minutes');
var seconds_canvas = document.getElementById('seconds');- 為了可以自定義指定倒計(jì)時(shí)結(jié)束時(shí)間, 寫一個(gè)設(shè)置結(jié)束時(shí)間的函數(shù), 返回值為一個(gè) Date 對(duì)象.
// 設(shè)置倒計(jì)時(shí)時(shí)間:年 月 日 小時(shí) 分鐘 秒 毫秒
endTime = setEndTime(2020, 11, 30, 15, 0, 0);
// 設(shè)置到期時(shí)間
function setEndTime(year, month, day, hour, minute, millisecond) {
return new Date(year, month - 1, day, hour, minute, millisecond);
}js 中創(chuàng)建 Date 對(duì)象的5種方式 new Date("month dd,yyyy hh:mm:ss"); new Date("month dd,yyyy"); new Date(yyyy,mth,dd,hh,mm,ss); new Date(yyyy,mth,dd); new Date(ms);
注意: Date 對(duì)象中的月份取值是 0 - 11, 0 就表示 1 月, 用變量 endTime 保存這個(gè)設(shè)置好的 Date 對(duì)象
- 根據(jù)現(xiàn)在的時(shí)間和設(shè)置好的到期時(shí)間計(jì)算現(xiàn)在到到期時(shí)間還有多少秒,多少分鐘, 多少小時(shí), 多少天.
// 計(jì)算距離到期時(shí)間還剩下多少 days, hours, minutes
function getLeftTimeObj() {
var date = new Date();
var millisecond = date.getTime()
var end_millisecond = endTime.getTime();
if (end_millisecond < millisecond) {
return {
days: 0,
hours: 0,
minutes: 0,
seconds: 0
}
}
// 距離結(jié)束時(shí)間的秒數(shù)
var left_seconds = (( end_millisecond - millisecond ) / 1000);
var seconds = (left_seconds % 60);
var left_minutes = (left_seconds - seconds) / 60;
var minutes = (left_minutes % 60);
var left_hours = (left_minutes - minutes) / 60;
var hours = (left_hours % 24);
var left_days = ((left_hours - hours) / 24);
return {
days: left_days,
hours: hours,
minutes: minutes,
seconds: seconds
}
}
var dateObj = getLeftTimeObj();思路:
- Date 對(duì)象中有一個(gè)方法 getTime(), 返回值是從 1970 年 1 月 1 日至今的毫秒數(shù)。
- 用兩個(gè) Date 對(duì)象的 getTime() 返回值相減值再 ÷ 1000 獲得兩者相差的秒數(shù)
- 用兩者相差的秒數(shù)換算出兩者相差多少天,多少小時(shí),多少分鐘,多少秒
- 最后返回一個(gè)對(duì)象 { days: 還有多少天, hours: 還有多少個(gè)小時(shí), minutes: 還有多少分鐘, secondsL: 還有多少秒 }
我們定義一個(gè)變量 dateObj 保存這個(gè)對(duì)象, 然后根據(jù)這個(gè)對(duì)象去在相應(yīng)的 canvas 元素上畫圖
- 繪圖
我們先不考慮怎么讓圓環(huán)動(dòng)起來, 而是先考慮怎么把圖畫出來
觀察單個(gè)圖形的樣子如下圖所示:

可以把這個(gè)圖形分成4個(gè)部分:
- 灰色的整個(gè)圓環(huán)
- 紅黃漸變的不完整圓環(huán)
- 中間的數(shù)字 54
- 下面的字符串 Seconds
我們可以為寫四個(gè)方法分別完成每一個(gè)步驟:
// 畫整個(gè)圓
function drawCricle(canvas) {
// 獲取 context 對(duì)象
var context = canvas.getContext('2d');
// 獲取 canvas 的中心點(diǎn)的 x 坐標(biāo)
var centerX = canvas.width / 2;
// 獲取 canvas 的中心點(diǎn)的 y 坐標(biāo)
var centerY = canvas.height / 2;
context.save();
context.beginPath();
context.lineWidth = 7;
context.strokeStyle = "#636363";
// 圓環(huán)的寬度為 7, canvas 元素的長和寬是 144, 所以圓半徑應(yīng)為 (144 - 7) / 2, 也就是 canvas.width / 2 - 3.5
context.arc(centerX, centerY, canvas.width / 2 - 3.5 , 0, Math.PI*2, false);
context.stroke();
context.closePath();
context.restore();
}
// 畫數(shù)字下的字符
function drawStr(canvas, str) {
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
context.save();
context.fillStyle = '#fff';
context.font = "14px Arial"; //設(shè)置字體大小和字體
context.textAlign = 'center';
context.fillText(str, centerX, centerY+54);
context.restore();
}
// 畫數(shù)字
function drawNumber(canvas, num) {
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
num = num.toFixed();
num = num < 10 ? '0' + num : num;
context.save();
context.fillStyle = '#fff';
context.font = "bolder 60px Arial"; //設(shè)置字體大小和字體
context.textAlign = 'center';
// context.fontWeight = 'bold';
context.fillText(num, centerX, centerY+10);
context.restore();
}
// 畫進(jìn)度條 -0.5為起點(diǎn)
// percentage 為進(jìn)度條的百分比(0<percentage<1), 1 表示整個(gè)圓
function drawProgress(canvas, percentage) {
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var end_cricle = (percentage * 2) - 0.5;
end_cricle = Number(end_cricle.toFixed(4))
// 設(shè)置垂直漸變
var linearGrad = context.createLinearGradient(canvas.width / 2, 0, canvas.width / 2, canvas.height);
linearGrad.addColorStop(0, '#ff0503');
linearGrad.addColorStop(1, '#ff8200');
context.save();
context.strokeStyle = linearGrad; //設(shè)置描邊樣式
context.lineWidth = 7; //設(shè)置線寬
context.beginPath(); //路徑開始
context.arc(centerX, centerY, canvas.width / 2 -3.5 , -0.5 * Math.PI, end_cricle * Math.PI, false); //用于繪制圓弧context.arc(x坐標(biāo),y坐標(biāo),半徑,起始角度,終止角度,順時(shí)針/逆時(shí)針)
context.stroke(); //繪制
context.closePath(); //路徑結(jié)束
context.restore();
}// 為秒計(jì)數(shù)的 canvas 繪圖, 畫圖依賴 dateObj.seconds drawCricle(seconds_canvas); drawStr(seconds_canvas, 'Seconds'); // 每一分鐘有60秒, 用 dateObj.seconds / 60 獲得 當(dāng)前進(jìn)度 drawProgress(seconds_canvas, (dateObj.seconds / 60).toFixed(4)); drawNumber(seconds_canvas, dateObj.seconds);
寫一個(gè)繪制 4 個(gè) canvas 的方法 draw(); 然后執(zhí)行
// 繪圖
function draw() {
drawCricle(days_canvas);
drawStr(days_canvas, 'Days');
drawProgress(days_canvas, dateObj.days / 10);
drawNumber(days_canvas, dateObj.days);
drawCricle(hours_canvas);
drawStr(hours_canvas, 'Hours');
drawProgress(hours_canvas, (dateObj.hours / 24).toFixed(4));
drawNumber(hours_canvas, dateObj.hours);
drawCricle(minutes_canvas);
drawStr(minutes_canvas, 'Minutes');
drawProgress(minutes_canvas, (dateObj.minutes / 60).toFixed(4));
drawNumber(minutes_canvas, dateObj.minutes);
drawCricle(seconds_canvas);
drawStr(seconds_canvas, 'Seconds');
drawProgress(seconds_canvas, (dateObj.seconds / 60).toFixed(4));
drawNumber(seconds_canvas, dateObj.seconds);
}
drwa();然后先記得把之前設(shè)置的白色背景色去掉
執(zhí)行了 draw() 之后效果如下圖:

但是現(xiàn)在還沒有實(shí)現(xiàn)讓他動(dòng)起來,
- 讓倒計(jì)時(shí)動(dòng)起來
先明白動(dòng)起來的邏輯:
- 用定時(shí)器每過 16ms 清除一次所有 canvas 畫的圖像
- 重新獲取所剩下的時(shí)間的對(duì)象 dateObj
- 再次繪圖 draw();
// 清除 canvas 內(nèi)容
function clear() {
var days_ctx = days_canvas.getContext('2d');
var hours_ctx = hours_canvas.getContext('2d');
var minutes_ctx = minutes_canvas.getContext('2d');
var seconds_ctx = seconds_canvas.getContext('2d');
days_ctx.clearRect(0, 0, days_canvas.width, days_canvas.height);
hours_ctx.clearRect(0, 0, hours_canvas.width, hours_canvas.height);
minutes_ctx.clearRect(0, 0, minutes_canvas.width, minutes_canvas.height);
seconds_ctx.clearRect(0, 0, seconds_canvas.width, seconds_canvas.height);
}
// 讓倒計(jì)時(shí)動(dòng)起來
// 可以直接用定時(shí)器
setInterval(function() {
clear();
dateObj = getLeftTimeObj();
draw();
}, 16)
// 也可以考慮使用 requestAnimationFrame 這個(gè)方法
(function count() {
requestAnimationFrame(count);
clear();
dateObj = getLeftTimeObj();
draw();
})();到現(xiàn)在為止整個(gè)完整的功能全部實(shí)現(xiàn)了。
5、全部源碼
1.index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./bootstrap.min.css" rel="external nofollow" >
<link rel="stylesheet" href="./style.css" rel="external nofollow" >
<style>
.count-down {
background: #000;
}
</style>
</head>
<body>
<div class="count-down">
<div class="container">
<div class="row">
<div class="days col-lg-3 col-md-3 col-sm-3 col-xs-6">
<div class="colck">
<canvas id="days" width="144" height="144"></canvas>
</div>
</div>
<div class="hours col-lg-3 col-md-3 col-sm-3 col-xs-6">
<div class="colck">
<canvas id="hours" width="144" height="144"></canvas>
</div>
</div>
<div class="minutes col-lg-3 col-md-3 col-sm-3 col-xs-6">
<div class="colck">
<canvas id="minutes" width="144" height="144"></canvas>
</div>
</div>
<div class="seconds col-lg-3 col-md-3 col-sm-3 col-xs-6">
<div class="colck">
<canvas id="seconds" width="144" height="144"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>2. style.scss
.count-down {
position: absolute;
bottom: 90px;
left: 0;
width: 100%;
.row {
padding: 0 145px;
}
.col-lg-3 {
padding: 0;
position: relative;
.colck {
width: 144px;
height: 144px;
// background-color: #fff;
margin: 0 auto;
}
}
.days, .hours, .minutes {
&:after, &:before {
content: "";
display: block;
width: 12px;
height: 12px;
background-color: #fff;
position: absolute;
right: -6px;
}
&:after {
bottom: 45px;
}
&:before {
top: 45px;
}
}
}3. index.js
// 倒計(jì)時(shí)
// 到期時(shí)間
// 設(shè)置倒計(jì)時(shí)時(shí)間:年 月 日 小時(shí) 分鐘 秒 毫秒
endTime = setEndTime(2020, 11, 30, 15, 0, 0);
// 設(shè)置到期時(shí)間
function setEndTime(year, month, day, hour, minute, millisecond) {
return new Date(year, month - 1, day, hour, minute, millisecond);
}
// 獲取4個(gè) canvas 元素
var days_canvas = document.getElementById('days');
var hours_canvas = document.getElementById('hours');
var minutes_canvas = document.getElementById('minutes');
var seconds_canvas = document.getElementById('seconds');
// 計(jì)算距離到期時(shí)間還剩下多少 days, hours, minutes
function getLeftTimeObj() {
var date = new Date();
var millisecond = date.getTime()
var end_millisecond = endTime.getTime();
if (end_millisecond < millisecond) {
return {
days: 0,
hours: 0,
minutes: 0,
seconds: 0
}
}
// 距離結(jié)束時(shí)間的秒數(shù)
var left_seconds = (( end_millisecond - millisecond ) / 1000);
var seconds = (left_seconds % 60);
var left_minutes = (left_seconds - seconds) / 60;
var minutes = (left_minutes % 60);
var left_hours = (left_minutes - minutes) / 60;
var hours = (left_hours % 24);
var left_days = ((left_hours - hours) / 24);
return {
days: left_days,
hours: hours,
minutes: minutes,
seconds: seconds
}
}
var dateObj = getLeftTimeObj();
// 畫整個(gè)圓
function drawCricle(canvas) {
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
context.save();
context.beginPath();
context.lineWidth = 7;
context.strokeStyle = "#636363";
context.arc(centerX, centerY, canvas.width / 2 - 3.5 , 0, Math.PI*2, false);
context.stroke();
context.closePath();
context.restore();
}
// 畫數(shù)字下的字符
function drawStr(canvas, str) {
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
context.save();
context.fillStyle = '#fff';
context.font = "14px Arial"; //設(shè)置字體大小和字體
context.textAlign = 'center';
context.fillText(str, centerX, centerY+54);
context.restore();
}
// 畫數(shù)字
function drawNumber(canvas, num) {
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
num = num.toFixed();
num = num < 10 ? '0' + num : num;
context.save();
context.fillStyle = '#fff';
context.font = "bolder 60px Arial"; //設(shè)置字體大小和字體
context.textAlign = 'center';
// context.fontWeight = 'bold';
context.fillText(num, centerX, centerY+10);
context.restore();
}
// 畫進(jìn)度條 -0.5為起點(diǎn)
// percentage 為進(jìn)度條的百分比(0<percentage<1), 1 表示整個(gè)圓
function drawProgress(canvas, percentage) {
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var end_cricle = (percentage * 2) - 0.5;
end_cricle = Number(end_cricle.toFixed(4))
// 設(shè)置垂直漸變
var linearGrad = context.createLinearGradient(canvas.width / 2, 0, canvas.width / 2, canvas.height);
linearGrad.addColorStop(0, '#ff0503');
linearGrad.addColorStop(1, '#ff8200');
context.save();
context.strokeStyle = linearGrad; //設(shè)置描邊樣式
context.lineWidth = 7; //設(shè)置線寬
context.beginPath(); //路徑開始
context.arc(centerX, centerY, canvas.width / 2 -3.5 , -0.5 * Math.PI, end_cricle * Math.PI, false); //用于繪制圓弧context.arc(x坐標(biāo),y坐標(biāo),半徑,起始角度,終止角度,順時(shí)針/逆時(shí)針)
context.stroke(); //繪制
context.closePath(); //路徑結(jié)束
context.restore();
}
// 清除 canvas 內(nèi)容
function clear() {
var days_ctx = days_canvas.getContext('2d');
var hours_ctx = hours_canvas.getContext('2d');
var minutes_ctx = minutes_canvas.getContext('2d');
var seconds_ctx = seconds_canvas.getContext('2d');
days_ctx.clearRect(0, 0, days_canvas.width, days_canvas.height);
hours_ctx.clearRect(0, 0, hours_canvas.width, hours_canvas.height);
minutes_ctx.clearRect(0, 0, minutes_canvas.width, minutes_canvas.height);
seconds_ctx.clearRect(0, 0, seconds_canvas.width, seconds_canvas.height);
}
// 繪圖
function draw() {
drawCricle(days_canvas);
drawCricle(hours_canvas);
drawCricle(minutes_canvas);
drawCricle(seconds_canvas);
drawStr(days_canvas, 'Days');
drawStr(hours_canvas, 'Hours');
drawStr(minutes_canvas, 'Minutes');
drawStr(seconds_canvas, 'Seconds');
drawProgress(days_canvas, dateObj.days / 10 >= 1 ? 1 : dateObj.days / 10);
drawNumber(days_canvas, dateObj.days);
drawProgress(hours_canvas, (dateObj.hours / 24).toFixed(4));
drawNumber(hours_canvas, dateObj.hours);
drawProgress(minutes_canvas, (dateObj.minutes / 60).toFixed(4));
drawNumber(minutes_canvas, dateObj.minutes);
drawProgress(seconds_canvas, (dateObj.seconds / 60).toFixed(4));
drawNumber(seconds_canvas, dateObj.seconds);
}
draw();
(function count() {
requestAnimationFrame(count);
clear();
dateObj = getLeftTimeObj();
draw();
})();到此這篇關(guān)于JavaScript中的canvas 實(shí)現(xiàn)一個(gè)圓環(huán)漸變倒計(jì)時(shí)效果的文章就介紹到這了,更多相關(guān)JavaScript canvas 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
比較詳細(xì)的javascript對(duì)象的property和prototype是什么一種關(guān)系
比較詳細(xì)的javascript對(duì)象的property和prototype是什么一種關(guān)系...2007-08-08
微信小程序如何調(diào)用新聞接口實(shí)現(xiàn)列表循環(huán)
這篇文章主要介紹了微信小程序如何調(diào)用新聞接口實(shí)現(xiàn)列表循環(huán),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
javaScript對(duì)文字按照拼音排序?qū)崿F(xiàn)代碼
這篇文章主要介紹了javaScript對(duì)文字按照拼音排序?qū)崿F(xiàn)代碼,有需要的朋友可以參考一下2013-12-12
javascript+HTML5自定義元素播放焦點(diǎn)圖動(dòng)畫
這篇文章主要介紹了javascript+HTML5自定義元素播放焦點(diǎn)圖動(dòng)畫的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02
javascript容錯(cuò)處理代碼(屏蔽js錯(cuò)誤)
本文主要介紹了javascript的容錯(cuò)處理代碼。具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01

