在 CSS 中使用三角函数绘制曲线图形及展示动画

简介: 在 CSS 中使用三角函数绘制曲线图形及展示动画

最近一直在使用 css-doodle 实现一些 CSS 效果。

css-doodle 是一个基于 Web-Component 的库。允许我们快速的创建基于 CSS Grid 布局的页面,以实现各种 CSS 效果(或许可以称之为 CSS 艺术)。后续几篇文章可能都会与之有关。

当然,本文的主角并不是 css-doodle。

CSS本身一直在快速发展更新,标准也与时俱进,各种新特性层出不穷,为了能够使用 CSS 来创造各种布局实现各种形状,除了合理运用及搭配各个属性之外,去理解压榨每个属性的每个细节点也是非常重要的。

本文将介绍一种在 CSS 中借助三角函数绘制曲线图形的小技巧。

 

理解 box-shadow



首先,回顾一下 box-shadow 这个属性。基本属性用法就是给元素创造一层阴影。

关于阴影的许多细节,可以先看看这篇文章:你所不知道的 CSS 阴影技巧与细节

再简单提一下,本文会用到的关于阴影的第一个技巧:

 

使用阴影复制图像/投影图像


当 box-shadow 的第三、第四个参数模糊半径和扩张半径都为 0 的时候,我们可以得到一个和元素大小一样的阴影:

div {
    width: 80px;
    height: 80px;
    border: 1px solid #333;
    box-sizing: border-box;
    box-shadow: 80px 80px 0 0 #000;
}

得到如下结果:

47730691-a28e2180-dc9d-11e8-863c-00a1340f1cb8.png


阴影可以是多重的


第二个技巧则是,box-shadow 是允许多重阴影的,并且他们的坐标是可以完全掌控的。

是的,我们可以像下面这样给一个元素定义多重阴影,并且利用阴影的第一、第二个参数控制它相对于元素的坐标:

div {
    width: 80px;
    height: 80px;
    border: 1px solid #333;
    box-sizing: border-box;
    box-shadow:
        80px 80px 0 0 #000,
        70px 70px 0 0 #000,
        ...
        60px 60px 0 0 #000;
}

在阴影坐标中运用三角函数



继续。接下来,我们尝试在阴影的坐标中引入三角函数。

为啥是三角函数,不是圆的标准方程或者椭圆的标准方程或者其他图形函数呢?当然也是可以的,只是这里借助三角函数的 cos 或 sin 可以实现直接使用 CSS 实现起来很困难的曲线。

带着疑问,先继续向下,假设我们要实现这样一条曲线:


69336062-fd276080-0c98-11ea-912a-bb4a46a49dd7.png

使用 CSS 的话,有什么办法呢?

可能的一些办法是 clip-path,或者一些奇技淫巧,使用 text-decoration 里的波浪下划线 wavy,或者是使用渐变叠加。

当然,还有一种办法是本文将提到的使用 box-shadow 及 三角函数。

 

三角函数


咳咳,简单回顾下三角函数里面的 sin、cos 曲线图像变换,还没有全部还给老师。

608782-20191122103747598-2127388406.png

如果我们有一个 1x1 的 div,它的多重阴影,能够按照像正弦/余弦函数的图像一样进行排布,连起来不就是一条曲线吗?

 

如何在 CSS 中使用三角函数 sin/cos


想法不错,但是 CSS 本身并没有提供三角函数。这里,我们需要借助 Sass 来在 CSS 中实现简单的三角函数。

还好,已经有前人帮忙把这个工作做完了:

简单而言,就是借助三角函数的泰勒展开式,使用 Sass 函数模拟实现三角函数的 sin()、cos()、tan()


608782-20191122103733202-1008810067.png



由于展开式是无限长的,使用 Sass 函数模拟时,不可能得到一个非常精确的值,但是在日常作图下已经完全够用了,以下是使用 Sass 函数模拟实现三角函数的 sin()、cos()、tan():


