当前位置: 首页 > 科技观察

前端进阶之路:写出高质量的视觉“圈

时间:2023-03-14 22:53:39 科技观察

”。图1显示了我们通常看到的一些常见环(弧)效应。虽然图形的主要成分是圆弧,但不同的效果在信息传递的功能上还是有细微差别的。例如:一个闭合的环可以表示过程“进度”的概念一个开放的环一般用于显示状态量(标量),一般称为“仪表板”,具有不同的效果色调可以用来标识状态范围状态量(例如低风险-中风险-高风险范围可以用三种颜色组成的过渡效果来表示)。为了更方便、更完美的解决我们在业务开发中的具体问题,如果需要的话,可以对这些样式和样式进行一定程度的分析和抽象,归纳出一个通用组件需要具备的能力,比如:颜色和渐变可配置(支持单一色值或色序)弧宽可调(内、外半径大小可配置)弧的夹角可调(起始角和截止角可配置)圆弧终点形状可选(平角/半圆形状可切换,文案效果(字号、字体、颜色等)可调(字号、字体、颜色等).).为了实现这样一个功能全面、业务通用性强的环形组件1.环形的形状绘制环形形状的第一步是绘制环形图的构成元素,即的一部分e弧。对于像下图这样的两个倒角效果(黄色部分圆弧两端的样式)既可以是直角也可以是半圆。因此,我们需要实现一个通用的方法来绘制圆弧,并提供两种倒角样式给用户。1.1绘制前景圆弧绘制圆弧的思路如上图所示,大致可以依次分为几个步骤:(1)绘制圆弧起点的半圆轮廓(2)绘制外圆圆弧的边缘轮廓(3)绘制圆弧末端的半圆轮廓(4)绘制圆弧的内边缘轮廓(5)关闭轮廓并填充颜色注意:由于在canvas中绘制圆弧的方法默认是顺时针方向,我们的绘制步骤也是顺时针方向以下是一些姿势要领:1.1.1端点坐标的计算在绘制端点半圆之前,我们需要端点的位置坐标,以实际端点为例,根据到圆环的半径(内外径的平均值,即圆弧中心线的半径)如何从起点的角度计算圆上一点的坐标://计算th上的一个点的坐标earc//originX,originY-圆心坐标//radius-圆的半径,等于圆内外径的平均值,即圆心的半径弧线//alpha-radianfunctioncalcPosition(originX,originY,radius,alpha){return[radius*Math.cos(alpha)+originX,radius*Math.sin(alpha)+originY,];}1.1.2端点半圆的起始角和终止角要在画布中绘制圆弧,需要知道它的起始角和终止角。由于canvas绘图默认的方向是屏幕的顺时针方向(屏幕Z轴的左手螺旋方向),所以从上面的示意图可以看出:起始端半圆的范围-[radianStart-Math.PI,radianStart]端点处的半圆范围end-[radianEnd,radianEnd+Math.PI]1.1.3画半圆有了端点坐标和起止角,就可以画半圆在endpoint://以起始端的半圆倒角为例myCanvas.context.arc(x,y,(radiusOutter-radiusInner)/2,//小圆的半径等于线宽的一半圆radianStart-Math.PI,radianStart);直角倒角样式的绘制步骤与半圆倒角圆弧的绘制步骤基本相同,主要区别在于圆弧的两个端点不是画一个小半圆,而是画一条直线线代替。背景弧线的绘制也与前景弧线的绘制方法一致。2.Canvas实现锥形渐变。以上几步就可以画出圆弧的轮廓了。为了达到如图1所示的视觉效果,我们需要用之前绘制的轮廓填充图像。沿圆周渐变,因为它的图像看起来像一个圆锥形的俯视效果,俗称锥形渐变:众所周知,CSS中有一个属性叫conic-gradient,直接支持圆锥渐变,而HTML的原生APICanvas还没有类似的能力。那么,我们如何在画布上绘制这样的图像呢?下面说说大概的原理:(1)对用户传入的颜色进行插值,得到一个颜色序列。这里,我们直接使用canvas原生的createLinearGradient方法在离屏画布中绘制一个1px的线性渐变效果。图片宽度就是我们要插值的量,梯度插值的结果就是画布上对应像素位置的颜色值。颜色插值(渐变选色)代码实现如下://实现颜色插值的工具类exportdefaultclassColorInterpolate{//参数01:stops-为待插值的颜色序列,数据格式如下:[[0,'red'],[0.5,'green'],[1.0,'yellow']]//参数02:segment-插值段数,即插值结果的颜色值个数constructor(stops=[],segment=100){//构建离屏画布constcanvas=document.createElement('canvas');canvas.width=segment;canvas.height=1;this.ctx=canvas.getContext('2d');//绘制线性渐变constgradient=this.ctx.createLinearGradient(0,0,segment,0);for(let[offset,color]ofstops){gradient.addColorStop(offset,color);}this.ctx.fillStyle=gradient;this.ctx.fillRect(0,0,segment,1);}//根据位置偏移获取插值颜色值getColor(offset){constimgData=this.ctx.getImageData(offset,0,1,1);return`rgba(${imgData.data.slice(0,3).join(',')},${imgData.data[3]/255})`;}}(2)如下图所示,我们可以把渐变图像看作是由足够小的“扇子”拼凑而成,以填充单个颜色值。按照这个思路,我们只需要遍历上面颜色插值得到的每一种颜色,然后把小扇子一个一个画出来,就可以得到一个锥形的渐变图。为此,我们封装了一个名为createConicalGradient的方法,其使用习惯类似于canvas原有的createLinearGradient和createRadialGradient方法。具体代码见我的Github(如果觉得有用,可以star)。3.过渡动画3.1圆弧过渡当值发生变化时,我们的图表需要一个能够跟随数据变化的过渡动画效果。对于canvas来说,就是清除旧图像,然后绘制一帧新图像。下面是方法封装的一些技巧://注:伪代码,真实场景建议OOP封装为工具类let_animTick=null;let_animFrames=null;let_frameData=null;let_animDiff=null;//动画方法function_animate(duration){if(_animTick===null){//根据动画时长,计算整个动画需要多少帧(以60fps计算)_animFrames=Math.round((duration/1e3)*60);//相邻两帧ofanimationDatachange_animDiff=_calcAnimDiff(_animFrames);//动画帧数识别_animTick=0;}//当前帧数据值_frameData=_caclCurentData(_animDiff,_animTick);_renderFrame(_frameData);if(_animTick!==null&&_animTick<_animFrames){//继续执行动画window.requestAnimationFrame(()=>{_animate();_animTick+=1;});}else{//动画结束_renderFrame(_frameData);_animTick=null;}}//绘制当前帧function_renderFrame(data){//...}//计算动画funct相邻帧的数据差ion_calcAnimDiff(){//...}//根据两帧的数据差异计算当前帧function_caclCurentData(){//...}3.2两种动画模式在过渡动画执行的过程中,需要两种不同的模式需要考虑的:一是梯度图像没有变化,只有圆弧的轮廓从旧状态变为新状态;是渐变图随轮廓大小变化的角度范围。这两种模式其实都有一定的含义:前者可以用不同的颜色来表示数值的不同状态;后者只是将渐变色作为一种装饰效果。4、结果展示的关键原理已经介绍完了,最后展示一下我们封装的图表组件的效果(右边的GUI部分是我们自研设计引擎的编辑效果):