zoukankan      html  css  js  c++  java
  • 实现一个螺旋转盘

    转盘效果

    本文章讲解怎么实现这样一个螺旋转盘动态效果。不停旋转,箭头指向的扇形会变成高亮,整个转盘有个渐变效果,中间镂空。

    利用图片填充颜色

    首先准备如下三张图

    三张图怎么利用? 思路大概下面所标示的。第一张和第三种是盖到第二张上的,第二张图作为填充圆形的颜色。

     

       因为用canvas本身的shadow并不好实现这个效果,另外性能也不高。所以就直接利用了上面左侧的这张图。来呈现高亮选中的那个转盘扇形。

    初始化转盘

    1. 加载图片并保存填充颜色

    为了可以绘制出扇形上颜色渐变的效果,保证整体上是一张过渡性很好的渐变色盘。需要加载这三张图片,并绘制到一个离屏canvas上,借助createPattern,用canvas作为重复内容填充绘制路径,并保存下来用于后面绘制扇形填充使用。

     1     initWheelImage() {
     2         var intoCanvas = document.createElement("canvas");//创建canvas
     3         var ctx = intoCanvas.getContext('2d');
     4         intoCanvas.width = this.width + this.position.x;//设置到正确的宽高
     5         intoCanvas.height = this.height + this.position.y;
     6 
     7         //将三张图片加载
     8         let wheelSurfColor = new Image();
     9         wheelSurfColor.src = wheelSurfColorUrl;
    10         let wheelSurfCoverImg = new Image();
    11         wheelSurfCoverImg.src = wheelSurfCoverUrl;
    12         let innerGlowSector = new Image();
    13         innerGlowSector.src = innerGlowSectorUrl;
    14         //使用promise将图片加载成功后执行绘制操作
    15         let that = this;
    16         let arrPromise = [];
    17         arrPromise.push(Util.getImgLoadPromise(wheelSurfColor));
    18         arrPromise.push(Util.getImgLoadPromise(innerGlowSector));
    19         arrPromise.push(Util.getImgLoadPromise(wheelSurfCoverImg));
    20         Promise.all(arrPromise).then(
    21             function (imgArr) {
    22                 let r = that.innerRadius + that.maxOuterR;
    23                 //将图片绘制到离屏canvas上
    24                 ctx.drawImage(wheelSurfColor, that.position.x + that.width / 2 - r, that.position.y + that.height / 2 - r, r * 2, r * 2);
    25                 //指定重复元素用来填充路径
    26                 //或者  ctx.createPattern(intoCanvas, 'no-repeat')  使用哪个canvas都行
    27                 that.wheelSurfColorPattern = that.layer.canvasContext.createPattern(intoCanvas, 'no-repeat');
    28                 ctx.clearRect(0, 0, intoCanvas.width, intoCanvas.height)
    29 
    30                 //同上面几句代码作用一致
    31                 ctx.drawImage(wheelSurfCoverImg, that.position.x + that.width / 2 - r, that.position.y + that.height / 2 - r, r * 2, r * 2);
    32                 that.wheelSurfCoverPattern = that.layer.canvasContext.createPattern(intoCanvas, 'no-repeat');
    33                 that.initWheel.call(that, innerGlowSector);
    34                 that.update.call(that)
    35             }
    36         );
    37     }

    图片加载完成后,调用初始化转盘其他元素的方法。 

    2. 初始化转盘其他展示内容

     展示内容包括:31个扇形块,31个文字,顶部扇形,中间圆环,中间文字,箭头,刻度线等。这里用group划分组利于管理控制。

    绘制文字有性能问题,所以用到了离屏canvas。

      1     initWheel(innerGlowSector: HTMLImageElement) {
      2         let circlePerDis = 73 * this.scale;
      3         let font18 = 18 * this.scale;
      4         let font20 = 20 * this.scale;
      5         //绘制30个扇形块
      6         let i;
      7         let renderStyle = new RenderStyle(true, false, this.wheelSurfColorPattern);
      8         //30个扇形组成group 并且设置颜色渐变的效果 需要初始化31个这样转的时候才不会有空缺
      9         this.sectorGroup = new Group(renderStyle, new RotateConfig());
     10         for (i = 1; i <= 36; i++) {
     11             if (i >= 32) {//不需要做什么了
     12 
     13             } else {//加31个扇形
     14                 this.addNewSector(i);
     15             }
     16         }
     17 
     18         this.sectorGroup.setRotateCenter(this.position.x + this.width / 2, this.position.y + this.height / 2);
     19         this.sectorGroup.rotateBy(-60);
     20         this.group.add(this.sectorGroup);
     21 
     22         //绘制扇形上面的文字
     23         this.textGroup = new Group(new RenderStyle(), new RotateConfig());
     24         for (i = 1; i <= 36; i++) {
     25             if (i >= 32) {
     26 
     27             } else {
     28                 this.addNewText(i, i >= 17 ? "left" : "right");
     29             }
     30         }
     31         this.textGroup.setRotateCenter(this.position.x + this.width / 2, this.position.y + this.height / 2);
     32         //优化text绘制方式 提高性能 begin
     33         this.redrawTextCanvas();
     34         this.textCanvasImage = new CanvasImage(this.textCanvas, new RenderStyle(), new RotateConfig(), new RegionConfig());
     35         this.textCanvasImage.setRotateCenter(this.position.x + this.width / 2, this.position.y + this.height / 2);
     36         this.textCanvasImage.rotateBy(-60);
     37         this.group.add(this.textCanvasImage);
     38         //优化text绘制方式 提高性能 end
     39 
     40         //绘制顶部的扇形  镂空效果
     41         let renderStyle2 = new RenderStyle(true, false, this.wheelSurfCoverPattern);
     42         renderStyle2.setGlobalCompositeOperation("destination-out");
     43         let sector2 = new Circle(new CircleConfig(this.innerRadius + this.maxOuterR + this.radiusDiff, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2), 0, 360), renderStyle2, new RotateConfig());
     44         sector2.setRotateCenter(this.position.x + this.width / 2, this.position.y + this.height / 2);
     45         this.group.add(sector2);
     46 
     47         //绘制顶部半个扇形,蓝颜色有加深部分
     48         let sectorColorDeeprenderStyle = new RenderStyle(true, false);
     49         let percentColorArr = new Array<PercentColor>(new PercentColor(0, "rgba(0,30,255,0)"), new PercentColor(0.5, "rgba(0,30,255,0.2)"), new PercentColor(1, "rgba(0,30,255,1)"));
     50         sectorColorDeeprenderStyle.setLinearGradient(new LinearGradientConfig(this.position.x + this.width / 2, this.position.y + this.height / 2, this.position.x + this.width / 2 + this.innerRadius + this.maxOuterR / 2, this.position.y + this.height / 2, percentColorArr));
     51         let sectorColorDeep = new Sector(new SectorConfig(this.innerRadius + circlePerDis * 4, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2), 270, 315), sectorColorDeeprenderStyle);
     52         this.group.add(sectorColorDeep);
     53 
     54         //绘制中间的圆  镂空效果
     55         let renderStyle1 = new RenderStyle(true, false, "green");//随便什么颜色
     56         renderStyle1.setGlobalCompositeOperation("destination-out");
     57         let circle = new Circle(new CircleConfig(this.innerRadius, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2)), renderStyle1);
     58         this.group.add(circle);
     59 
     60         //环绕文字内部的圆 紧贴扇形
     61         let circleLineRenderStyle = new RenderStyle(false, true, "", "", 1, false);
     62         circleLineRenderStyle.setLinearGradient(new LinearGradientConfig(this.position.x, this.position.y, this.position.x + this.width, this.position.y, this.percentColorArr))
     63         let circleLine = new Circle(new CircleConfig(this.innerRadius, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2), -30, 300, true), circleLineRenderStyle, new RotateConfig());
     64         circleLine.setRotateCenter(this.position.x + this.width / 2, this.position.y + this.height / 2);
     65         circleLine.rotateBy(-60);
     66         this.group.add(circleLine);
     67         //绘制环绕文字的两个线条
     68         let circleLineTop = new Circle(new CircleConfig(this.innerRadius / 3 * 2, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2), 220, 320), new RenderStyle(false, true, "", "rgba(38,110,225,1)", 1, false));
     69         this.group.add(circleLineTop);
     70         let circleLineBottom = new Circle(new CircleConfig(this.innerRadius / 3 * 2, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2), 20, 160), new RenderStyle(false, true, "", "rgba(38,110,225,1)", 1, false));
     71         this.group.add(circleLineBottom);
     72 
     73         //绘制内部文字
     74         this.className = new Text(this.dataList.get(1).gradeName + "-" + this.dataList.get(1).subjectName, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2 - 20), new TextConfig("Arial", font18, "normal", "normal"), new RenderStyle(true, true, "#00f6ff"), new RotateConfig());
     75         this.className.moveBy(-this.className.getWidth(this.layer.canvasContext) / 2, 0);
     76         this.group.add(this.className);
     77         this.articleName = new Text(Util.subString1(this.dataList.get(1).unitName, 28, "..."), new Point(this.position.x + this.width / 2, this.position.y + this.height / 2 + 20), new TextConfig("Arial", font20, "normal", "normal"), new RenderStyle(true, true, "#00f6ff"), new RotateConfig());
     78         this.articleName.moveBy(-this.articleName.getWidth(this.layer.canvasContext) / 2, 0);
     79         this.group.add(this.articleName);
     80 
     81 
     82         //依次绘制4个圆圈
     83         let circle1 = new Circle(new CircleConfig(this.innerRadius + circlePerDis, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2)), new RenderStyle(false, true, "", "rgba(255,255,255,0.15)", 1, false));
     84         this.group.add(circle1);
     85         let circle2 = new Circle(new CircleConfig(this.innerRadius + circlePerDis * 2, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2)), new RenderStyle(false, true, "", "rgba(255,255,255,0.15)", 1, false));
     86         this.group.add(circle2);
     87         let circle3 = new Circle(new CircleConfig(this.innerRadius + circlePerDis * 3, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2)), new RenderStyle(false, true, "", "rgba(60,126,229,0.5)", 1, false));
     88         this.group.add(circle3);
     89         //最外层的圆圈有内发光效果
     90         let circle4RenderStyle = new RenderStyle(true, true, "", "rgba(60,126,229,0.5)", 1);
     91         circle4RenderStyle.setRadialGradient(new RadialGradientConfig(this.position.x + this.width / 2, this.position.y + this.height / 2, this.innerRadius, this.position.x + this.width / 2, this.position.y + this.height / 2, this.innerRadius + 73 * 4,
     92             [new PercentColor(0, "rgba(0,97,242,0)"), new PercentColor(0.7, "rgba(0,97,242,0)"), new PercentColor(0.9, "rgba(3,44,253,0.1)"), new PercentColor(1, "rgba(0,97,242,0.1)")], true, false))
     93         let circle4 = new Circle(new CircleConfig(this.innerRadius + circlePerDis * 4, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2)), circle4RenderStyle);
     94         this.group.add(circle4);
     95 
     96         //绘制纵向坐标尺
     97         let lineGroup = new Group();
     98         let lineY = new Line(new Point(this.position.x + this.width / 2, this.position.y + this.height / 2 - this.innerRadius), new Point(this.position.x + this.width / 2, this.position.y + this.height / 2 - this.innerRadius - this.maxOuterR), new RenderStyle(false, true, "", "rgba(255,255,255,0.1)"))
     99         lineGroup.add(lineY);
    100         for (let i = 0; i <= 12; i++) {
    101             let lineX = new Line(new Point(this.position.x + this.width / 2 - 6, this.position.y + this.height / 2 - this.innerRadius - circlePerDis * 4 / 12 * i), new Point(this.position.x + this.width / 2, this.position.y + this.height / 2 - this.innerRadius - circlePerDis * 4 / 12 * i), new RenderStyle(false, true, "", "rgba(255,255,255,0.2)"))
    102             lineGroup.add(lineX);
    103         }
    104 
    105         //绘制内发光激活扇形及上面的文字
    106         let innerGlowSectorImg = new CanvasImage(innerGlowSector, new RenderStyle(), new RotateConfig(), new RegionConfig(this.position.x + this.width / 2, this.position.y + this.height / 2 - 61 * this.scale, this.innerRadius + this.minOuterR + this.radiusDiff * 30, innerGlowSector.height * this.scale));
    107         this.innerGlowSectorGroup.add(innerGlowSectorImg);
    108         let activeSectorText = new Text(this.textGroup.children[1].text, new Point(this.position.x + this.width / 2 + this.innerRadius + 60 * this.scale, this.position.y + this.height / 2 + 7 * this.scale), new TextConfig("Arial", font18, "normal", "normal"), new RenderStyle(true, true, "#FFF"), new RotateConfig());
    109         this.innerGlowSectorGroup.add(activeSectorText);
    110         this.innerGlowSectorGroup.setRotateCenter(this.position.x + this.width / 2, this.position.y + this.height / 2);
    111         this.innerGlowSectorGroup.rotateBy(-45);
    112         this.group.add(this.innerGlowSectorGroup);
    113 
    114         //绘制发光箭头
    115         let arrowRenderStyle = new RenderStyle(true, false, "#FFF", "#FFF", 1, true);
    116         arrowRenderStyle.setShadowStyle(new ShadowStyle(0, 0, 20, "#FFF"))
    117         let arrowPath = new Path(arrowRenderStyle);
    118         let p0 = Util.computePointAfterRotate(this.position.x + this.width / 2 + this.innerRadius / 3 * 2, this.position.y + this.height / 2 - 4, this.position.x + this.width / 2, this.position.y + this.height / 2, -45);
    119         let p1 = Util.computePointAfterRotate(this.position.x + this.width / 2 + this.innerRadius / 3 * 2, this.position.y + this.height / 2 + 4, this.position.x + this.width / 2, this.position.y + this.height / 2, -45);
    120         let p2 = Util.computePointAfterRotate(this.position.x + this.width / 2 + this.innerRadius / 3 * 2 + 90, this.position.y + this.height / 2, this.position.x + this.width / 2, this.position.y + this.height / 2, -45);
    121         arrowPath.addPoint(new Point(p0.x, p0.y));
    122         arrowPath.addPoint(new Point(p1.x, p1.y));
    123         arrowPath.addPoint(new Point(p2.x, p2.y));
    124         this.group.add(arrowPath);
    125 
    126         //绘制左右两部分关联的折线
    127         let connectLinePath = new Path(new RenderStyle(false, true, "", "#00c6ff", 1, false));
    128         let point0 = Util.computePointAfterRotate(this.position.x + this.width / 2 + this.innerRadius + this.maxOuterR + 5 * this.scale, this.position.y + this.height / 2, this.position.x + this.width / 2, this.position.y + this.height / 2, -45);
    129         let point1 = Util.computePointAfterRotate(this.position.x + this.width / 2 + this.innerRadius + this.maxOuterR + 50 * this.scale, this.position.y + this.height / 2, this.position.x + this.width / 2, this.position.y + this.height / 2, -45);
    130         let point2 = new Point(this.width, point1.y);
    131         connectLinePath.addPoint(new Point(point0.x, point0.y));
    132         connectLinePath.addPoint(new Point(point1.x, point1.y));
    133         connectLinePath.addPoint(new Point(point2.x, point2.y));
    134         this.group.add(connectLinePath);
    135         //绘制左右两部分关联的折线起点小矩形
    136         let connectLineRect = new Rect(new RenderStyle(true, false, "#00c6ff", ""), new RotateConfig(), new RegionConfig(point0.x - 5, point0.y - 5, 10, 10));
    137         this.group.add(connectLineRect);
    138 
    139         this.group.add(lineGroup)
    140         this.layer.add(this.group)
    141     };

    创建扇形

    1     addNewSector(index: number) {
    2         let sector = new Sector(new SectorConfig(this.maxOuterR + this.innerRadius - index * this.radiusDiff, new Point(this.position.x + this.width / 2, this.position.y + this.height / 2), (index - 1) * this.degreeDiff, index * this.degreeDiff - 0.1), new RenderStyle(true, false, "", "", 1, false));
    3         this.sectorGroup.add(sector);
    4     }
    addNewSector

    创建文字

     1     addNewText(index: number, textAlign: string) {
     2         let font14 = 14 * this.scale;
     3         let text;
     4         let unitName = Util.subString1(this.dataList.get(this.textIndex).unitName, 28, "...");
     5         if (textAlign == "left") {
     6             text = new Text(unitName, new Point(this.position.x + this.width / 2 - this.innerRadius - this.textHeadDis, this.position.y + this.height / 2 + 5 * this.scale), new TextConfig("Arial", font14, "normal", "normal", "right"), new RenderStyle(true, true, "#FFF"), new RotateConfig());
     7             //获知text的宽度后 将其移动到合适的位置
     8             let width = text.width;
     9             text.moveBy(width, 0);
    10             text.setRotateCenter(this.position.x + this.width / 2, this.position.y + this.height / 2);
    11             text.rotateBy((index - 18) * 10 - 5);
    12         } else {
    13             text = new Text(unitName, new Point(this.position.x + this.width / 2 + this.innerRadius + this.textHeadDis, this.position.y + this.height / 2 + 5 * this.scale), new TextConfig("Arial", font14, "normal", "normal", "left"), new RenderStyle(true, true, "#FFF"), new RotateConfig());
    14             text.setRotateCenter(this.position.x + this.width / 2, this.position.y + this.height / 2);
    15             text.rotateBy(index * 10 - 5);
    16         }
    17 
    18         this.textGroup.add(text);
    19         this.textIndex++;
    20         this.textIndex = this.textIndex % this.dataList.length();
    21     }
    addNewText

    旋转刷新

     定时刷新画面,将角度每次变化:degreeDiff = 0.05。因为有组group的概念,所以旋转的时候整个组旋转比单个元素旋转要方便。性能也会提高,因为减少了context的API调用。

     1     update() {
     2         if (this.sectorGroup && this.textGroup) {
     3             let that = this;
     4             that.clearLayer();
     5             let degreeDiff = 0.05; 
     6             // 没旋转10度 需要新生成一个小扇形,最大扇形去除。所有扇形长度慢慢增加
     7             let allSectors = this.sectorGroup.getChildren();
     8             let allTexts = this.textGroup.getChildren();
     9             allSectors.forEach((sector: Sector, index: number) => {
    10                 sector.sectorConfig.radius += (degreeDiff / that.degreeDiff) * that.radiusDiff;
    11                 sector.changeStartDegree(sector.sectorConfig.startDegree - degreeDiff);
    12                 sector.changeEndDegree(sector.sectorConfig.endDegree - degreeDiff);
    13             });
    14             //优化text绘制方式 提高性能 begin
    15             this.textCanvasImage.rotateBy(-degreeDiff);
    16             //优化text绘制方式 提高性能 end
    17 
    18             //选中高亮扇形不断旋转
    19             this.innerGlowSectorGroup.rotateBy(-degreeDiff);
    20 
    21             this.updateDegree = Math.round((this.updateDegree + degreeDiff) * 1000) / 1000;//防止出现小数位太多的情况
    22             if ((this.updateDegree + 5) % 10 == 0) {//选中高亮扇形需要切换到下一个位置
    23                 this.currentIndex++;
    24                 this.currentIndex = this.currentIndex % this.dataList.length();
    25                 this.innerGlowSectorGroup.rotateBy(10);
    26                 let unitName = Util.subString1(this.dataList.get(this.currentIndex).unitName, 28, "...")
    27                 this.innerGlowSectorGroup.children[1].text = unitName;
    28                 this.articleName.text = unitName;
    29                 this.articleName.position.x = this.position.x + this.width / 2 - this.articleName.getWidth(this.layer.canvasContext) / 2;
    30 
    31                 this.className.text = this.dataList.get(this.currentIndex).gradeName + "-" + this.dataList.get(this.currentIndex).subjectName;
    32                 this.className.position.x = this.position.x + this.width / 2 - this.className.getWidth(this.layer.canvasContext) / 2;
    33                 this.nextCallBack && this.nextCallBack();
    34             }
    35             if (this.updateDegree % 10 == 0) {
    36                 (<Sector>allSectors[0]).removeSelf();
    37                 this.addNewSector(31);
    38                 //优化text绘制方式 提高性能 begin
    39                 allTexts.forEach((text: Text, index: number) => {
    40                     text.rotateBy(-10);
    41                 });
    42                 this.textCanvasImage.rotateBy(10);
    43                 //优化text绘制方式 提高性能 end
    44 
    45                 (<Text>allTexts[0]).removeSelf();
    46                 this.addNewText(31, "left");
    47 
    48                 //修改最底部扇形条文字的方向
    49                 (<Text>allTexts[15]).textConfig.align = "left";
    50                 (<Text>allTexts[15]).moveBy(this.position.x + this.width / 2 + this.innerRadius + this.textHeadDis - (<Text>allTexts[15]).position.x, this.position.y + this.height / 2 - (<Text>allTexts[15]).position.y);
    51                 (<Text>allTexts[15]).rotateBy((16 * 10 - 4) - (<Text>allTexts[15]).rotateConfig.rotateDegree);
    52                 this.redrawTextCanvas();
    53             }
    54             this.group.draw(this.layer.canvasContext, null);
    55         }
    56     }

    底层有个绘图库,定义了很多基本形状,像代码中sector扇形,group组,text文字类等。

    底层库github地址:https://github.com/fangsmile/Canvas-GraphLib。

    上面的代码不是很全面,如果有需要可以私信找我要哈。

  • 相关阅读:
    五分钟上手Markdown
    css中居中方法小结
    事务和同步锁
    插入排序
    插入排序
    交换排序
    eclipse 常用快捷键
    交换排序
    二叉搜索树(BST)
    二叉树遍历以及根据前序遍历序列反向生成二叉树
  • 原文地址:https://www.cnblogs.com/fangsmile/p/14722896.html
Copyright © 2011-2022 走看看