问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

SVG、SVC、FC这三种无功补偿装置的区别是什么?

发布网友 发布时间:2022-04-25 08:16

我来回答

6个回答

懂视网 时间:2022-05-15 13:21

本文我们主要和大家分享用SVG和Vanilla JS框架创建一个“星形变心形”的动画效果代码,希望能帮助到大家。

思路

它们都是由五个三次贝塞尔曲线构成。下边的互动演示展示了每条曲线以及这些曲线相连接的点。点击任意曲线或连接点可以看到两个图形的曲线是如何相对应的。

可以看出所有曲线都是由三次贝塞尔曲线创建的。即使其中一些曲线的两个控制点重叠了。

构成星形和心形的形状都是极简且不符合实际的。但它们可以做到。

初始代码

从表情动画的例子中可以看出, 我通常选择用 Pug(译:即Jade,一种模版引擎) 生成这类形状。但在这里,由于生成的路径数据还将由JavaScript处理过渡效果。包括计算坐标以及将这些坐标放入属性d 。所以使用JavaScript来做所有的这些是最好的选择。

这意味着我们不必写很多标签:

<svg> <path id='shape'/></svg>

JavaScript中,我们首先获得元素 svg 和元素 pathpath 是那个星形变心形再变回星形的形状。然后,我们给元素 svg 设置viewBox属性,使得 SVG 沿两个轴的尺寸相等,并且坐标轴的原点(0,0)在 SVG 正中间。这意味着,当viewBox的尺寸值为D 时,它的左上角坐标为(-.5*D,-.5*D)。最后,这个也很重要,就是创建一个对象来存储过渡的初始和最终状态,以及一个将我们想要的值设置给 SVG 图形属性的方法。

const _SVG = document.querySelector('svg'), 
 _SHAPE = document.getElementById('shape'), 
 D = 1000, 
 O = { ini: {}, fin: {}, afn: {} };

(function init() {
 _SVG.setAttribute('viewBox', [-.5*D, -.5*D, D, D].join(' '));
})();

现在我们把这事解决了,可以开始更有趣的部分了!

图形的几何绘制

我们用终点和控制点的初始坐标来绘制星形,用它们的最终坐标来绘制心形。 每个坐标的过渡范围是它的初始值与最终值之间的差值。在这个例子中,当星形向心形转换时,我们会转动(rotate)它,因为我们想让星形的角朝上。我们还会改变填充(fill),从金色的星形变成深红色的心形。

那么,我们怎么能获得这两个图形的终点和控制点的坐标呢?

星形

在星形的例子中,我们先从一个正五角星形开始。我们的曲线(译:构成星形每个角的曲线)终点落在正五角星形边的交叉点上,我们把正五角星形的顶点作为控制点。

五个三次贝塞尔曲线的终点和控制点用黄点标识在了正五角星形的顶点和边的交叉点上(Live)

直接给定正五角星形外接圆的半径(或者直径)就可以获得五角星形的顶点。也就是我们给 SVG 的viewBox 设定的尺寸(简单起见,在这种情况下我们不考虑高填密)。但是如何获得他们的交叉点呢?

首先,我们先看下边的说明图。注意图中正五角星形中间高亮标注的小五边形。小五边形的顶点与正五角星形边的交叉点是重合的。这个小五边形显然是个正五边形(译:五个边的长度相等)。这个小正五边形的内切圆和内径跟正五角星形的是同一个。

正五角星形和内部的正五边形的内切圆是同一个 (Live)。

因此,如果我们计算出正五角星形的内径,那么也就获得了正五边形的内径。这个内径和圆心角 一起对应正五边形的边。根据这个我们就可以获得正五边形的外接圆半径 。这样就可以倒推出正五边形顶点的坐标。这些点正是正五角星形边的交叉点,也就是星形五个三次贝塞尔曲线的终点。

我们的正五角星形可以用拓扑符号 {5/2}来表示。也就是说,正五角星形有5个顶点。这5个顶点均匀分布在它的外接圆上,间隔是 360°/5 = 72°。我们从第一个点开始,跳过紧挨着的下一个点,连接到紧挨着的第二个点(这就是符号{5/2}2的含义;1 代表的意思是连接到第一个点,不跳过任何点,构成一个五边形)。照这样一直连接,就可以画出正五角星形了。

在下边的演示中,点击五边形或者五角星形按钮,查看它们是怎样被绘制的。

这样,我们得到正五角星形的边所对应的的圆心角是正五边形的边所对应的圆心角的二倍。那么正五边形是1 * (360°/5) = 1 * 72° = 72° (或者1 * (2 * π / 5)弧度),那正五角星形就是2 * (360° / 5) = 2 * 72° = 144°2 * (2 * π / 5)弧度)。通常,一个用拓扑符号表示为{p,q}的正多边形,它的一个边所对应的圆心角就是 q * (360° / p)q * (2 * π / p) 弧度)。

