這里給大家分享我在網(wǎng)上總結(jié)出來的一些知識,希望對大家有所幫助
最近喜歡研究起了手表,對勞力士這款“百事圈”實在是心水的不行??!
心癢難耐無奈錢包不支持,作為一個前端程序員,買不起的東西該怎么辦?
當(dāng)然是自己做一個?。?/p>
說干就干,熬夜自己做了個“百事圈”出來!源碼在最后!
先看成品
?還是有那么六七成相像了吧!主要還是在顏色選擇和細(xì)節(jié)處理上要花些功夫,無奈通過單純的平面很難展現(xiàn)出“材質(zhì)感”。
雖然質(zhì)感比不上人家吧,但咱們這個走時精準(zhǔn)度肯定比那鋼砣子強不是??。
除了實現(xiàn)了手表的“走時”這個基礎(chǔ)功能外,還把日歷窗、GMT雙時區(qū)功能、外圈旋轉(zhuǎn)功能也一并實現(xiàn)啦!圖片里不好展示,大家可以看完文章,自行領(lǐng)取源碼來玩玩!
實現(xiàn)思路
本想想用盡量多的CSS來實現(xiàn),但CSS實現(xiàn)這種較為復(fù)雜的動態(tài)功能還是有些頭疼,所以還是把思路放到了用canvas來實現(xiàn)。
小小構(gòu)思后手表被分為三個模塊:
-
表盤
-
表圈
- 表針
先在html結(jié)構(gòu)中建立三個canvas標(biāo)簽:
<div class="watch-box"> <!-- 表針 --> <canvas id="watchPointer" width="1800" height="1800"></canvas> <!-- 表盤 --> <canvas id="dial" width="1800" height="1800"></canvas> <!-- 表圈 --> <canvas id="bezel" width="1800" height="1800"></canvas> </div>
getContext("2d")
方法來獲取到canvas畫布的二維渲染上下文,并且為了能讓畫面清晰度更高,我們使用.scale()
方法對canvas進(jìn)行四倍縮放,這樣我們可以得到一個清晰度更高的canvas畫布:const watchBox = document.querySelector(".watch-box"); const watchPointer = document.querySelector("#watchPointer"); const dial = document.querySelector("#dial"); const bezel = document.querySelector("#bezel"); const ctx = watchPointer.getContext("2d"); const dialCtx = dial.getContext("2d"); const bezelCtx = bezel.getContext("2d"); const ratio = 4; ctx.scale(ratio, ratio); dialCtx.scale(ratio, ratio); bezelCtx.scale(ratio, ratio);
// 初始化顏色變量 const gmtBezelRed = "#8a2811"; const blue = "#133760"; const black = "#10111e"; const white = "#fff"; const grayD = "#ddd"; const grayC = "#ccc"; const grayB = "#bbb"; const grayA = "#aaa"; const gray9 = "#999"; const gray8 = "#888"; const gmtPointerRed = "#aa0d0f"; const transparent = "grba(0,0,0,255)";
好,準(zhǔn)備部分做完了,我們開始正題!構(gòu)建繪制方法drawGmtBezel
,首先對最簡單的表圈部分進(jìn)行繪制:
// 繪制表圈 function drawGmtBezel() { // 設(shè)置中心點,此時225,225變成了坐標(biāo)的0,0 bezelCtx.translate(225, 225); // 陰影的x偏移 bezelCtx.shadowOffsetX = 50; // 陰影的y偏移 bezelCtx.shadowOffsetY = 50; // 陰影顏色 bezelCtx.shadowColor = "rgba(0, 0, 0, 0.5)"; // 陰影的模糊半徑 bezelCtx.shadowBlur = 100; /** * 繪制陶瓷表圈 * @param {CanvasRenderingContext2D} bezelCtx * @param {number} begin * @param {number} end * @param {string} color * @returns **/ const drawCeramicCircle = (bezelCtx, begin, end, color) => { bezelCtx.beginPath(); bezelCtx.lineWidth = 26.5; bezelCtx.arc(0, 0, 113.25, begin, end); bezelCtx.strokeStyle = color; bezelCtx.stroke(); bezelCtx.closePath(); } // 畫上表圈(藍(lán)) drawCeramicCircle(bezelCtx, Math.PI, 2 * Math.PI, blue) // 畫下表圈(紅) drawCeramicCircle(bezelCtx, 0, Math.PI, gmtBezelRed) }
目前的代碼只是繪制出了雙色表圈:
我們首先使用bezelCtx.translate(225, 225)
來設(shè)置畫布的原始點,由于我們要畫的是圓心在畫布中心點的表圈,所以我們把畫布原始點設(shè)置到225, 225
這個畫布中心點的位置。
之所以是225這個數(shù)字,是因為我們在canvas標(biāo)簽中將canvas大小設(shè)置為1800x1800,又在canvas初始化時把scale設(shè)置為4倍縮放,所以畫布分辨率實際上就是1800/4,也就是450x450像素,中心點自然就是225, 225
了。
隨后我們對表圈部分的陰影進(jìn)行設(shè)置,這里就不用我多介紹啦,和CSS的box-shadow邏輯是一樣的。
接下來就是繪制的部分了,我們再來看看代碼:
const drawCeramicCircle = (bezelCtx, color, begin, end) => { bezelCtx.beginPath(); bezelCtx.lineWidth = 26.5; bezelCtx.arc(0, 0, 113.25, begin, end); bezelCtx.strokeStyle = color; bezelCtx.stroke(); bezelCtx.closePath(); }
我們首先用beginPath
方法來開始一個新路徑,可以理解為調(diào)用一只新“畫筆”,我們在canvas中的所有線條繪制都要靠這個“畫筆”來進(jìn)行。
思路其實很簡單,每個畫面都是由一筆一畫的基礎(chǔ)元素組成的,我們需要對這些基礎(chǔ)元素進(jìn)行拆解,每一筆不一樣的筆觸都需要換一只新筆,然后設(shè)置這只筆的各種屬性,最后再進(jìn)行繪畫。
我們隨后就使用lineWidth()
和strokeStyle()
設(shè)置這只“畫筆”的粗細(xì)、顏色屬性,并且使用arc
方法來繪制一個“弧形”,這個arc
方法接收五個參數(shù):圓心的 x 軸坐標(biāo)、圓心的 y 軸坐標(biāo)、圓弧的半徑、圓弧的起始點、圓弧的終點。
我們在前面已經(jīng)把畫布的起始點設(shè)置為畫布中心了,所以前兩個圓心參數(shù)我們都傳入0,半徑就選用113.25這個數(shù)字(純?yōu)榱吮壤齾f(xié)調(diào)),起點和終點的設(shè)置就需要稍微計算一下了,所謂的“圓弧起始點”默認(rèn)是x軸上右側(cè)的半徑切點:
所以如果我們要先畫“下半圈”的話,起點也就是0
,終點也就是在x軸的左側(cè)。這兩個參數(shù)的單位都是“弧度”,一個半圓對應(yīng)的弧度也就是PI,所以下半圓的起始點是0,終點是PI。
按照這樣調(diào)用方法drawCeramicCircle(bezelCtx, 0, Math.PI, gmtBezelRed)
,看看效果:
?沒問題,我們再用同樣的邏輯drawCeramicCircle(bezelCtx, Math.PI, 2 * Math.PI, blue)
制作上半圈:
最后我們用stroke
方法來進(jìn)行繪制,圖像就被繪制出來了!
這就實現(xiàn)啦,邏輯其實不過就是這樣:新建路徑(畫筆)—> 設(shè)置路徑屬性 —> 設(shè)置路徑信息 —> 繪制。
表盤的邏輯也是一致,只要你稍微掌握如何在canvas中繪制矩形、線條,實現(xiàn)起來其實沒有什么難度,我們直接快進(jìn)到表針部分:
function drawWatchPointer() { // 設(shè)置中心點,此時225, 225變成了坐標(biāo)的0,0 ctx.translate(225, 225); // 獲取當(dāng)前時分秒 let time = new Date(); let day = time.getDate(); let hour = time.getHours() % 12; let min = time.getMinutes(); let second = time.getSeconds(); let millsecond = time.getMilliseconds(); // 時針 ctx.rotate(((2 * Math.PI) / 12) * hour + ((2 * Math.PI) / 12) * (min / 60) - Math.PI / 2); ctx.beginPath(); ctx.lineWidth = 3; ctx.fillStyle = white; ctx.fillRect(0, -4, 40, 8); ctx.strokeStyle = grayA; ctx.strokeRect(0, -3, 40, 6); ctx.closePath(); // 奔馳針頭上三角 ctx.beginPath(); ctx.moveTo(48, -4.5); ctx.lineTo(57, 0); ctx.lineTo(48, 4.5); ctx.lineWidth = 2; ctx.strokeStyle = grayA; ctx.fillStyle = white; ctx.fill(); ctx.stroke(); ctx.closePath(); // 繪制奔馳針 ctx.beginPath(); ctx.arc(40, 0, 10, 0, 2 * Math.PI); ctx.fillStyle = white; ctx.lineWidth = 2; ctx.strokeStyle = grayA; ctx.fill(); ctx.stroke(); ctx.closePath(); ctx.beginPath(); ctx.moveTo(30, 0); ctx.lineTo(39, 0); ctx.lineTo(46.5, 7); ctx.lineTo(39, 0); ctx.lineTo(46.5, -7); ctx.lineWidth = 2; ctx.strokeStyle = grayA; ctx.stroke(); ctx.closePath(); }
其實可以看到,整體邏輯和畫表圈并沒有什么不同,只是有一些新的方法需要學(xué)習(xí)。我們還是順著邏輯走一遍:
- 設(shè)置畫布中心點為
255, 255
- 通過
new Date()
獲取當(dāng)前時間 - 表針其實就是一個固定的矩形,只是需要改變矩形的旋轉(zhuǎn)就可以表示時間。所以通過
((2 * Math.PI) / 12) * hour + ((2 * Math.PI) / 12) * (min / 60) - Math.PI / 2
來計算出當(dāng)前時間對于時針來說需要旋轉(zhuǎn)的角度并傳參給rotate
方法,使用rotate
方法可以旋轉(zhuǎn)繪制的角度 - 新建路徑,并設(shè)置路徑屬性(粗細(xì)、顏色)
- 使用
fillRect
來繪制矩形,四個參數(shù)分別代表x起始點、y起始點、寬、高 - 結(jié)束表針本體的繪制,使用
closePath
來清除畫筆信息。接下來繪制表針的針頭(三角形) - 使用
moveTo
方法來移動畫筆起始位置 - 使用
lineTo
方法來從畫筆起始位置繪制一條直線,參數(shù)為直線終點的x和y - 連續(xù)繪制三條直線,形成三角形
- 使用
fill
來填充矩形內(nèi)部的顏色 - 結(jié)束表針本體的繪制,使用
closePath
來清除畫筆信息。接下來繪制表針的奔馳針部分(圓形) - …
可以看到,其實使用邏輯都是一樣的,不過就是上面說的這些,你可以自己嘗試一下把分針和秒針給實現(xiàn)出來,應(yīng)該就會對canvas有個基本的認(rèn)識啦。
時針的實現(xiàn)效果:
完整源碼
Canvas的基礎(chǔ)知識都是比較零碎但深度普遍不深的,我就不帶著大家把每個實現(xiàn)都過一遍了,直接把源碼拿出來,大家隨意取用!
可以試著在這個源碼基礎(chǔ)上,把自己喜歡的別的表也給做出來,做這玩意有種玩“我的世界”的快感,快來試試吧!
熬夜寫的代碼比較匆忙,還有很大的優(yōu)化空間,我就以此拋磚引玉啦:文章來源:http://www.zghlxwxcb.cn/news/detail-747015.html
<!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>Rolex GMT-MASTER</title> <style> @font-face { font-family: "Optima"; src: url("fonts/Optima.ttc"); } @font-face { src: url("./fonts/Palatino.ttc"); font-family: "Trebuchet MS"; } @font-face { font-family: "Nunito Sans"; src: url("./fonts/NunitoSans-Regular.ttf"); } body { margin: 0; } .watch-box { width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; background: radial-gradient(circle, #eee, #ccc, #aaa, #777); } h2 { position: absolute; top: 0; font-family: "Nunito Sans"; } canvas { position: absolute; transform: scale(0.25); } #bezel { z-index: 0; font-weight: bold; font-stretch: 0px; } #dial { z-index: 1; letter-spacing: 0.5px; } #watchPointer { z-index: 2; } </style> </head> <body> <div class="watch-box"> <!-- 表針 --> <canvas id="watchPointer" width="1800" height="1800"></canvas> <!-- 表盤 --> <canvas id="dial" width="1800" height="1800"></canvas> <!-- 表圈 --> <canvas id="bezel" width="1800" height="1800"></canvas> </div> <script> const watchBox = document.querySelector(".watch-box"); const watchPointer = document.querySelector("#watchPointer"); const dial = document.querySelector("#dial"); const bezel = document.querySelector("#bezel"); const ctx = watchPointer.getContext("2d"); const dialCtx = dial.getContext("2d"); const bezelCtx = bezel.getContext("2d"); const ratio = 4; ctx.scale(ratio, ratio); dialCtx.scale(ratio, ratio); bezelCtx.scale(ratio, ratio); const logo = new Image(); const rolexLogo = new Image(); const imgResources = [logo, rolexLogo]; rolexLogo.src = "./images/rolex.png"; logo.src = "./images/logo.png"; // 圖片資源加載后繪制表盤 const renderDrawDial = (() => { let imageOnloadSuccessCount = 0; return () => { imageOnloadSuccessCount ++; if (imageOnloadSuccessCount >= imgResources.length) { // 圖片資源加載完畢 drawDial(); drawGmtBezel(); setInterval(drawWatchPointer, 100); } } })() rolexLogo.onload = renderDrawDial; logo.onload = renderDrawDial; const gmtBezelRed = "#8a2811"; const blue = "#133760"; const black = "#10111e"; const white = "#fff"; const grayD = "#ddd"; const grayC = "#ccc"; const grayB = "#bbb"; const grayA = "#aaa"; const gray9 = "#999"; const gray8 = "#888"; const gmtPointerRed = "#aa0d0f"; const transparent = "grba(0,0,0,255)"; // 繪制表圈 function drawGmtBezel() { bezelCtx.save(); bezelCtx.clearRect(0, 0, 1800, 1800); // 設(shè)置中心點,此時225, 225變成了坐標(biāo)的0,0 bezelCtx.translate(225, 225); bezelCtx.save(); // 陰影的x偏移 bezelCtx.shadowOffsetX = 50; // 陰影的y偏移 bezelCtx.shadowOffsetY = 50; // 陰影顏色 bezelCtx.shadowColor = "rgba(0, 0, 0, 0.5)"; // 陰影的模糊半徑 bezelCtx.shadowBlur = 100; /** * 繪制陶瓷表圈 * @param {CanvasRenderingContext2D} bezelCtx * @param {number} begin * @param {number} end * @param {string} color * @returns **/ const drawCeramicCircle = (bezelCtx, begin, end, color) => { bezelCtx.beginPath(); bezelCtx.lineWidth = 26.5; bezelCtx.arc(0, 0, 113.25, begin, end); bezelCtx.strokeStyle = color; bezelCtx.stroke(); bezelCtx.closePath(); } // 畫上表圈(藍(lán)) drawCeramicCircle(bezelCtx, Math.PI, 2 * Math.PI, blue) // 畫下表圈(紅) drawCeramicCircle(bezelCtx, 0,Math.PI, gmtBezelRed) // 最外層金屬旋轉(zhuǎn)外圈 bezelCtx.beginPath(); bezelCtx.lineWidth = 6; bezelCtx.arc(0, 0, 129.5, 0, 2 * Math.PI); bezelCtx.strokeStyle = grayD; bezelCtx.stroke(); bezelCtx.closePath(); bezelCtx.save(); bezelCtx.rotate(-Math.PI / 2); for (let i = 1; i <= 60; i++) { bezelCtx.rotate((2 * Math.PI) / 60); // 繪制旋轉(zhuǎn)外圈上的凹槽 bezelCtx.beginPath(); bezelCtx.lineWidth = 0.6; bezelCtx.arc(132.5, 0, 4.2, Math.PI / 2, (3 / 2) * Math.PI); if ((i > 13 && i < 18) || (i > 28 && i < 33)) { bezelCtx.fillStyle = gray9; } else if (i >= 18 && i <= 28) { bezelCtx.fillStyle = gray8; } else { bezelCtx.fillStyle = grayA; } bezelCtx.strokeStyle = white; bezelCtx.fill(); bezelCtx.stroke(); bezelCtx.closePath(); bezelCtx.lineWidth = 1; if (i === 60) { // 繪制十二點方向外圈 bezelCtx.beginPath(); bezelCtx.lineWidth = 1; bezelCtx.moveTo(106, 0); bezelCtx.lineTo(120, 16); bezelCtx.lineTo(120, -16); bezelCtx.lineTo(107, 0); bezelCtx.fillStyle = white; bezelCtx.strokeStyle = white; bezelCtx.fill(); bezelCtx.stroke(); bezelCtx.closePath(); } if (i % 5 === 0 && i !== 60) { bezelCtx.save(); bezelCtx.rotate(Math.PI / 2); bezelCtx.beginPath(); bezelCtx.fillStyle = white; bezelCtx.font = "500 24px Saira"; bezelCtx.textBaseline = "bottom"; let width = bezelCtx.measureText((i * 4) / 10).width; if (width < 20) { bezelCtx.fillText((i * 4) / 10, -8, -99.5); } else { bezelCtx.fillText((i * 4) / 10, -12, -99.5); } bezelCtx.fill(); bezelCtx.stroke(); bezelCtx.closePath(); bezelCtx.restore(); } if (i % 5 === 3) { bezelCtx.beginPath(); bezelCtx.fillStyle = white; bezelCtx.strokeStyle = white; bezelCtx.arc(109, -4, 2.7, 0, 2 * Math.PI); bezelCtx.fill(); bezelCtx.stroke(); bezelCtx.closePath(); } } bezelCtx.restore(); bezelCtx.restore(); bezelCtx.rotate(0.5 * Math.PI); } // 繪制表盤 function drawDial() { dialCtx.save(); dialCtx.clearRect(0, 0, 1800, 1800); // 設(shè)置中心點,此時225, 225變成了坐標(biāo)的0,0 dialCtx.translate(225, 225); // 畫表盤外圈 dialCtx.beginPath(); // 畫圓線使用arc(中心點X,中心點Y,半徑,起始角度,結(jié)束角度) dialCtx.arc(0, 0, 100, 0, 2 * Math.PI); dialCtx.strokeStyle = grayC; dialCtx.stroke(); // 執(zhí)行畫線段的操作stroke dialCtx.closePath(); // 畫表盤 dialCtx.beginPath(); // 畫圓線使用arc(中心點X,中心點Y,半徑,起始角度,結(jié)束角度) dialCtx.arc(0, 0, 53, 0, 2 * Math.PI); dialCtx.fillStyle = black; dialCtx.strokeStyle = black; dialCtx.lineWidth = 94; dialCtx.stroke(); // 執(zhí)行畫線段的操作stroke dialCtx.closePath(); dialCtx.drawImage(rolexLogo, -25, -56, 50, 27); dialCtx.fillStyle = white; dialCtx.font = "500 6px Nunito Sans"; dialCtx.textBaseline = "bottom"; dialCtx.fillText( "OYSTER PERPETUAL DATE", -dialCtx.measureText("OYSTER PERPETUAL DATE").width / 2, -21 ); dialCtx.font = "6px Nunito Sans"; dialCtx.fillText("GMT-MASTER", -28, 34); dialCtx.font = "6px Marmelad"; dialCtx.fillText("II", 25, 34.3, 4); dialCtx.font = "5px Trebuchet MS"; dialCtx.fillText("SUPERLATIVE CHRONOMETER", -32.5, 40, 65); dialCtx.fillText("OFFICIALLY CERTIFIED", -24, 46, 48); // 繪制刻度 dialCtx.save(); dialCtx.lineWidth = 1; dialCtx.shadowOffsetX = 5; dialCtx.shadowOffsetY = 5; dialCtx.shadowColor = "rgba(0, 0, 0, 0.4)"; dialCtx.shadowBlur = 10; dialCtx.rotate(-Math.PI / 2); for (let i = 1; i <= 60; i++) { dialCtx.rotate((2 * Math.PI) / 60); dialCtx.beginPath(); dialCtx.lineWidth = 1; dialCtx.strokeStyle = grayD; if (i % 5 === 0) { dialCtx.strokeStyle = white; dialCtx.lineWidth = 1.3; } if (i === 28 || i === 29 || i === 31 || i === 32) { dialCtx.moveTo(94, 0); dialCtx.lineTo(96, 0); } else { dialCtx.moveTo(94, 0); dialCtx.lineTo(98.5, 0); } if (i !== 30) dialCtx.stroke(); if (i === 29) { dialCtx.save(); dialCtx.rotate(-Math.PI / 2 - 0.05); dialCtx.textBaseline = "middle"; dialCtx.font = "4px Nunito Sans"; dialCtx.fillStyle = white; dialCtx.fillText( "M A D E", -dialCtx.measureText("MADE").width / 2, 98, 13 ); dialCtx.restore(); } if (i === 30) { dialCtx.save(); dialCtx.rotate(-Math.PI / 2); ctx.mozImageSmoothingEnabled = false; ctx.webkitImageSmoothingEnabled = false; ctx.msImageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false; dialCtx.drawImage(logo, -3.5, 93, 7, 6); dialCtx.restore(); } if (i === 31) { dialCtx.save(); dialCtx.rotate(-Math.PI / 2 + 0.05); dialCtx.textBaseline = "middle"; dialCtx.font = "4px Nunito Sans"; dialCtx.fillStyle = white; dialCtx.fillText( "S W I S S", -dialCtx.measureText("SWISS").width / 2, 98, 13.5 ); dialCtx.restore(); } dialCtx.closePath(); if (i === 60) { dialCtx.beginPath(); dialCtx.moveTo(90, 12); dialCtx.lineTo(62, 0); dialCtx.lineTo(90, -12); dialCtx.lineTo(90, 12.5); dialCtx.lineWidth = 1.5; dialCtx.strokeStyle = gray9; dialCtx.fillStyle = white; dialCtx.fill(); dialCtx.stroke(); dialCtx.closePath(); } // 繪制刻度 if (i % 5 === 0 && i % 15 !== 0) { dialCtx.beginPath(); dialCtx.arc(82, 0, 8.5, 0, 2 * Math.PI); dialCtx.lineWidth = 1.5; dialCtx.strokeStyle = gray9; dialCtx.fillStyle = white; dialCtx.fill(); dialCtx.stroke(); dialCtx.closePath(); } // 繪制刻度 if (i % 15 === 0 && i !== 60 && i !== 15) { dialCtx.beginPath(); dialCtx.lineWidth = 1.5; dialCtx.strokeStyle = gray9; dialCtx.fillStyle = white; dialCtx.fillRect(60, -5, 30, 10); dialCtx.strokeRect(60, -5, 30, 10); dialCtx.fill(); dialCtx.stroke(); dialCtx.closePath(); } // 繪制日歷窗 if (i === 15) { dialCtx.beginPath(); dialCtx.lineWidth = 2; dialCtx.strokeStyle = gray9; dialCtx.fillStyle = white; dialCtx.fillRect(57, -8, 25, 16); dialCtx.fill(); dialCtx.stroke(); dialCtx.closePath(); } } dialCtx.restore(); dialCtx.restore(); } function drawWatchPointer() { ctx.save(); ctx.clearRect(0, 0, 1800, 1800); // 設(shè)置中心點,此時225, 225變成了坐標(biāo)的0,0 ctx.translate(225, 225); // 把狀態(tài)保存起來 ctx.save(); // 獲取當(dāng)前時分秒 let time = new Date(); let day = time.getDate(); let hour = time.getHours() % 12; let min = time.getMinutes(); let second = time.getSeconds(); let millsecond = time.getMilliseconds(); // 渲染日歷窗數(shù)字 ctx.fillStyle = "#000"; ctx.font = "bold 16px AppleGothic"; let width = ctx.measureText(day).width; ctx.fillText(day, width < 15 ? 63.5 : 58, 6); ctx.fill(); // 繪制圓軸 ctx.beginPath(); ctx.arc(0, 0, 7, 0, 2 * Math.PI); ctx.fillStyle = grayA; ctx.fill(); ctx.closePath(); // 時針 ctx.rotate(((2 * Math.PI) / 12) * hour +((2 * Math.PI) / 12) * (min / 60) -Math.PI / 2); ctx.beginPath(); ctx.lineWidth = 3; ctx.fillStyle = white; ctx.fillRect(0, -4, 40, 8); ctx.strokeStyle = grayA; ctx.strokeRect(0, -3, 40, 6); ctx.stroke(); ctx.closePath(); // 奔馳針頭上三角 ctx.beginPath(); ctx.moveTo(48, -4.5); ctx.lineTo(57, 0); ctx.lineTo(48, 4.5); ctx.lineWidth = 2; ctx.strokeStyle = grayA; ctx.fillStyle = white; ctx.fill(); ctx.stroke(); ctx.closePath(); // 繪制奔馳針 ctx.beginPath(); ctx.arc(40, 0, 10, 0, 2 * Math.PI); ctx.fillStyle = white; ctx.lineWidth = 2; ctx.strokeStyle = grayA; ctx.fill(); ctx.stroke(); ctx.closePath(); ctx.beginPath(); ctx.moveTo(30, 0); ctx.lineTo(39, 0); ctx.lineTo(46.5, 7); ctx.lineTo(39, 0); ctx.lineTo(46.5, -7); ctx.lineWidth = 2; ctx.strokeStyle = grayA; ctx.stroke(); ctx.closePath(); // 恢復(fù)成上一次save的狀態(tài) ctx.restore(); ctx.save(); // GMT針 ctx.rotate(((2 * Math.PI) / 24) * time.getHours() + ((2 * Math.PI) / 24) * (min / 60) - Math.PI / 2); ctx.beginPath(); ctx.shadowOffsetX = 5; ctx.shadowOffsetY = 5; ctx.shadowColor = "rgba(0, 0, 0, 0.2)"; ctx.shadowBlur = 15; ctx.lineWidth = 2; ctx.fillStyle = white; ctx.strokeStyle = gmtPointerRed; ctx.moveTo(0, 0); ctx.lineTo(80, 0); ctx.stroke(); ctx.closePath(); ctx.beginPath(); ctx.strokeStyle = grayA; ctx.moveTo(79, -9); ctx.lineTo(95, 0); ctx.lineTo(80, 8); ctx.lineTo(80, -9); ctx.fill(); ctx.stroke(); ctx.closePath(); // 繪制圓軸 ctx.beginPath(); ctx.arc(0, 0, 6, 0, 2 * Math.PI); ctx.fillStyle = grayD; ctx.fill(); ctx.closePath(); ctx.beginPath(); ctx.arc(0, 0, 2.5, 0, 2 * Math.PI); ctx.fillStyle = grayA; ctx.fill(); ctx.closePath(); ctx.restore(); ctx.save(); // 分針 ctx.rotate(((2 * Math.PI) / 60) * min +((2 * Math.PI) / 60) * (second / 60) - Math.PI / 2); ctx.beginPath(); ctx.lineWidth = 2; ctx.fillStyle = white; ctx.fillRect(10, -4, 70, 8); ctx.strokeStyle = grayA; ctx.fillStyle = grayA; ctx.strokeRect(0, -4, 80, 8); ctx.moveTo(80.7, -5.1); ctx.lineTo(90, 0); ctx.lineTo(80.7, 5.1); ctx.fillRect(0, -4, 10, 8); ctx.fill(); ctx.closePath(); // 繪制圓軸 ctx.beginPath(); ctx.arc(0, 0, 6, 0, 2 * Math.PI); ctx.fillStyle = grayD; ctx.fill(); ctx.closePath(); ctx.beginPath(); ctx.arc(0, 0, 2.5, 0, 2 * Math.PI); ctx.fillStyle = grayA; ctx.fill(); ctx.closePath(); ctx.restore(); ctx.save(); // 秒針 ctx.rotate(((2 * Math.PI) / 60) * second +((2 * Math.PI) / 60) * (millsecond / 1000) - Math.PI / 2); ctx.beginPath(); ctx.shadowOffsetX = 5; ctx.shadowOffsetY = 5; ctx.shadowColor = "rgba(0, 0, 0, 0.2)"; ctx.shadowBlur = 15; ctx.moveTo(-30, 0); ctx.lineTo(90, 0); ctx.lineWidth = 2; ctx.strokeStyle = grayA; ctx.closePath(); ctx.stroke(); // 繪制秒針尾部 ctx.beginPath(); ctx.arc(-30, 0, 5, 0, 2 * Math.PI); ctx.fillStyle = white; ctx.fill(); ctx.closePath(); // 繪制秒針中間圓形 ctx.shadowOffsetX = 5; ctx.shadowOffsetY = 5; ctx.shadowColor = "rgba(0, 0, 0, 0.2)"; ctx.shadowBlur = 15; ctx.beginPath(); ctx.arc(55, 0, 5.5, 0, 2 * Math.PI); ctx.fillStyle = white; ctx.lineWidth = 2; ctx.strokeStyle = grayA; ctx.fill(); ctx.stroke(); ctx.closePath(); ctx.restore(); ctx.save(); ctx.restore(); ctx.restore(); } </script> </body> </html>
本文轉(zhuǎn)載于:
https://juejin.cn/post/7304533060514971657
如果對您有所幫助,歡迎您點個關(guān)注,我會定時更新技術(shù)文檔,大家一起討論學(xué)習(xí),一起進(jìn)步。
?文章來源地址http://www.zghlxwxcb.cn/news/detail-747015.html
到了這里,關(guān)于記錄--買不起勞力士,一氣之下熬夜寫一個的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!