用H5 Canvas绘制一个仪表盘笔记
前端有许多做数据可视化的图表插件,但有时候UI设计的图可能用现成的js插件无法定制或者比较麻烦(还不如自己造轮子来的快)。
下面记录下用H5 canvas设计一个仪表盘代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="Generator" content="EditPlus®"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>Document</title> <script src="tween.umd.min.js"></script> <style> .container { position: absolute; top: 40px; left: 100px; width: 138px; height: 138px; display: flex; align-items: center; justify-content: center; } #bg, #ring { position: absolute; width: 138px; height: 138px; } #bg { background-color: #fff; } </style> </head> <body style="background-color: #ccc"> <div class="container"> <div id="percent" style="z-index: 100; color: #fff;">0%</div> <canvas id="bg" width="138" height="138"></canvas> <canvas id="ring" width="138" height="138"></canvas> </div> </body> <script> const bgCanvas = document.querySelector('#bg'); const ringCanvas = document.querySelector('#ring'); const bgCtx = bgCanvas.getContext('2d'); const ctx = ringCanvas.getContext('2d'); const percentEl = document.querySelector('#percent'); const pixelRatio = window.devicePixelRatio; const BG_RING_WIDTH = 6; const POINTER_BALL_RADIUS = 6; const CANVAS_WIDTH = 138; const CANVAS_HEIGHT = 138; const INNER_CIRCLE_RADIUS = 45; if(pixelRatio > 1) { bgCanvas.width = CANVAS_WIDTH * 2; bgCanvas.height = CANVAS_HEIGHT * 2; ringCanvas.width = CANVAS_WIDTH * 2; ringCanvas.height = CANVAS_HEIGHT * 2; bgCanvas.style.width = CANVAS_WIDTH; bgCanvas.style.height = CANVAS_HEIGHT; ringCanvas.style.width = CANVAS_WIDTH; ringCanvas.style.height = CANVAS_HEIGHT; } function createBgCircle() { bgCtx.save(); bgCtx.beginPath(); bgCtx.arc(CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2, INNER_CIRCLE_RADIUS, 0, 2 * Math.PI); let gradient = bgCtx.createLinearGradient(INNER_CIRCLE_RADIUS * 2, 0, 0, INNER_CIRCLE_RADIUS * 2); gradient.addColorStop(0, '#E69908'); gradient.addColorStop(1, '#FDF27B'); bgCtx.fillStyle = gradient; bgCtx.fill(); bgCtx.restore(); } function createBgRing() { bgCtx.save(); bgCtx.beginPath(); const x = CANVAS_WIDTH / 2; const y = r = x; bgCtx.arc(x, y, r, 0, 2 * Math.PI, true); bgCtx.arc(x, y, x - BG_RING_WIDTH, 0, 2 * Math.PI, false); bgCtx.fillStyle = '#EEF5F5'; bgCtx.fill(); bgCtx.restore(); } let imageLoaded = false; let img = null; async function createProcessRing(radian) { const x = CANVAS_WIDTH / 2; const y = r = x; //裁剪扇形区域 ctx.save(); ctx.beginPath(); ctx.moveTo(x, y); ctx.arc(x, y, r, -Math.PI/2, -Math.PI/2 + radian); ctx.clip(); if(imageLoaded) { ctx.drawImage(img, 0, 0, img.width, img.height); ctx.restore(); return; } return new Promise(resolve => { const imgUrl = "ring.png"; img = new Image(); img.src = imgUrl; img.onload = function() { imageLoaded = true; ctx.drawImage(img, 0, 0, img.width, img.height); ctx.restore(); resolve(); } }) } function createBallPointer(x, y) { const cx = CANVAS_WIDTH / 2; const cy = cx; ctx.save(); ctx.translate(cx, cy); ctx.beginPath(); ctx.arc(x, y, POINTER_BALL_RADIUS, 0, 2 * Math.PI); let gradient = ctx.createLinearGradient(0, POINTER_BALL_RADIUS * 2, POINTER_BALL_RADIUS * 2, 0); gradient.addColorStop(0, '#FF8B00'); gradient.addColorStop(1, '#F2C008'); ctx.fillStyle = gradient; ctx.fill(); ctx.restore(); } function createAnimation(startAngle = 0, endAngle = 360, callback) { function animate(time) { requestAnimationFrame(animate) TWEEN.update(time) } requestAnimationFrame(animate) const p = {angle: startAngle} const tween = new TWEEN.Tween(p) .to({angle: endAngle}, 600) .easing(TWEEN.Easing.Quadratic.Out) .onUpdate(() => { let radian = Math.PI / 180 * p.angle; let x = (CANVAS_WIDTH / 2 - BG_RING_WIDTH) * Math.cos(Math.PI/2 - radian); let y = -(CANVAS_HEIGHT / 2 - BG_RING_WIDTH) * Math.sin(Math.PI/2 - radian); ctx.clearRect(0, 0, ringCanvas.width, ringCanvas.height); createProcessRing(radian); createBallPointer(x, y); percentEl.innerText = Math.round(p.angle / 360 * 100) + '%'; }) .start(); } async function setup() { createBgCircle(); createBgRing(); await createProcessRing(); createBallPointer(0, -(CANVAS_WIDTH / 2 - BG_RING_WIDTH/2)); createAnimation(0, 360); } setup(); </script> </html>
效果: