用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>

效果:

 

posted on 2022-02-27 20:05  DavidXu2014  阅读(637)  评论(0编辑  收藏  举报