@function fact($number) {
    $value: 1;
    @if $number>0 {
        @for $i from 1 through $number {
            $value: $value * $i;
        }
    }
    @return $value;
}
@function pow($number, $exp) {
    $value: 1;
    @if $exp>0 {
        @for $i from 1 through $exp {
            $value: $value * $number;
        }
    }
    @else if $exp < 0 {
        @for $i from 1 through -$exp {
            $value: $value / $number;
        }
    }
    @return $value;
}
@function rad($angle) {
    $unit: unit($angle);
    $unitless: $angle / ($angle * 0 + 1);
    @if $unit==deg {
        $unitless: $unitless / 180 * pi();
    }
    @return $unitless;
}
@function pi() {
    @return 3.14159265359;
}
@function sin($angle) {
    $sin: 0;
    $angle: rad($angle);
    // Iterate a bunch of times.
    @for $i from 0 through 20 {
        $sin: $sin + pow(-1, $i) * pow($angle, (2 * $i + 1)) / fact(2 * $i + 1);
    }
    @return $sin;
}
@function cos($angle) {
    $cos: 0;
    $angle: rad($angle);
    // Iterate a bunch of times.
    @for $i from 0 through 20 {
        $cos: $cos + pow(-1, $i) * pow($angle, 2 * $i) / fact(2 * $i);
    }
    @return $cos;
}
@function tan($angle) {
    @return sin($angle) / cos($angle);
}


由于上面最终计算 sin、cos 泰勒展开的时候,只使用了 20 层循环,所以当传入的值太大的时候,则会产生较大误差。经测试,传入数值在 [-20, 20] 以内,精度还是非常高的。


而以 sin 函数为例,x 取值在 [-π, π] 之间,已经能覆盖所有 sin(x) 的取值范围,所以 [-20, 20] 这个范围是完全够用的,我们只需要尽量让传入的 x 值落在这个区域范围内即不会产生太大误差。


好,铺垫了那么多,接下来使用上述的 sin 函数试一下,假设我们有这样一个结构:


<div></div>
div {
    width: 1px;
    height: 1px;
    background: #000;
    border-radius: 50%;
}

我们再借助 Sass 实现一个 50 层的循环,当然其中阴影的 x 坐标使用了 sin 函数:

@function shadowSet($vx, $vy) {
    $shadow : 0 0 0 0 #000;
    @for $i from 0 through 50 {
        $x: sin($i / 8) * $vx;
        $y: $i * $vy;
        $shadow: $shadow, #{$x} #{$y} 0 0 rgba(0, 0, 0, 1);
    }
    @return $shadow;
}
div {
    width: 1px;
    height: 1px;
    background: #000;
    border-radius: 50%;
    box-shadow: shadowSet(4px, 1px);
}

上面 sin($i / 8),这里除以 8 是为了让整个sin(x) 传入的作用域的取值范围为 [0, 6.25],当而 sin(x) 的作用域为 [0,2π] 时刚好可以画一条完整的单次曲线。这个 8 是可以根据循环的次数不同而进行调整的。


实际,我们得到的 box-shadow 如下:


{
    box-shadow:
    0 0 0 0 black, 0.4986989335px 1px 0 0 black, 0.989615837px 2px 0 0 black,
    1.4650901163px 3px 0 0 black, 1.9177021544px 4px 0 0 black, 2.3403890918px 5px 0 0 black,
    2.7265550401px 6px 0 0 black, 3.0701740089px 7px 0 0 black, 3.3658839392px 8px 0 0 black,
    3.6090703764px 9px 0 0 black, 3.7959384774px 10px 0 0 black, 3.9235722281px 11px 0 0 black,
    3.9899799464px 12px 0 0 black, 3.9941253622px 13px 0 0 black, 3.9359437875px 14px 0 0 black,
    3.8163431264px 15px 0 0 black, 3.6371897073px 16px 0 0 black, 3.4012791593px 17px 0 0 black,
    3.1122927876px 18px 0 0 black, 2.7747401278px 19px 0 0 black, 2.3938885764px 20px 0 0 black,
    1.9756811944px 21px 0 0 black, 1.5266439682px 22px 0 0 black, 1.0537839735px 23px 0 0 black,
    0.5644800322px 24px 0 0 black, 0.0663675689px 25px 0 0 black, -0.4327805381px 26px 0 0 black,
    -0.9251752496px 27px 0 0 black, -1.4031329108px 28px 0 0 black, -1.8591951521px 29px 0 0 black,
    -2.286245275px 30px 0 0 black, -2.677619305px 31px 0 0 black, -3.0272099812px 32px 0 0 black,
    -3.3295620582px 33px 0 0 black, -3.5799574329px 34px 0 0 black, -3.7744887692px 35px 0 0 black,
    -3.9101204707px 36px 0 0 black, -3.9847360499px 37px 0 0 black, -3.9971711559px 38px 0 0 black,
    -3.9472317429px 39px 0 0 black, -3.8356970987px 40px 0 0 black, -3.6643076841px 41px 0 0 black,
    -3.4357379737px 42px 0 0 black, -3.1535547213px 43px 0 0 black, -2.8221613023px 44px 0 0 black,
    -2.446729px 45px 0 0 black, -2.03311631px 46px 0 0 black, -1.58777752px 47px 0 0 black,
    -1.1176619928px 48px 0 0 black, -0.630105724px 49px 0 0 black, -0.1327168662px 50px 0 0 black;
}

