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

    效果:

  • 相关阅读:
    有关.net 框架的学习笔记
    简单定义工程架构
    respondsToSelector的相关使用
    IOS框架和服务
    iOS常用第三方类库
    ios换肤思想,及工具类
    集成激光推送
    远程推送
    ios本地推送
    UIPopoverController 的使用
  • 原文地址:https://www.cnblogs.com/davidxu/p/15943219.html
Copyright © 2020-2023  润新知