Sass 中的反三角函数

发布于 2021-08-29 01:31:02 字数 13923 浏览 1788 评论 0

有可能你会认为数学在 CSS 中用不上,但实际上,在写 CSS 时运用一些数学可以帮你做一些令人惊讶的事情。数学(尤其是三角函数)可以帮助你模拟一个真实的世界。如果你想做这样的事情,你需要了解复杂的三维变换。如果你只是想在朋友面前展示一下你的才华,这将是一件非常有兴趣的事情。

这里有一个示例:https://codepen.io/anon/embed/kpCyx

这是一个旋转的三十二面体(由二十个三角形的页和十二个五角形的面组成),在CSS中可以说是一个使用三角学完成的一个高级示例。如果你为此感到头疼,你可以先看看Mason Wendell写的文本阴影的案例。Mason Wendell使用了Compass的sin()cos()函数来实现CSS的阴影重叠的事情。

我对三角函数并不太了解。但有时候三角函数对我来说并不够用,特别是在CSS中写2D和3D的案例时,我发现需要使用正弦,余弦和正切来计算一些值。我需要asin()acos()atan()函数。不幸的是,Compass并不提供这些函数功能,所以我有下面两个选择:

  • 通过计算器手动计算
  • 我自己写这些函数功能

那我肯定会选择第二条了。

幸运的我,偶然发现了一篇使用Sass写的正弦和余弦函数功能的文章。我认为使用同样的方法,也能实现我需要的功能。

声明:数学运算让人头疼,如果您仅仅是想看看效果,你可以跳过下面的内容,直接点击这里查看效果。

三角 101(Trigonometry 101)

先别深入,我们先回过头来回忆一下高中数学知识。

Sass中的反三角函数

这个图看起来有点熟悉。如果不了解,可以看看 YouTube上MathBFF 的视频:基本的三角函数:Sin,Cos,Tan

根据上面的直角三角形,我们一起来回顾几个公式:

α+β=90°

用弧度来表示是这样的:

Sass中的反三角函数

大家可能还记得,毕达哥拉斯定理(Pythagorean theorem)告诉我们:

Sass中的反三角函数

还有一些基本的三角函数是这样定义的:

sin(α) = a/c
cos(α) = b/c
tan(α) = a/b
sin(β) = b/c
cos(β) = a/c
tan(β) = b/a

知道这些,我们就可以推导出其他的一些公式:

Sass中的反三角函数

头晕了?保持清醒的头脑继续往下。

反正弦函数

什么是反正弦呢?好吧,如果:

sin(α)=z

那么反正弦是逆着的:

arcsin(z)=α

换句话说,给了一个角度的正弦,通过反正弦可以告诉你这个角度值。反余弦和反正切类似于在一个余弦或正切基础给了一个角度。

我们将用Sass来制作一个反正弦asin()的函数。我们将用级数来展开这一点。如果你不是一个数学奇才,泰勒级数展开是相当复杂的。我会尽我所能向大家介绍。对于反正弦,他看起来像这样:

Sass中的反三角函数

看到这个公式就吓坏了。让我们来分析这个结构:z是我们想要得到的α角的正弦。整个和是α的弧度值。当总和在[−π/2,π/2]之间时,z应该在[-1,1]之间。

每一个标签,包括第一个,你都可以写成(1)⋅z,它是被编造出来的两部分;第一部分是毕达哥拉斯定理内的部分,第二部分是毕达哥拉斯定理以外的部分。

对于每个i标签是第一位的,第一部分是(2i−1)/(2i),第二部分中的分子部分是z2i+1幂次方,而分母是2i+1

这可能会得到一个无限大的数,但是一旦得到期限值,对于条款中的值可能会变得非常小,小得我们甚至可以忽略他不计,而且还是安全的,不会影响其他任何东西。

但我们应该从哪个地方开始停下来呢?比方说,在一个范围的十分之一。以弧度1度的值来计算,1度 = π/180 ≈ 3.14 / 180 ≈ .0175。所以这里的十分之一就是“.00175” 。所以当值小于“.00175”时,他就会停下来,就像我们起床的闹钟,到这个点就会停下来。

