最近一直在使用 css-doodle 實現一些 CSS 效果。css
css-doodle 是一個基於 Web-Component 的庫。容許咱們快速的建立基於 CSS Grid 佈局的頁面,以實現各類 CSS 效果(或許能夠稱之爲 CSS 藝術)。後續幾篇文章可能都會與之有關。html
固然,本文的主角並非 css-doodle。git
CSS自己一直在快速發展更新,標準也與時俱進,各類新特性層出不窮,爲了可以使用 CSS 來創造各類佈局實現各類形狀,除了合理運用及搭配各個屬性以外,去理解壓榨每一個屬性的每一個細節點也是很是重要的。github
本文將介紹一種在 CSS 中藉助三角函數繪製曲線圖形的小技巧。web
首先,回顧一下 box-shadow
這個屬性。基本屬性用法就是給元素創造一層陰影。sass
關於陰影的許多細節,能夠先看看這篇文章:你所不知道的 CSS 陰影技巧與細節less
再簡單提一下,本文會用到的關於陰影的第一個技巧:函數
當 box-shadow 的第3、第四個參數模糊半徑和擴張半徑都爲 0 的時候,咱們能夠獲得一個和元素大小同樣的陰影:佈局
div {
width: 80px;
height: 80px;
border: 1px solid #333;
box-sizing: border-box;
box-shadow: 80px 80px 0 0 #000;
}
複製代碼
獲得以下結果:測試
第二個技巧則是,box-shadow
是容許多重陰影的,而且他們的座標是能夠徹底掌控的。
是的,咱們能夠像下面這樣給一個元素定義多重陰影,而且利用陰影的第1、第二個參數控制它相對於元素的座標:
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 實現起來很困難的曲線。
帶着疑問,先繼續向下,假設咱們要實現這樣一條曲線:
使用 CSS 的話,有什麼辦法呢?
可能的一些辦法是 clip-path
,或者一些奇技淫巧,使用 text-decoration
裏的波浪下劃線 wavy
,或者是使用漸變疊加。
固然,還有一種辦法是本文將提到的使用 box-shadow
及 三角函數。
咳咳,簡單回顧下三角函數裏面的 sin、cos 曲線圖像變換,尚未所有還給老師。
若是咱們有一個 1x1 的 div,它的多重陰影,可以按照像正弦/餘弦函數的圖像同樣進行排布,連起來不就是一條曲線嗎?
想法不錯,可是 CSS 自己並無提供三角函數。這裏,咱們須要藉助 Sass 來在 CSS 中實現簡單的三角函數。
還好,已經有前人幫忙把這個工做作完了:
簡單而言,就是藉助三角函數的泰勒展開式,使用 Sass 函數模擬實現三角函數的 sin()、cos()、tan():
因爲展開式是無限長的,使用 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;
}
複製代碼
實際獲得的圖像以下:
看看上面 Sass 實現的這個方法 @function shadowSet($vx, $vy)
,其中 $vx
,$vy
用於控制圖像的振幅及鬆散程度,咱們再添加一個控制初始方向的 $direction
,控制陰影層數的 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);
}
複製代碼
再進一步,咱們能夠藉助 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 中還有不少其餘顏色函數:
更多 Sass 顏色函數,能夠看看這篇文章:Sass基礎——顏色函數
OK,看看此次的效果:
OK,前面全部的鋪墊都是爲了在實際的一些創意想法中去使用它。
在 css-doodle 中,因爲是利用 Web Component 特性。在須要三角函數的時候,能夠直接使用 JavaScript 提供的 Math 函數,會更加的方便。
Web Components 是一套不一樣的 Web 技術,容許您建立可重用的定製元素(它們的功能封裝在您的代碼以外)而且在您的web應用中使用它們。
袁川老師,也就是 css-doodle 庫的做者,在他的 Codepen 首頁背景板中,使用的就是使用上述技巧實現的一副純 CSS 畫做:
我也嘗試使用這個技巧,作了一副:
Codepen Demo -- CSS-Doodle fish 🐟 & seaweed🍀
有幾點,有必要提一下的。
一、爲何必定要使用 box-shadow
,直接堆疊 div 不行麼?
能夠,使用多重 box-shadow
只是由於這樣能夠更省標籤,一個 div 搞定。更甚,願意折騰,使用多重漸變也是能夠的。
二、上述兩個 Demo 都是純 CSS 畫出來的嗎?
是的。雖然藉助了 css-doodle 庫,可是本質都是 CSS 代碼,只是這個庫封裝好了不少拿來即用的函數。css-doodle
三、有什麼用?
額,有沒有用是一個哲學問題。至少我以爲仍是挺有意思的。
好了,本文到此結束,但願對你有幫助 :)
更多精彩 CSS 技術文章彙總在個人 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。
若是還有什麼疑問或者建議,能夠多多交流,原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。