正多边形的一条边所对应的圆心角:正五角星形(左,144°)vs 正五边形(右,``72°`)(Live)。

已知正五角星形外接圆半径,也就是的viewBox尺寸。那么,已知直角三角形斜边的长(即正五角星形外接圆的半径)和锐角的度数(正五角星形一条边所对应的角度的一半),这意味着我们可以算出正五角星形的内径(这个内径与正五角星形内部的小正五边形的内径相等)。

通过直角,可以计算出正五角星形的内径长。这个直角的斜边等于正五角星形外接圆半径,其中一个锐角的角度等于正五角星形一条边所对应的角度的一半 (Live)。

圆心角一半的余弦等于五角星形的内径比外接圆半径。就可以得出,五角星形的内径等于外接圆半径乘以这个余弦值。

现在我们得到了正五角星形内部小正五边形的内接圆半径,我们就可以计算出这个正五边形的外接圆半径了。还是通过一个小直角来计算。这个直角的斜边等于正五边形外接圆半径。一个锐角等于正五边形一条边所对应的圆心角的一半。这个锐角的一条边是这个圆心角的中直线,这个中直线是正五边形的外接圆半径。

下边的说明图中高亮标注了一个直角三角形,它是由正五边形的一条外接圆半径、内接圆半径、一个圆心角的一半构成的。如果我们已知内接圆半径和正五边形一条边所对应的圆心角,这个圆心角的一半也就是两条外接圆半径的夹角的话。用这个直角三角形我们可以计算出外接圆半径的长。

通过一个直角三角形计算正五边形外接圆的半径 (Live)。

前文提到过,正五边形圆心角的度数与正五角星形的圆心角度数是不相等的。前者是后者的一半 (360° / 5 = 72°)。

好,现在我们有了这个半径,就可以得到所有想要的点的坐标了。这些点均匀分布在两个圆上。有5个点在外层的圆上(正五角星形的外接圆),还有5个在内层的圆上(小正五边形的外接圆)。共计10个点,他们所在的半径射线的夹角是 360° / 10 = 36°

终点均匀分布在小正五边形的外接圆上,控制点均匀分布在正五角星形的外接圆上 (Live)

已知两个圆的半径。外层圆的半径等于正五角星形外接圆半径,也就是我们定的有点儿随意的viewBox 尺寸的一部分(.5.25.32或者我们认为效果更好地尺寸)。内层圆的半径等于正五角星形内部构成的小正五边形的外接圆半径。计算这个半径的方法是:首先,通过正五角星形的外接圆半径和它的一条边所对应的圆心角计算出正五角星形的内接圆半径。这个内接圆半径与小正五边形的内接圆半径相等;然后,再通过小正五边形一条边所对应的圆心角和它的内接圆半径来计算。

所以,基于这点,我们就能够生成绘制星形的路径的数据了。绘制它所需要的数据,我们都已经有了。

那么让我们来绘制吧!并且把上边的思考过程写成代码。

首先,先创建一个getStarPoints(f) 的函数。参数 (f) 将决定根据 viewBox的尺寸获取的正五角星形外接圆半径是多少。这个函数返回一个由坐标组成的数组,之后我们会给这个数组增加数组项。

在这个函数中,我们首先计算常量:正五角星形外接圆半径(外层圆的半径)、正五角星形一条边所对应的圆心角、正五角星形内部构成的正五边形的一条边所对应的圆心角、正五角星形内部构成的正五边形的一条边所对应的圆心角、正五角星形和内部构成的正五边形共用的内接圆的半径(正五变形的顶点是正五角星形边的交叉点)、内部小正五变形的外接圆半径、需要计算坐标的点的总数、所有点所在的径向线的夹角。

然后,用一个循环来计算我们想要的点的坐标,并将它们插入坐标数组中。

const P = 5; // 三次曲线、多边形顶点数function getStarPoints(f = .5) { const RCO = f*D,  // outer (pentagram) circumradius 
 BAS = 2*(2*Math.PI/P), // base angle for star poly
 BAC = 2*Math.PI/P, // base angle for convex poly 
 RI = RCO*Math.cos(.5*BAS),// pentagram/ inner pentagon inradius
 RCI = RI/Math.cos(.5*BAC),// inner pentagon circumradius 
 ND = 2*P,   // total number of distinct points we need to get 
 BAD = 2*Math.PI/ND, // base angle for point distribution 
 PTS = [];   // array we fill with point coordinates for(let i = 0; i < ND; i++) {

 } return PTS;
}

计算坐标需要的条件:用点所在圆的半径,以及一条半径与水平轴线构成的夹角。如下面的交互式演示所示(拖动点来查看它的笛卡尔坐标如何变化):

在我们的例子里,当前的半径有两个。一个是外圆的半径(正五角星形的外接圆半径RCO),可以帮助算出索引值为偶数的点的的坐标(0, 2, ...)。还有一个是内接圆的半径(内部小正五边形的外接圆半径RCI),可以帮助算出索引值为奇数的点的的坐标(1, 3, ...)。当前点与圆心点的连线所构成的径向线的夹角等于点的索引值(i)乘以所有点所在的径向线的夹角(BAD,在我们的例子里恰巧是36°π / 10)。

因此,循环体里的代码如下:

for(let i = 0; i < ND; i++) {
 let cr = i%2 ? RCI : RCO, 
 ca = i*BAD, 
 x = Math.round(cr*Math.cos(ca)), 
 y = Math.round(cr*Math.sin(ca));
}

由于我们给viewBox 设定的尺寸足够大,所以我们可以放心的给坐标值做四舍五入计算,舍弃小数部分,这样我们的代码看起来会更干净。

我们会把外层圆(索引值是偶数的情况)计算出的坐标值推入坐标数组中两次。因为实际上星形在这个点上有两个重叠的控制点。如果要绘制成心形,就要把这两个重叠的控制点放在别的的位置上。

for(let i = 0; i < ND; i++) { // same as before

 PTS.push([x, y]); if(!(i%2)) PTS.push([x, y]);
}

接下来,我们给对象O添加数据。添加一个属性(d)来储存有关路径的数据。设置一个初始值来储存数组,这个数组是由上文提到的函数计算出的点的坐标组成的。我们还创建了一个函数用来生成实际的属性值(这个例子中,曲线的两个终点坐标的差值范围是路径的数据串,浏览器根据这个数据串绘制图形)。最后,我们获得了所有已经保存了数据的属性,并将这些属性的值作为前面提到的函数的返回值:

(function init() { // same as before

 O.d = {
 ini: getStarPoints(), 
 afn: function(pts) {  return pts.reduce((a, c, i) => {  return a + (i%3 ? ' ' : 'C') + c
  }, `M${pts[pts.length - 1]}`)
 }
 }; for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p].ini))
})();

绘制的结果可以在下边的演示中看到:

这是一个很有前途的星形。但我们想让生成的五角星形第一个尖朝下并且由它生成的星形的第一个尖朝上。目前,他们的指向都偏右了。这是因为我们是从 开始的(对应时钟的三点位置)。所以为了能从时钟6点的位置开始,我们给getStarPoints() 函数中的每个角加 90°π / 2 弧度)。

ca = i*BAD + .5*Math.PI

这样生成的五角星形和由它生成的星形的第一个角就都朝下了。为了旋转星形,我们需要给它的 transform 属性设置成旋转半个圆的角度。为了到达这个效果,我们首先设置初始的旋转角度为-180 。然后,我们把生成实际属性值的函数设置成这样一个函数。这个函数接收两个参数,一个是函数名字,另一个为参数,函数返回由这两个参数组成的字符串:

function fnStr(fname, farg) { 
 return `${fname}(${farg})` 
};

(function init() { // same as before

 O.transform = { 
 ini: -180, 
 afn: (ang) => fnStr('rotate', ang) 
 }; // same as before})();

我们用类似的方式给我们的星形填充(fill)金色。我们给初始值设置一个 RGB 字符串,用同一个函数来给属性(fill)设置值:

(function init() { // same as before 

 O.fill = { 
 ini: [255, 215, 0], 
 afn: (rgb) => fnStr('rgb', rgb) 
 }; // same as before})();

现在我们用 SVG 绘制好了一个漂亮的金色星形,它是由五个三次贝塞尔曲线构成的:

心形

我们已经绘制好星形了,现在来看下如何绘制心形吧!

我们先从两个半径相等并横向相交的圆开始,这两个圆都是 viewBox 尺寸的一部分(暂时定位.25)。这两个圆相交的方式为:它们中心点相连的线落在 x 轴上,它们相交点相连的线落在 y 轴上。这两条线要相等。

我们先从两个半径相等的相交的圆开始。这两个圆的圆心落在水平轴上,他们相交的点落在垂直轴上 (Live)。

接着,我们画两条直径,这两条直径穿过靠上的那个交点。在直径与圆的另一个交点处画一条正切线。这两条正切线在 y 轴相交。

画两条直径,穿过两个圆相交的点中靠上的那个,并在直径与圆的另一个交点处画正切线,两条正切线在垂直轴相交 (Live)。

两个圆上边的交点和两个直径与圆的另两个交点构成了我们需要的5个点中的3个。另外两个终点则是把外侧的半圆切割成两个相等弧线的中点,这使我们得到4个四分之一圆弧。

高亮显示了构成心形的三次贝塞尔曲线的终点以及靠下的那条曲线的控制点(Live)。

靠下的曲线控制点很明显已经得到了,就是两条切线的交点。但是另外四条曲线的控制点呢?我们怎么能把圆弧变成三次贝塞尔曲线呢?

我们无法得到四分之一圆弧的三次贝塞尔曲线,但我们可以得到一个近似的,在这篇文章中有阐述。

这篇文章告诉我们,可以用一个值为R 的半径,和半径的切线(NQ)来绘制四分之一圆弧。两条半径的切线相交于点 P。四边形 ONPQ 的四个角都等于90° (或π / 2,其中三个是公理得出的(O90° ,两条切线与半径的夹角也是90°),最后一个是计算得出的(内角的合是 360°,其它三个角都是90°, 最后一个角也就是90°了)。这样 ONPQ 就是一个矩形。同时 ONPQ 有两个相邻的边是相等的(OQON 的长度都等于半径R),这样它就是一个边长为R的正方形。所以 NPQP 长也等于R

用三次贝塞尔曲线绘制近似四分之一圆弧的弧线 (Live)。

我们用三次贝塞尔曲线绘制的近似四分之一圆弧的弧线的控制点就在切线 NPQP 上,也就是从终点算起C * R的长度,C在之前提到文章中算出的值是.551915

知道了上边这些,我们可以开始计算三次贝塞尔曲线终点和控制点的坐标了,有了这些坐标,就可以构建我们的心形了。

由于我们选择这种方式构建心形, TO0SO1是一个四边相等(四个边都是由两个圆的半径构成)的 正方形 ,并且它的对角线也是相等的(这点前文有说过,两个圆心的连线与两个交点的连线相等)。这里,O 是两个对角线的交点,并且 OT 等于对角线 ST 的一半。TSy 轴上,所以他们的 x 坐标为 0。他们的 y 坐标对应 OT 的绝对值,也就是对角线的一半(OS 同理)。

正方形 TO0SO1 (Live)

我们可以把任意边长为l的正方形切割成两个等腰三角形。这个等腰三角形的直角边与正方形的边重合,斜边与正方形对角线重合。

任意正方形可以被切割成两个等腰三角形(Live)。

利用勾股定理:d? = l? + l?,我们可以计算出其中一个直角的斜边(也就是正方形的对角线)。这样根据边长就可以得出正方形对角线的长 d = √(2 * l) = l * √2(相反,根据对角线的长就可以得出边的长 l = d / √2)。还能计算出对角线的一半d / 2 = (l * √2) / 2 = l / √2

把这个应用到我们的边长为 RTO0SO1 正方形上,我们得到 T 点(它的绝对值等于正方形对角线的一半)的 y 坐标是 -R / √2 ,同时 S 点的 y 坐标是R / √2

TO0SO1 正方形四个顶点的坐标 (Live)

类似的,O1 点在 x 轴上,所以他们的 y 轴坐标为0,他们的 x 轴坐标是对角线 OO1 的一半:±R/√2

TO0SO1 是个正方形,那么它的四个角都是90°π / 2圆弧)。

四边形 TA1B1S (Live)

如上图所示,直线 TB1 是对角线,也就是说圆弧 TB1 是圆形的一半,或者叫做180°弧线。我们用 A1 点将这个弧分割成了相等的两半儿,得到两个相等的 90° 弧线:TA1A1B1 。他们对应两个相等的 90° 角:∠TO1A1∠A1O1B1

根据公理 ∠TO1S∠TO1A1 都是90°的角,这证明直线 SA1 也是直径。这告诉我们在四边形 TA1B1S 中,对角线 TB1SA1 是垂直且相等的,并且相交于各自的中心点(TO1O1B1SO1O1A1 都等于圆形的的半径R)。这说明四边形 TA1B1S 是正方形,且它的对角线等于2 * R

到这里我们就可以得到四边形 TA1B1S 的边等于2 * R / √2 = R * √2。由于正方形所有的角都是90° ,并且边 TS 与垂直轴重叠,所以边 TA1SB1 是水平的,且平行于 x 轴。根据他们的长度可以算出 A1B1 两点的 x 轴坐标:±R * √2

因为 TA1SB1 是水平的, 所以 A1B1 两点的 y 轴坐标分别等于 T (-R / √2)S (R / √2) 点。

正方形 TA1B1S 四个顶点坐标(Live)

我们从这里得到的另一个结论是,因为 TA1B1S 是正方形,所以 A1B1 平行于 TS ,因为 TSy (垂直)轴上,所以 A1B1 也是垂直的。此外,因为 x 轴平行于 TA1SB1 ,并且将 TS 平分切为两断,所以 x 轴也将 A1B1 平分切为了两断。

现在让我来看看控制点。

我们先从最下边弧线的重叠的控制点开始。

四边形 TB0CB1 (Live)

四边形 TB0CB1 的所有角都等于 90° (因为 TO0SO1 是正方形所以 ∠T 是直角;因为 B1C 是圆的切线,它与半径 O1B1 垂直,并相交于 B1 点, 所以 ∠B1 是直角;因为其他三个都是直角,所以 ∠C 也是直角),所以它是个矩形。同样它有两个相邻的边相等:TB0TB1。这两条线都是圆形的直径,且都等于 2 * R。最后得出结论四边形 TB0CB1 是一个边长为2 * R的正方形。

然后我们可以得到它的对角线 TC2 * R * √2。因为 Cy 轴上,它的 x 轴坐标为 0。它的 y 轴坐标是 OC 的长度。OC 的长度等于 TC 减去 OT2 * R * √2 - R / √2 = 4 * R / √2 - R / √2 = 3 * R / √2

正方形 TB0CB1 四个顶点的坐标 (Live)

现在我们得到了最下边弧线两个重叠的控制点的坐标为(0,3 * R / √2)

为了获得其他曲线控制点的坐标,我们在他们的终点上画切线,并且获得这些切线的交叉点 D1E1

四边形 TO1A1D1A1O1B1E1 (Live)

在四边形 TO1A1D1 中,已知所有角都是直角(90° ),其中三个是公理得出的(∠D1TO1∠D1A1O1 是由半径和切线获得的;∠TO1A1 是对应四分之一弧 TA1 的角),那么第四个角通过计算就得出也是直角。这证明 TO1A1D1 是矩形。又因为它有两个相邻的边相等(O1TO1A1 等于半径 R),所以 TO1A1D1 是正方形。

这说明对角线 TA1O1D1 等于 R * √2。已知 TA1 是水平的,又正方形两个对角线是垂直的,就证明 O1D1 是垂直的。那么点 O1D1x 轴坐标相等,O1x 轴坐标是±R / √2。因为我们知道 O1D1 的长,所以我们可以算出 y 轴坐标:如前文提到的那样用对角线的长( R * √2)做减法。

四边形 A1O1B1E1 的情况类似。已知所有角都是直角(90°),其中三个是公理得出的(∠E1A1O1∠E1B1O1 是由半径和切线获得的;∠A1O1B1 是对应四分之一弧 A1B1 的角),那么第四个角通过计算就得出也是直角。这证明 A1O1B1E1 是矩形。又因为它有两个相邻的边相等(O1A1O1B1 等于半径R),所以 A1O1B1E1 是正方形。

至此,我们得到对角线 A1B1O1E1 的长为R * √2。我们知道 A1B1 是垂直的,并且被水平轴切割成相等的两半儿,也就是 O1E1 在水平轴上,点 E1y 轴坐标为0。因为点 O1x 轴坐标为±R / √2,并且 O1E1 等于R * √2,我们就可以计算出点 E1x 轴坐标为:±3 * R / √2

四边形 TO1A1D1A1O1B1E1 的顶点坐标(Live)

但是这些切线的交叉点并不是控制点,所以我们需要用近似圆弧形的方法来计算。我们想要的控制点在 TD1A1D1A1E1B1E1 上,距离弧线终点(TA1B1)大约55%(这个值来源于前文提到的那篇文章中算出的常量C的值)的位置。也就是说从终点到控制点的距离是C * R

在这种情况下,我们的控制点坐标为:终点(TA1B1)坐标的1 - C,加上,切线交点(D1E1)坐标的 C

让我们把这些写入JavaScript代码吧!

跟星形的例子一样,我们先从函数getStarPoints(f) 开始。根据这个函数的参数 (f) ,我们可以从viewBox 的尺寸中获得辅助圆的半径。这个函数同样会返回一个坐标构成的数组,以便我们后边插入数组项。

在函数中,我们先声明常量。

  • 辅助圆的半径。

  • 边与这个辅助圆半径相等的小正方形对角线的一半。对角线的一半也是这些正方形外接圆半径。

  • 三次贝塞尔曲线终点的坐标值(点TA1B1),沿水平轴的绝对值。

  • 然后我们把注意力放在切线交点的坐标上( 点 CD1E1 )。这些点或者与控制点(C)重合,或者可以帮助我们获得控制点(例如点 D1E1)。

    function getHeartPoints(f = .25) { const R = f*D,   // helper circle radius 
     RC = Math.round(R/Math.SQRT2), // circumradius of square of edge R 
     XT = 0, YT = -RC,  // coords of point T 
     XA = 2*RC, YA = -RC,  // coords of A points (x in abs value) 
     XB = 2*RC, YB = RC,  // coords of B points (x in abs value)
     XC = 0, YC = 3*RC,  // coords of point C 
     XD = RC, YD = -2*RC,  // coords of D points (x in abs value) 
     XE = 3*RC, YE = 0;  // coords of E points (x in abs value)}

    点击下边交互演示上的点,可以展示这些点的坐标:

    现在我们可以通过终点和切线交点来获得控制点:

    function getHeartPoints(f = .25) { // same as before // const for cubic curve approx of quarter circle const C = .551915, 
     CC = 1 - C, 
     // coords of ctrl points on TD segs
     XTD = Math.round(CC*XT + C*XD), YTD = Math.round(CC*YT + C*YD), 
    
     // coords of ctrl points on AD segs
     XAD = Math.round(CC*XA + C*XD), YAD = Math.round(CC*YA + C*YD), 
    
     // coords of ctrl points on AE segs 
     XAE = Math.round(CC*XA + C*XE), YAE = Math.round(CC*YA + C*YE), 
    
     // coords of ctrl points on BE segs
     XBE = Math.round(CC*XB + C*XE), YBE = Math.round(CC*YB + C*YE); // same as before}

    下一步,我们要把相关的坐标合成一个数组,并将这个数组返回。在星形的例子中,我们是从最下边的弧形开始的,然后按照顺时针方向绘制,所以在这里我们用同样的方法。每个曲线,我们为控制点放入两组坐标,为终点放入一组坐标。

    请注意,第一个曲线(最下边的那个),他的两个控制点重叠了,所以我们把相同的坐标组合推入两次。代码看起来也许并不像绘制星形时那样整洁好看,但可以满足我们的需求:

    return [
     [XC, YC], [XC, YC], [-XB, YB], 
     [-XBE, YBE], [-XAE, YAE], [-XA, YA], 
     [-XAD, YAD], [-XTD, YTD], [XT, YT], 
     [XTD, YTD], [XAD, YAD], [XA, YA], 
     [XAE, YAE], [XBE, YBE], [XB, YB]
    ];

    现在我们可以把星形的最终状态设置成函数getHeartPoints(),没有旋转,没有填充( fill)深红色。然后把当前状态设置成最终状态,以便能看到心形:

    function fnStr(fname, farg) { 
     return `${fname}(${farg})` 
    };
    
    (function init() { 
     _SVG.setAttribute('viewBox', [-.5*D, -.5*D, D, D].join(' '));
    
     O.d = {
     ini: getStarPoints(), 
     fin: getHeartPoints(), 
     afn: function(pts) { return pts.reduce((a, c, i) => {  return a + (i%3 ? ' ' : 'C') + c
     }, `M${pts[pts.length - 1]}`)
     }
     };
    
     O.transform = {
     ini: -180, 
     fin: 0, 
     afn: (ang) => fnStr('rotate', ang)
     };
    
     O.fill = {
     ini: [255, 215, 0], 
     fin: [220, 20, 60], 
     afn: (rgb) => fnStr('rgb', rgb)
     }; for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p].fin))
    })();

    这个心形看上去很不错:

    确保两个图形是对齐的

    如果我们不给图形填充( fill)颜色、不旋转(transform)图形,只是看他们的骨架(stroke)叠在一起。就会发现它们并没有对齐:

    解决这个问题最简单的方法就是利用辅助圆的半径把心形向上移动一些:

    return [ /* same coords */ ].map(([x, y]) => [x, y - .09*R])

    现在我们已经对齐了,忽略我们是如何调整这两个例子的f参数的。这个参数在星形中决定了五角星形外接圆半径与viewBox尺寸的对应关系(默认值是 .5),在心形中决定了辅助圆的半径与viewBox尺寸的对应关系(默认值是 .25)。

    在两个图形中切换

    当点击的时候,我们希望能从一种图形转换成另一种。为了做到这个,我们设置一个dir变量,当我们从星形变成心形时,它的值是1。当我们从心形转换成星形时,它的值是-1。初始值是-1,已达到刚刚从心形转换成星形的效果。

    然后我们在元素_SHAPE上添加一个click事件监听,监听的函数内容为:改变变量dir的值、改变图形的属性。这样就可以获得从一个金色星形转换成深红色心形,再变回星形的效果:

    let dir = -1;
    
    (function init() { 
     // same as before
    
     _SHAPE.addEventListener('click', e => {
     dir *= -1; for(let p in O)
     _SHAPE.setAttribute(p, O[p].afn(O[p][dir > 0 ? 'fin' : 'ini']));
     }, false);
    })();

    现在我们可以通过点击图形在两种图形中转换了:

    在两个图形中转变

    我们最终想要的并不是两个图形间唐突的切换,而是柔和的渐变效果。所以我们用以前的文章说明的插值技术来实现。

    首先我们要决定转变动画的总帧数(NF),然后选择一种我们想要的时间函数:从星形到心形的的路径(path)转变我们选择ease-in-out函数,旋转角度的转变我们选择 bounce-ini-fin 函数,填充(fill)颜色转变我们选择ease-out 函数。我们先只做这些,如果之后我们改变注意了想探索其它的选项,也可以添加。

    /* same as before */const NF = 50, 
    TFN = { 'ease-out': function(k) { return 1 - Math.pow(1 - k, 1.675)
     }, 
     'ease-in-out': function(k) { return .5*(Math.sin((k - .5)*Math.PI) + 1)
     }, 'bounce-ini-fin': function(k, s = -.65*Math.PI, e = -s) { return (Math.sin(k*(e - s) + s) - Math.sin(s))/(Math.sin(e) - Math.sin(s))
     }
    };

    然后我们为每种属性指定转换时使用的时间函数。

    (function init() { 
     // same as before
    
     O.d = { // same as before 
     tfn: 'ease-in-out'
     };
    
     O.transform = { // same as before
     tfn: 'bounce-ini-fin'
     };
    
     O.fill = { // same as before 
     tfn: 'ease-out'
     }; // same as before})();

    我们继续添加请求变量 IDrID)、当前帧变量 (cf) 、点击时第一个被调用并在每次显示刷新的时候都会被调用的函数update() 、当过渡结束时被调用的函数stopAni(),这个函数用来退出循环动画。在 update()函数里我们更新当前帧 cf,计算进程变量 k,判断过渡是否结束,是退出循环动画还是继续动画。

    我们还会添加一个乘数变量 m ,用于防止我们从最终状态(心形)返归到最初状态(星形)时倒转时间函数。

    let rID = null, cf = 0, m;function stopAni() {
     cancelAnimationFrame(rID);
     rID = null; 
    };function update() {
     cf += dir; let k = cf/NF; if(!(cf%NF)) {
     stopAni(); return
     }
    
     rID = requestAnimationFrame(update)
    };

    然后我们需要改变点击时所做的事情:

    addEventListener('click', e => { if(rID) stopAni();
     dir *= -1;
     m = .5*(1 - dir);
     update();
    }, false);

    update()函数中,我们需要设置当过渡到中间值(取决于进程变量k)时的属性。如同前边的文章中所述,最好是在开始时计算出最终值和初始值之间的差值范围,甚至是在设置监听之前就设置好,所以我们的下一步是:创建一个计算数字间差值范围的函数。无论在这种情况下,还是在数组中,无论数组的嵌套有多深,都可以这个函数来设置我们想要转变的属性的范围值。

    function range(ini, fin) { return typeof ini == 'number' ? 
     fin - ini : 
     ini.map((c, i) => range(ini[i], fin[i]))
    };
    
    (function init() { 
     // same as before for(let p in O) {
     O[p].rng = range(O[p].ini, O[p].fin);
     _SHAPE.setAttribute(p, O[p].afn(O[p].ini));
     } // same as before})();

    现在只剩下 update() 函数中有关插值的部分了。使用一个循环,我们会遍历所有我们想要从一个状态顺滑转换到另一个状态的属性。在这个循环中,我们先得到插值函数的运算结果,然后将这些属性设置成这个值。插值函数的运算结果取决于初始值(s)、当前属性(inirng)的范围(s)、我们使用的定时函数(tfn) 和进度(k):

    function update() { 
     // same as before for(let p in O) { let c = O[p];
    
     _SHAPE.setAttribute(p, c.afn(int(c.ini, c.rng, TFN[c.tfn], k)));
     } // same as before};

    最后一步是编写这个插值函数。这跟获得范围值的那个函数非常相似:

    function int(ini, rng, tfn, k) { return typeof ini == 'number' ? 
     Math.round(ini + (m + dir*tfn(m + dir*k))*rng) : 
     ini.map((c, i) => int(ini[i], rng[i], tfn, k))
    };

    最后获得了一个形状,当点击它时可以从星形过渡转换成心形,第二次点击的时候会变回星形!

    这几乎就是我们想要的了:但还有一个小问题。对于像角度值这样的循环值,我们并不想在第二次点击的时候将他调转。相反,我们希望他继续顺着同一个方向旋转。通过两次点击后,正好能旋转一周,回到起点。

    我们通过给代码添加一个可选的属性,稍稍调整更新函数和插值函数:

    function int(ini, rng, tfn, k, cnt) { return typeof ini == 'number' ? 
     Math.round(ini + cnt*(m + dir*tfn(m + dir*k))*rng) : 
     ini.map((c, i) => int(ini[i], rng[i], tfn, k, cnt))
    };function update() { 
     // same as before for(let p in O) { let c = O[p];
    
     _SHAPE.setAttribute(p, c.afn(int(c.ini, c.rng, TFN[c.tfn], k, c.cnt ? dir : 1)));
     } // same as before };
    
    (function init() { 
     // same as before
    
     O.transform = {
     ini: -180, 
     fin: 0, 
     afn: (ang) => fnStr('rotate', ang),
     tfn: 'bounce-ini-fin',
     cnt: 1
     }; // same as before})();

    现在我们得到了我们想要的最终结果:一个从金色星形变成深红色心形的形状,每次从一个状态到另一个状态顺时针旋转半圈。

    热心网友 时间:2022-05-15 10:29

    SVG与SVC无功补偿装置的对比分析

    1.工作原理不同

    (1) SVC可以被看成是一个动态的无功源。根据接入电网的需求,它可以向电网提供容性无功,也可以吸收电网多余的感性无功,把电容器组通常是以滤波器组接入电网,就可以向电网提供无功,当电网并不需要太多的无功时,这些多余的容性无功,就由一个并联的电抗器来吸收。电抗器电流是由一个可控硅阀组控制,借助于对可控硅触发相角的调整,就可以改变流过电抗器的电流有效值,从而保证SVC在电网接入点的无功量正好能将该点电压稳定在规定范围内,起到电网无功补偿的作用。

    (2) SVG以大功率电压型逆变器为核心,通过调节逆变器输出电压的幅值和相位,或者直接控制交流侧电流的幅值和相位,迅速吸收或发出所需的无功功率,实现快速动态调节无功功率的目的。
    2.响应速度快

    一般SVC的响应速度是20—40ms;而SVG的响应速度不大于5ms,能更好的抑制电压波动和闪变,在相同的补偿容量下,SVG对电压波动和闪变的补偿效果最好。

    3.低电压特性好

    SVG具有电流源的特性,输出容量受母线电压的影响很小 。这一优点使SVG用于电压控制时具有很大的优势,系统电压越低,越需要动态无功调节电压,SVG的低电压特性好,输出的无功电流与系统电压没有关系,可以看作是一个可控恒定的电流源,系统电压降低时,仍能输出额定无功电流,具备很强的过载能力;

    而SVC是阻抗型特性,输出容量受母线电压的影响很大,系统电压越低,输出无功电流的能力成比例降低,不具备过载能力。因此SVG的无功补偿能力与系统电压无关,而SVC的无功补偿能力随系统电压的下降线性降低。

    热心网友 时间:2022-05-15 11:47

    SVG是动态无功补偿装置,又号称静止无功发生器,SVC 是静态无功补偿装置,而FC是最低端的电容,电抗器一类的补偿装置,如果你想好好治理无功的话,建议你用SVG会好点,望采纳

    热心网友 时间:2022-05-15 13:22

    先来介绍一下,SVG是英文Static Var Generator的缩写,意思是静止无功发生器;SVC是英文Static Var Compensator的缩写,是无功补偿器的意思(为了方便,在下文中我们分别用英文大写首字母缩写表示).
    下面来分别介绍它们两个,让我们一起来看看他们的区别:
    首先说SVG,它可分为电压型和电流型两种,其既可提供滞后的无功功率,又可提供超前的无功功率。简单地说,SVG的基本原理就是将自换相桥式电路通过电抗器或者直接并联在电网上,适当调节桥式电路交流侧输出电压的相位和幅值,或者直接控制其交流侧电流,就可以使该电路吸收或者发出满足要求的无功电流,实现功率无功补偿的目的。SVC,它是用于无功补偿典型的电力电子装置,它是利用晶闸管作为固态开关来控制接入系统的电抗器和电容器的容量,从而改变输电系统的导纳。按控制对象和控制方式不同,分为晶闸管控制电抗器(TCR)和晶闸管投切电容器(FC)配合使用的静止无功补偿装置(FC+TCR)和TCR与机械投切电容器(MSC)配合使用的装置。如果你想补偿无功建议你上华西科技SVG,华西科技SVG是目前补偿无功最好的设备。

    热心网友 时间:2022-05-15 15:13

    1、SVG静止无功发生器:采用电能变换技术实现无功补偿,该装置产生的无功和滤除谐波是靠其内部电子开关频繁动作来产生无功电流和与谐波电流相反的电流。

    2、 SVC静止无功补偿装置:采用的是无源器件进行无功补偿,该装置产生的无功和滤除谐波是靠电容和电抗本身的性质产生的。

    3、  FC无源滤波补偿装置:采用的是滤波电抗器和滤波电容器在特征次谐波频率下形成LC串联谐振,对该次谐波相当于一个低阻抗通道,使谐波电流大部分流入滤波回路。

    所谓“低压无功补偿成套装置”,是指用于低压线路(380V等级)的、用来补偿用电设备无功功率的成套设备。常见的就是低压配电柜中的【低压电容柜】。

    热心网友 时间:2022-05-15 17:21

    北 京领 步为您解答:
    SVG静止无功发生器:采用电能变换技术实现无功补偿,该装置产生的无功和滤除谐波是靠其内部电子开关频繁动作来产生无功电流和与谐波电流相反的电流。
    SVC静止无功补偿装置:采用的是无源器件进行无功补偿,该装置产生的无功和滤除谐波是靠电容和电抗本身的性质产生的。
    FC无源滤波补偿装置:采用的是滤波电抗器和滤波电容器在特征次谐波频率下形成LC串联谐振,对该次谐波相当于一个低阻抗通道,使谐波电流大部分流入滤波回路。
    “SVG、SVC、FC”三种无功补偿装置的区别是什么?

    1、SVG静止无功发生器:采用电能变换技术实现无功补偿,该装置产生的无功和滤除谐波是靠其内部电子开关频繁动作来产生无功电流和与谐波电流相反的电流。2、 SVC静止无功补偿装置:采用的是无源器件进行无功补偿,该装置产生的无功和滤除谐波是靠电容和电抗本身的性质产生的。3、 FC无源滤波补偿装置:采用的...

    SVG、SVC、FC这三种无功补偿装置的区别是什么?

    1.工作原理不同 (1) SVC可以被看成是一个动态的无功源。根据接入电网的需求,它可以向电网提供容性无功,也可以吸收电网多余的感性无功,把电容器组通常是以滤波器组接入电网,就可以向电网提供无功,当电网并不需要太多的无功时,这些多余的容性无功,就由一个并联的电抗器来吸收。电抗器电流是由一...

    svc和svg有什么区别

    SVC和SVG都是电力系统中用于无功补偿的设备,但它们的工作原理、应用场景以及提供的无功补偿类型有所不同。SVC代表静止无功补偿器,而SVG代表静止无功发生器。1. 工作原理:- SVC(静止无功补偿器)通常是通过晶闸管控制电抗器(TCR)或晶闸管投切电容器(TSC)来实现无功功率的补偿。TCR通过改变晶闸管的触...

    SVG和SVC的区别

    1、SVC是静止式动态无功补偿装置,分TCR和TSC两种。SVG是静止无功发生器,采用电能变换技术实现无偿功补。2、SVC的无功补偿不能连续可调,而且只能输出容性。SVG动态无功补偿可从感性到容性连续调节。3、SVG占地面积小,安全性高。SVC占地面积较大,损耗也更大。

    SVG和SVC的区别

    SVC是静止式动态无功补偿装置,分TCR和TSC两种。SVG是SVC的升级版,在很多方面优于SVC。SVC的无功补偿不能连续可调,而且只能输出容性。SVG动态无功补偿可从感性到容性连续调节,占地面积小,安全性高。赞同1| 评论(1) 盛世傻子 | 发布于2011-11-18 举报| 评论(2) 15 22 目前来说,SVG和SVC均可以称为动态...

    SVC与SVG无功补偿的不同

    SVC与SVG无功补偿的不同之处1. 名称与工作原理 SVC,作为动态无功源,通过滤波器组接入电网,能提供或吸收无功。其工作原理是通过可控硅阀组调节电抗器电流,实现电压稳定。在常规变电站中常见。相反,SVG以大功率电压型逆变器为核心,能快速调节无功功率,特别是在风电场和光伏站的应用更为广泛,通过...

    无功补偿SVG和SVC的区别46

    SVC是Static Var Compensator的缩写,应译为“静止无功补偿装置”,这是因为构成这种装置的主要元件(电容器、电抗器、晶闸管阀等)是“静止”的(相对于调相机之类的旋转设备而言),但其功能是动态无功功率补偿。SVG(又称ASVG或STATCOM)是Static Var Generator的缩写,叫做静止无功发生器。也是做无功补偿...

    SVG与SVC无功补偿原理区别?

    1、静止无功补偿装置(SVG)静止无功发生装置SVG,是无功补偿领域最新技术应用的代表。SVG并联于电网中,相当于一个可变的无功电流源,通过调节逆变器交流侧输出电压的幅值和相位,或者直接控制其交流侧电流的幅值和相位,迅速吸收或者发出所需要的无功功率,实现快速动态调节无功的目的。当采用直接电流控制时,...

    SVG和SVC的区别

    SVC是一种静止无功补偿器。静止无功补偿器是由晶闸管所控制投切电抗器和电容器组成,由于晶闸管对于控制信号反应极为迅速,而且通断次数也可以不受限制。包括:TSC、TCR等,“静止”是与同步调相机对应,一般来说将使用晶闸管进行控制的补偿装置成为“SVC"。SVG是典型的电力电子设备,由三个基本功能模块构成...

    svc和svg有什么区别

    svc和sv的区别在于工作原理不同、响应速度、低电压特性、具体如下:1、工作原理不同:SVG以大功率电压型逆变器为核心,通过调节逆变器输出电压的幅值和相位,或者直接控制交流侧电流的幅值和相位,快速吸收或发出所需的无功功率,实现快速动态调节无功功率的目的,SVC算作是动态的无功源,可以根据接入电网的...

    无功补偿装置的基本要求 无功补偿装置的选择 无功补偿装置的用途 无功补偿装置有哪些 无功补偿装置有哪几类 无功补偿装置不自动投切 10kv无功补偿装置 无功补偿装置原理 svg无功补偿装置组成
    声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
    形容水的四字词语有哪些(形容水的寓意好的词语鉴赏) 探寻世界各地美食,天下美食网带你品味不一样的人间美味 买小吃去哪个网站 美食天下网是正规的吗 一首英文歌开头重复3句,开头前调和下个路口见前调乐器好像用的一样... 天津盘山风景名胜区详细介绍 请问从天津站北广场出发的机场专线多少钱,大概多长时间到?谢谢 用什么能使头发长出来 肉末咸菜黄豆芽 怎么做肉末咸菜黄豆芽 八字看夫妻年龄差距 一个变电站可以设置2个电压等级的补偿装置吗? 电力系统补偿装置有哪些?各有什么特点? 借钱转账记录算证据吗? 暴风影音里分类选择里韩剧用的图片是哪个电视剧的,男的帅爆啦 借贷转账是什么意思 电压补偿装置工作原理 喝红酒除了费力地把瓶塞拔个粉碎和塞进酒瓶里,还有别的选择吗? 红酒抽真空的酒塞怎么用? WLAN+要不要打开 什么是公租房?申请条件有哪些? 开瓶器断在木塞里了怎么办? 充电宝充电时要不要打开 红酒开瓶器断在酒瓶塞里了怎么办? 宝宝的小拳头,要不要打开呢? 玻璃酒瓶塞怎么打开 我要买两只兔子,是两只公的好还是两只母的好,还是一只公一只母的好 玻璃酒瓶的玻璃塞太紧取不出怎么办? 外面灰层多窗户要不要打开? 我要买大量的小兔子来养。请问哪里有卖的, 洋酒瓶的胶塞怎么取出来 股东的权益 电力系统无功补偿的设备有哪些 资产评估中,股东全部权益价值和净资产区别 电压补偿什麽意思? 麻烦推荐百元内不错的二手头戴式耳机 请问谁知道电压调节器多级无功补偿装置说明???急需知道答案·谢谢 请教下有人知道自动电压调整器(AVR)和静态无功补偿装置(SVC)的用途分别是什么吗? 求推荐头戴式耳机。准备入二手的。用笔记本推 我喜欢听鼓点加钢琴hipop(例DJ OKAWARI 口头借钱有转账记录可以告上法庭吗 股东权益是什么??? 有80来块钱,想搞一个二手头戴式耳机,求推 借钱是发红包还是转账 装设电容器进行电压补偿的利弊 不想要的头戴耳机可以放在哪里卖? 借钱有转账记录可以起诉吗 我想买一个听音乐的耳机头戴式的二手的三四百左右,谁可以帮忙呀! 单相电压是220V,能不能加无功补偿装置? 中国在国际上知名的麻豆有哪些 朋友找自己借钱时,给朋友借现金好还是转账好? vue3使用tsx文件时如何注解props