我们一起来看两示例。

z=0时,这个简单,因为所有条款都是0,所以根据级数展开,无单位的弧度值是0和度值是0⋅180°/π=0°。

z=1时,第一项是1,第二项是1/6=.167,第三项是3/40=0.075,第四项是.045,第五项是.030,第六项是.022,第七项是.017,第八项是.014。我们注意到我们有一个问题,每一项明显有下降,但这种放慢真的非常缓慢,离我们停下来的临界点.00175还好远。

但离我们需要的真正值有多远呢?我们把所有项做了一个总结,如下:

1+.167+.075+.045+.030+.022+.017+.014=1.496

这个弧度值转换成度值是85°。离正确的90°并不是太远,但现在变得越来越难接近准确值。这将会导致更多的循环和较慢。它的一个问题是,虽然在特定的情况下影响的程序较小,我们也应该保证每种情况下的绝对值在[0,π/2]上半部分。

我们要怎样解决这个问题呢?首先要检查产生的角度绝对值是否大于π/4,如果是,我们采用这种方法π/2−|α|计算他的补差。由于正弦函数在[0,π/2]之间是一个递增函数,所以我们要检查z的绝对值是否大于sin(π/4)

但是我们怎么知道正弦函数中的补差值中α的绝对值呢?嗯,他等于余弦的α的绝对值:sin(π/2−|α|)=cos(|α|)。根据相关公式,我们可以得知:

Sass中的反三角函数

编写 asin() 函数

哎,好多数学公式呀!让我们来看看一些代码。

首先给所有项的总和设置一个默认的阈值:

$default-threshold: pi() / 180 / 10;

pi()函数是Compass内置的一个数学函数,其返回的值就是一个π值。

然后我们开始写我们的函数:

@function asin($z) {
  $sum: 0;

//abs(),Sass内置函数,返回一个数的绝对值
//sin(),Compass内置函数,返回一个角度的正弦值
//sqrt(),Compass内置函数,返回一个数的平方根
//pow(),Compass内置函数,返回一个数的幂值,如pow($z,2)返回的是$z的二次方
  @if abs($z) > sin(pi() / 4) {
    $z: sqrt(1 - pow($z, 2));
  }

  @return $sum;
}

我们设计总和$sum的初始值为0,如果我们角度的绝对值大于pi()/4时,我们要确保计算他的补差。但在那之后,我们怎么知道我们已计算了补差呢?是切换到初始角度吗?

为了更好的跟踪,我引入了一个布尔变量$complement,并且定义其初始值为false,但在@if区块内通过true来切换返回的值。默认之下返回的是$sum值,如果检测到$complement变量值为true时,则返回pi()/2 - $sum

@function asin($z) {
  $sum: 0;
  $complement: false;

  @if abs($z) > sin(pi() / 4) {
    $complement: true;
    $z: sqrt(1 - pow($z, 2));
  }
//Miscellaneous函数,当$complement为true,返回pi()/2 - $sum,反之返回$sum    
  @return if($complement, pi() / 2 - $sum, $sum);
}

但这只适合正值,所以我们要引入变量$sign,它的值可以是1或者-1。我们还让$z值等于他的绝对值,并且让$sign值等于$z除以他自身的绝对值。代码变成:

@function asin($z) {
  $sum: 0;
  $complement: false;
  $sign: $z / abs($z);
  $z: abs($z);

  @if $z > sin(pi() / 4) {
    $complement: true;
    $z: sqrt(1 - pow($z, 2));
  }

  @return $sign * (if($complement, pi() / 2 - $sum, $sum));
}

现在,让实际项目加起来得到总和,一旦这个这个总和的值比我们传递给函数的阈值小,我们设置他停止下来。首先设置第一个项$term,其值等于$z,并且放在循环语句@while的前面:

@function asin($z, $threshold: $default-threshold) {
  $sum: 0;
  $complement: false;
  $sign: $z / abs($z);
  $z: abs($z);

  @if $z > sin(pi() / 4) {
    $complement: true;
    $z: sqrt(1 - pow($z, 2));
  }

  $term: $z;

  @while $term > $threshold {
    $sum: $sum + $term;
  }

  @return $sign * (if($complement, pi() / 2 - $sum, $sum));
}

