zoukankan      html  css  js  c++  java
  • 基于 HTML5 Canvas 的可交互旋钮组件

    前言

    此次的 Demo 效果如下:

    Demo 链接:https://hightopo.com/demo/comp-knob/

    整体思路

    • 组件参数
    • 绘制旋钮
    • 绘制刻度
    • 绘制指针
    • 绘制标尺
    • 绘制文本
    • 交互效果

    1.组件参数

    以下是下文会使用到的部分变量,在此先贴出来

    var origin, // 原点
        percent, // 显示刻度占总刻度的百分比
        partAngle, // 每个刻度所占的角度
        startAngle, //刻度起始的角度
        calibrationPoints, // 每个刻度的信息
        pointer, // 指针的信息
        scaleLine, // 标尺的信息
        calibrationColors // 刻度渐变色

    2.绘制旋钮

    这里主要就使用了 canvas api 中的 arc()createRadialGradient()

    主要代码:

    g.beginPath();
    var ringRadial = g.createRadialGradient(origin.x, origin.y, 0, origin.x, origin.y, ringRadio);
    ringRadial.addColorStop(0, ht.Default.brighter(ringColor, 20));
    ringRadial.addColorStop(0.95, ht.Default.brighter(ringColor, 40));
    ringRadial.addColorStop(1, ht.Default.darker(ringColor, 20));
    
    var borderRadial = g.createRadialGradient(origin.x, origin.y, ringRadio - ringBorderWidth / 2, origin.x, origin.y, ringRadio + ringBorderWidth / 2);
    borderRadial.addColorStop(0, ht.Default.brighter(ringBorderColor, 2));
    borderRadial.addColorStop(0.5, ht.Default.brighter(ringBorderColor, 4));
    borderRadial.addColorStop(1, ht.Default.darker(ringBorderColor, 4));
    g.fillStyle = ringRadial;
    g.lineWidth = ringBorderWidth;
    g.strokeStyle = borderRadial;
    g.arc(origin.x, origin.y, ringRadio, 0, 2 * Math.PI);
    g.closePath();
    g.fill();
    g.stroke();

    效果图:

    3.绘制刻度

    这里绘制每个刻度采用的是绘制路径的方法,所以声明了一个变量 calibrationPoints 用来存放每个刻度的起始点坐标,根据配置的参数去计算 calibrationPoints 的信息。

    首先根据参数 calibrationPercent 计算第一个刻度的起始角度 startAngle ,然后根绝 calibrationCount 的值去计算每个刻度所占用的角度 partAngle ,最后根据三角函数和相应的角度,转化为对应的坐标。

    主要代码:

    var calibrationPoints = [];
    for (var i = 0; i < calibrationCount + 1; i++) {
        var point = {
            startx: origin.x + (ringRadio + ringBorderWidth + 0 * calibrationHeight) * Math.cos(startAngle - i * partAngle),
            starty: origin.y - (ringRadio + ringBorderWidth + 0 * calibrationHeight) * Math.sin(startAngle - i * partAngle),
            endx: origin.x + (ringRadio + ringBorderWidth + 1 * calibrationHeight) * Math.cos(startAngle - i * partAngle),
            endy: origin.y - (ringRadio + ringBorderWidth + 1 * calibrationHeight) * Math.sin(startAngle - i * partAngle)
        };
        if (i <= (calibrationCount * percent) && percent > 0) {
            point.show = true;
        } else {
            point.show = false;
        }
        calibrationPoints.push(point);
    }

    有了每个刻度的信息后,接下来就开始绘制刻度。

    主要代码:

    calibrationPoints.forEach(function (i, index) {
        g.beginPath();
        if (calibrationColorWheelShow) {
            calibrationBrightColor = calibrationColors[index];
        }
        g.lineWidth = 1.2 * calibrationWidth;
        g.strokeStyle = i.show ? calibrationBrightColor : ht.Default.brighter(calibrationDarkColor, 10);
    
        g.moveTo(i.startx, i.starty);
        g.lineTo(i.endx, i.endy);
        g.closePath();
        g.stroke();
    })
    
    calibrationPoints.forEach(function (i, index) {
        g.beginPath();
        if (calibrationColorWheelShow) {
            calibrationBrightColor = calibrationColors[index];
        }
        g.lineWidth = calibrationWidth;
        g.strokeStyle = i.show ? calibrationBrightColor : calibrationDarkColor;
    
        g.moveTo(i.startx, i.starty);
        g.lineTo(i.endx, i.endy);
        g.closePath();
        g.stroke();
    })

    效果图:

    考虑到一种高亮颜色太单调,于是加了个色轮。思路:给每个刻度都添加了颜色的标识。
    每个刻度的颜色计算方法:把颜色值转换成 rgb 方式,设定多少秒改变完成,每次改变多少值,计算需要多少次,比如 rba(x,y,z)rgb(a,b,c),假设需要 100 次,那么每次设定 rgb((a - x) / 100 + x, (b - y) / 100 + y, (c - z) / 100 + z)

    主要代码:

    if (calibrationColorWheelShow) { // 显示刻度色轮
        var colors = [];
        calibrationColorWheel.forEach(function (i) {
            colors.push(ht.Default.toColorData(i))
        })
        // 把颜色值转换成rgb方式,设定多少秒改变完成,每次改变多少值,计算需要多少次
        // ,比如rba(x,y,z)到rgb(a,b,c),假设需要100次,那么每次设定
        // rgb((a-x)/100+x,(b-y)/100+y,(c-z)/100+z)
        var count = Math.ceil(calibrationCount / (calibrationColorWheel.length - 1)); // 渐变次数
        calibrationColors = [];
        for (var i = 0; i < colors.length - 1; i++) {
            for (var j = 1; j <= count; j++) {
                var item = 'rgb('
                    + Math.round((colors[i + 1][0] - colors[i][0]) / j + colors[i][0])
                    + ','
                    + Math.round((colors[i + 1][1] - colors[i][1]) / j + colors[i][1])
                    + ','
                    + Math.round((colors[i + 1][2] - colors[i][2]) / j + colors[i][2])
                    + ')';
                calibrationColors.push(item)
            }
        }
    }

    效果图:

    4.绘制指针

    这个主要是根据三角函数去计算相对圆心的偏移角度,按照当前值和刻度最大值的比例来计算偏移量,然后换算成对应的坐标。

    主要代码:

    pointer = {
        x: origin.x + (ringRadio - pointerRadio - ringBorderWidth) * Math.cos(startAngle - Math.PI * 2 * calibrationPercent * percent),
        y: origin.y - (ringRadio - pointerRadio - ringBorderWidth) * Math.sin(startAngle - Math.PI * 2 * calibrationPercent * percent),
        r: pointerRadio,
        color: percent > 0 ? calibrationBrightColor : calibrationDarkColor,
        show: true,
    }
    
    if (pointerShow) {
        g.beginPath();
        g.fillStyle = pointer.color;
        g.arc(pointer.x, pointer.y, pointer.r, 0, Math.PI * 2);
        g.closePath();
        g.fill();
    }

    效果图:

    5.绘制标尺

    计算标尺角度的算法同指针。

    主要代码:

    scaleLine = {
        startx: origin.x,
        starty: origin.y,
        endx: origin.x + (ringRadio + ringBorderWidth + 2 * calibrationHeight) * Math.cos(startAngle - Math.PI * 2 * calibrationPercent * percent),
        endy: origin.y - (ringRadio + ringBorderWidth + 2 * calibrationHeight) * Math.sin(startAngle - Math.PI * 2 * calibrationPercent * percent),
        color: percent > 0 ? calibrationBrightColor : calibrationDarkColor,
        show: scaleLineShow,
    }
    if (scaleLine) {
        g.beginPath();
        g.strokeStyle = 'red';
        g.setLineDash([1, 2]);
        g.lineWidth = 0.5 * calibrationWidth;
        g.moveTo(scaleLine.startx, scaleLine.starty);
        g.lineTo(scaleLine.endx, scaleLine.endy);
        g.closePath();
        g.stroke();
    }

    效果图:

    6.绘制文本

    这里主要的就是确定文本所要绘制的位置,然后根据 ht.Default.getTextSize() 来获取文本长度,方便设置文本居中。

    主要代码:

    if (labelShow) {
        var text = ht.Default.getTextSize(font, value);
        g.fillStyle = labelColor;
        g.font = font;
        g.fillText(value.toFixed(2), labelDot.x, labelDot.y);
    }

    效果图:

    到这就完成了基本的旋钮组件,下面继续做一些细节上的优化。

    例如加一些阴影效果,颜色渐变,配色调整等。

    主要代码:

    var backgroundRadial = g.createRadialGradient(x + 0.5 * width, y + 0.2 * height, 0, x + 0.5 * width, y + 0.2 * height, Math.sqrt(Math.pow(width / 2, 2) + Math.pow(height, 2)));
    backgroundRadial.addColorStop(0, 'rgba(220,220,220,1)');
    backgroundRadial.addColorStop(1, backgroundColor);
    g.fillStyle = backgroundRadial;
    g.fillRect(x, y, width, height);
    
    g.beginPath();
    var ringRadial = g.createRadialGradient(origin.x, origin.y - ringRadio / 2, 0, origin.x, origin.y - ringRadio / 2, 1.5 * ringRadio);
    ringRadial.addColorStop(0, ht.Default.brighter(ringColor, 40));
    // ringRadial.addColorStop(0.25, ht.Default.brighter(ringColor, 40));
    ringRadial.addColorStop(1, ht.Default.darker(ringColor, 20));
    
    var borderRadial = g.createRadialGradient(origin.x, origin.y, ringRadio - ringBorderWidth / 2, origin.x, origin.y, ringRadio + ringBorderWidth / 2);
    borderRadial.addColorStop(0, ht.Default.brighter(ringBorderColor, 2));
    borderRadial.addColorStop(0.5, ht.Default.brighter(ringBorderColor, 4));
    borderRadial.addColorStop(1, ht.Default.darker(ringBorderColor, 4));
    g.fillStyle = ringRadial;
    
    g.lineWidth = ringBorderWidth;
    g.strokeStyle = borderRadial;
    g.arc(origin.x, origin.y, ringRadio, 0, 2 * Math.PI);
    g.closePath();
    g.fill();
    g.shadowBlur = 20;
    g.shadowColor = shadowColor;
    g.shadowOffsetY = ringBorderWidth;
    g.stroke();
    g.shadowBlur = 0;
    g.shadowOffsetY = 0;

    效果图:

     7.交互效果

    以上就是绘制好了一张静态图,最后就只要再加上一些交互效果就可以了。
    这里我采用的是 HT for Web 的矢量来实现。可参考 → 戳这

    监听 onUp onDraw 事件。
    onUp
    当鼠标抬起时,获取当前旋钮显示的值,然后四舍五入,取其最近的刻度校准,使用 ht.Default.startAnim() 添加动画效果。
    onDraw
    根据当前鼠标停留的位置,以旋钮原点为参照点,根据三角函数来计算指针和起始刻度的夹角 A ,计算 A 占总刻度的百分比 p ,然后设置当前值为 max * p

    最后使用 HT 实现:

    var gv = new ht.graph.GraphView();
          dm = gv.dm();
          dm.a('pannable', true);
          dm.a('zoomable', true);
          dm.a('rectSelectable', true);
          
          ht.Default.setCompType('knob',func); //注册组件
          ht.Default.setImage('iconKnob', data); //注册图片
          
    var node = new ht.Node();
          node.setImage('iconKnob');
          node.s('2d.movable', false);
          dm.add(node);
          gv.fitContent();
          gv.addToDOM();
          window.addEventListener(
            'resize',
            function(e) {
              gv.invalidate();
              gv.fitContent();
            },
            false
          );
  • 相关阅读:
    python学习之字典合并
    python学习之列表、元组、集合、字典随笔
    图像检索中的概念
    卷积、反卷积、转置卷积资源
    计算机视觉顶级会议和期刊
    Week17
    Python协程资源
    深度图像资源
    Geo-localization论文阅读list2
    NetVLAD原理详解和推导
  • 原文地址:https://www.cnblogs.com/htdaydayup/p/11258689.html
Copyright © 2011-2022 走看看