实际得到的图像如下:


608782-20191122103618456-1123805939.png

CodePen Demo -- sass2sin Line

 

控制颜色及初始方向


看看上面 Sass 实现的这个方法 @function shadowSet($vx, $vy) ,其中 $vx,$vy 用于控制图像的振幅及松散程度,我们再添加一个控制初始方向的 $direction,控制阴影层数的 count,控制颜色的count,控制颜色的color:


@function shadowSet($vx, $vy, $direction, $count, $color) {
    $shadow : 0 0 0 0 $color;
    @for $i from 0 through $count {
        $x: sin($i / 8) * $vx * $direction;
        $y: $i * $vy;
        $shadow: $shadow, #{$x} #{$y} 0 0 $color;
    }
    @return $shadow;
}
.line {
    width: 1px;
    height: 1px;
    margin: 10vh auto;
    background: #000;
    border-radius: 50%;
    box-shadow: shadowSet(4px, 1px, 1, 50, #000);
}
.reverseline {
    width: 1px;
    height: 1px;
    margin: 10vh auto;
    background: #000;
    border-radius: 50%;
    box-shadow: shadowSet(8px, 2px, -1, 100, red);
}

608782-20191122103548860-1605353569.png


控制颜色


再进一步,我们可以借助 Sass 的各种颜色函数,实现颜色的变化:

@function shadowSetColor($vx, $vy, $direction, $count, $color) {
    $shadow : 0 0 0 0 $color;
    @for $i from 0 through $count {
        $color: lighten($color, .5);
        $x: sin($i / 8) * $vx * $direction;
        $y: $i * $vy;
        $shadow: $shadow, #{$x} #{$y} 0 0 $color;
    }
    @return $shadow;
}
.colorline {
    width: 5px;
    height: 5px;
    margin: 10vh auto;
    background: green;
    border-radius: 50%;
    box-shadow: shadowSetColor(8px, 2px, -1, 100, green);
}

上面,借助了 lighten 这个函数,通过改变颜色的亮度值,让颜色变亮,创建一个新的颜色。


当然,Sass 中还有很多其他颜色函数:

  • adjust-hue(color,color,degrees):通过改变一个颜色的色相值,创建一个新的颜色;
  • lighten(color,color,amount):通过改变颜色的亮度值,让颜色变亮,创建一个新的颜色;
  • darken(color,color,amount):通过改变颜色的亮度值,让颜色变暗,创建一个新的颜色;
  • saturate(color,color,amount):通过改变颜色的饱和度值,让颜色更饱和,从而创建一个新的颜色
  • desaturate(color,color,amount):通过改变颜色的饱和度值,让颜色更少的饱和,从而创建出一个新的颜色;


更多 Sass 颜色函数,可以看看这篇文章:Sass基础——颜色函数

OK,看看这次的效果:


608782-20191122103531143-243355832.png

image.png


在 css-doodle 中使用



OK,前面所有的铺垫都是为了在实际的一些创意想法中去使用它。

在 css-doodle 中,由于是利用 Web Component 特性。在需要三角函数的时候,可以直接使用 JavaScript 提供的 Math 函数,会更加的方便。


Web Components 是一套不同的 Web 技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们。


袁川老师,也就是 css-doodle 库的作者,在他的 Codepen 首页背景板中,使用的就是使用上述技巧实现的一副纯 CSS 画作:

608782-20191122103519810-322556208.jpg


image.png

我也尝试使用这个技巧,做了一副:

608782-20191122103445632-1071366561.gif


image.png


最后



有几点,有必要提一下的。

1、为什么一定要使用 box-shadow,直接堆叠 div 不行么?

可以,使用多重 box-shadow 只是因为这样可以更省标签,一个 div 搞定。更甚,愿意折腾,使用多重渐变也是可以的。

2、上述两个 Demo 都是纯 CSS 画出来的吗?

是的。虽然借助了 css-doodle 库,但是本质都是 CSS 代码,只是这个库封装好了很多拿来即用的函数。css-doodle

3、有什么用?

额,有没有用是一个哲学问题。至少我觉得还是挺有意思的。


好了,本文到此结束,希望对你有帮助 :)

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