在这一点上,除非我们的$term值小于$threshold值,不然@while会一直循环计算,因为我们在里面没有改变$term值。因此,每一次迭代计算机都会计算一次。为了做到这一点,我们在循环前初始化两个变量。一个是$i,即当前项索引;另一个是$k,毕达哥拉斯定理里面的那部分。在此之后,我们在循环之内,不断递增$i的值和重新计算$k$term的值:

@function asin($z, $threshold: $default-threshold) {
  $sum: 0;
  $complement: false;
  $sign: $z / abs($z);
  $z: abs($z);

  @if $z > sin(pi() / 4) {
    $complement: true;
    $z: sqrt(1 - pow($z, 2));
  }

  $term: $z;
  $i: 0;
  $k: 1;

  @while $term > $threshold {
    $sum: $sum + $term;

    $i: $i + 1;
    $k: $k * (2 * $i - 1) / (2 * $i);
    $j: 2 * $i + 1;

    $term: $k * pow($z, $j) / $j;
  }

  @return $sign * (if($complement, pi() / 2 - $sum, $sum));
}

到此就完成了,在Sass中我们有了自己的asin()函数功能。

我们是否有什么方法可以检测出abs($z) <= 1,如果他返回的是一个false,抛出一个错误。因为在这种情况之下,$term不会在$threshold值内返回false,我们的循环就是一个无限循环。

编写 acos() 函数

现在我们有一个函数来计算反正弦,在此基础上,我们可以很容易写出反余弦函数acos()。事实上,在此示例中,角度α的值都是在[0,π]之间,不难得出cos(α)=sin(π / 2−α)。如果我们知道cos(α)=z,那么arcsin(z)=π/2−α,如此一来,给我们提供α=π/2−arcsin(z)

@function acos($z, $threshold: $default-threshold) {
  @return pi() / 2 - asin($z, $threshold);
}

编写 atan() 函数

对于 atan() 函数,我们从这样的事实开始 tan(α)=sin(α)/cos(α)。我们也知道:

Sass中的反三角函数

也知道:

Sass中的反三角函数

根据这样的关系,我们可以得出正弦与正切之间的关系:

Sass中的反三角函数

我们也知道 tan(α)=z 所以:

Sass中的反三角函数

将上面公式简化一下:

Sass中的反三角函数

因此得出我们的反正切 atan() 函数:

@function atan($z, $threshold: $default-threshold) {
  @return asin($z / sqrt(1 + pow($z, 2)), $threshold);
}

将这些函数功能更好的用于 CSS 中

请记住,这些函数返回的值是一个无单位的弧度值。在我们CSS中是无法直接使用这些值,因为在CSS中至少需要有一个1rad或者带有单位的换算值。但是,如果我们在调用函数时,能指定单位呢?如:

transform: rotate(asin(.5, 'deg'));

为了做到这一点,我们需要一个能转换角度的函数,它能接受一个无单位的弧度值,并且它能将其转换成我们指定的单位,如:

$in-degrees: convert-angle(pi() / 4, 'deg');
$in-turns: convert-angle(pi() / 2, turn); // 让单位带不带引号都能工作

编写角度转换函数

首先我们创建无单位的弧度与 CSS 单位之间的换算系数表。在 Sass 中,我们可以使用 Map 功能:

$factors: (
  rad: 1rad,
  deg: 180deg / pi(),
  turn: .5turn / pi(),
  grad: 200grad / pi()
);

这样,我们只需要使用无单位的弧度值乘以系数值,也就是:

@function convert-angle($value, $unit-name) {
  $factors: (
    rad: 1rad,
    deg: 180deg / pi(),
    turn: .5turn / pi(),
    grad: 200grad / pi()
  );

  @return $value * map-get($factors, $unit-name);
}

这个功能是失败的,如果 $unit-name 不是 $factors 列表中对应的关键词,或者 $value 已经有单位了,在 CSS 中无效的。因此我们需要完善此函数功能:

@function convert-angle($value, $unit-name) {
  $factors: (
    rad: 1rad,
    deg: 180deg / pi(),
    grad: 200grad / pi(),
    turn: .5turn / pi()
  );

  @if not unitless($value) {
    @warn '`#{$value}` should be unitless';
    @return false;
  }

  @if not map-has-key($factors, $unit-name) {
    @warn 'unit `#{$unit-name}` is not a valid unit - please make sure it is either `deg`, `rad`, `grad` or `turn`';
    @return false;
  }

  @return $value*map-get($factors, $unit-name);
}

增强反三角函数功能

现在我们只需要完善我们的反三角函数具有单位换算功能:

@function asin($z, $unit-name: deg, $threshold: $default-threshold) {
  $sum: 0;
  $complement: false;
  $sign: $z / abs($z);
  $z: abs($z);

  @if $z > sin(pi() / 4) {
    $complement: true;
    $z: sqrt(1 - pow($z, 2));
  }

  $term: $z;
  $i: 0;
  $k: 1;

  @while $term > $threshold {
    $sum: $sum + $term;

    $i: $i + 1;
    $k: $k * (2 * $i - 1) / (2 * $i);
    $j: 2 * $i + 1;

    $term: $k * pow($z, $j) / $j;
  }

  @return convert-angle($sign*(if($complement, pi()/2 - $sum, $sum)), $unit-name);
}

@function acos($z, $unit-name: deg, $threshold: $default-threshold) {
  @return convert-angle(pi()/2, $unit-name) - asin($z, $unit-name, $threshold);
}

@function atan($z, $unit-name: deg, $threshold: $default-threshold) {
  @return asin($z/sqrt(1 + pow($z, 2)), $unit-name, $threshold);
}

我将度 deg 设置为默认单位,因为这可以是大多数人比较了解和会使用的,并且将其放置在 $threshold 前面,因为它仍然有可能有人可能会改变单位。

总结

如果你能跟着看到这里,你是一个真正的爱学习的人。最后的功能如下:

$default-threshold: pi() / 180 / 20;

@function convert-angle($value, $unit-name) {
  $factors: (
    rad: 1rad,
    deg: 180deg / pi(),
    grad: 200grad / pi(),
    turn: .5turn / pi()
  );

  @if not unitless($value) {
    @warn '`#{$value}` should be unitless';
    @return false;
  }

  @if not map-has-key($factors, $unit-name) {
    @warn 'unit `#{$unit-name}` is not a valid unit - please make sure it is either `deg`, `rad`, `grad` or `turn`';
    @return false;
  }

  @return $value*map-get($factors, $unit-name);
}

@function asin($z, $unit-name: deg, $threshold: $default-threshold) {
  $sum: 0;
  $complement: false;
  $sign: if($z != 0, $z / abs($z), 1);
  $z: abs($z);

  @if $z > 1 {
    @warn 'illegal `#{$z}` value for function';
    @return false;
  }

  @if $z > sin(pi() / 4) {
    $complement: true;
    $z: sqrt(1 - pow($z, 2));
  }

  $term: $z;
  $i: 0;
  $k: 1;

  @while $term > $threshold {
    $sum: $sum + $term;

    $i: $i + 1;
    $k: $k * (2 * $i - 1) / (2*$i);
    $j: 2 * $i + 1;

    $term: $k * pow($z, $j) / $j;
  }

  @return convert-angle($sign * (if($complement, pi() / 2 - $sum, $sum)), $unit-name);
}

@function acos($z, $unit-name: deg, $threshold: $default-threshold) {
  @return convert-angle(pi() / 2, $unit-name) - asin($z, $unit-name, $threshold);
}

@function atan($z, $unit-name: deg, $threshold: $default-threshold) {
  @return asin($z/sqrt(1 + pow($z, 2)), $unit-name, $threshold);
}

在结束这篇文章时,我在 Codepen 放了两个案例,以激发你的学习激情:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

泛泛之交

暂无简介

0 文章
0 评论
21557 人气
更多

推荐作者

遂心如意

文章 0 评论 0

5513090242

文章 0 评论 0

巷雨优美回忆

文章 0 评论 0

junpengz2000

文章 0 评论 0

13郎

文章 0 评论 0

qq_xU4RDg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文