目录
相关文章
|
2月前
|
机器学习/深度学习 前端开发 JavaScript
|
1月前
纯css3实现的百分比渐变进度条加载动画源码
纯css3实现的百分比渐变进度条加载动画特效源码
54 31
|
2月前
|
前端开发 搜索推荐 UED
实现 CSS 动画效果的兼容性
【10月更文挑战第16天】实现 CSS 动画效果的兼容性需要对不同浏览器的特性有深入的了解,并采取适当的策略和方法。通过不断的实践和优化,你可以在各种浏览器上创造出流畅、美观且兼容的动画效果,为用户带来更好的体验。在实际开发中,要密切关注浏览器的发展动态,及时掌握最新的兼容性技巧和解决方案,以确保你的动画设计能够在广泛的用户群体中得到良好的呈现。
115 58
|
15天前
|
Web App开发 移动开发 JavaScript
纯CSS3+SVG实现的节日庆祝五彩纸屑动画效果源码
这是一款基于纯CSS3+SVG实现的节日庆祝五彩纸屑动画效果源码。画面中左下角是一个圆锥形礼炮卡通效果,呈现出节日庆祝时礼花爆破、五彩纸屑纷飞的动画特效。整体动画效果采用纯css3+svg实现,没有引入任何外部图形或js脚本元素。建议使用支持HTML5与css3效果较好的火狐(Firefox)或谷歌(Chrome)等浏览器预览本源码。
32 6
|
25天前
|
前端开发 JavaScript UED
CSS滚动效果和视差滚动的原理、应用及其对用户体验的影响。从平滑滚动到元素跟随,再到滚动触发动画
本文探讨了CSS滚动效果和视差滚动的原理、应用及其对用户体验的影响。从平滑滚动到元素跟随,再到滚动触发动画,这些效果增强了页面的吸引力和互动性。视差滚动通过不同层次元素的差异化移动,增加了页面的深度感和沉浸感。文章还讨论了实现方法、性能优化及案例分析,旨在为设计师和开发者提供实用指导。
51 7
|
1月前
CSS3制作的聚光灯下倒影文字选装动画特效源码
CSS3聚光灯下倒影文字特效是一段基于CSS3实现的聚光灯下带倒影的文字旋转动画效果代码,具有真实的视觉感,同时文字还会在旋转过程中显示出灯光的反射效果,很有意思,欢迎对此段代码感兴趣的朋友前来下载使用。
30 6
|
1月前
|
移动开发 前端开发 JavaScript
除了 CSS3,还有哪些技术可以实现动画效果?
除了 CSS3,还有哪些技术可以实现动画效果?
54 5
|
1月前
纯css3加载loading发光变色动画代码
纯css3加载loading发光变色动画特效代码是一款基于css3 keyframes属性实现的发光变色圆点串联旋转loading加载动画
23 2
|
1月前
|
Web App开发 前端开发 iOS开发
CSS加载动画大全 126种
CSS加载动画大全是一个css Loaders加载动画特效汇总,一共包含126种加载动画效果,不同样式不同图案,简单实用,一览包含所有,会让你在等待的过程中,体验视觉盛宴,给用户不一般的加载体验,欢迎下载试试!代码适用浏览器:搜狗、360、FireFox(建议)、Chrome、Safari、Opera、傲游、世界之窗,是一款不错的的特效插件,希望大家喜欢!
27 2
|
1月前
|
JavaScript 前端开发
CSS3 动画和 JavaScript 动画的性能比较
具体的性能表现还会受到许多因素的影响,如动画的复杂程度、浏览器的性能、设备的硬件条件等。在实际应用中,需要根据具体情况选择合适的动画技术。