前言
生成藝術一直是我很想學習的主題,雖然知道大部分的人會使用 processing, paper.js, 或是 zimjs 來製作 Generative art, 但在對於自己的藝術天份很有自知之明的情況下,一直都沒有去嘗試製作,再加上這些工具的學習也是有ㄧ定門檻。
然而,最近在 Youtube 上看到 css-doodle 的作者 - 袁川於 CSSConf CN 2019 的演講,才了解到要"開始"似乎沒有這麼難,只要掌握一些基本觀念與技巧即可,而且他開發的 web component css-doodle 無論是語法或是使用都算易懂好上手。
今天藉由這篇文章分享該演講中我很喜歡的一些重點,並試著介紹與運用 CSS/JS 和 css-doodle 製作一些簡單的生成藝術作品。
先給大家看第一個小 demo,這是參照袁川在演講中提及的一個範例實作,利用純 CSS 與 JS 所製作的簡單生成藝術:
簡單的事重複做就不簡單
在生成藝術中,Loop
佔了很主要的角色,需要透過迴圈的方式去自動產生圖案。而圖案其實不需要複雜,在第一個範例中的元素就只有直線。
簡單的直線,透過 transform: rotate()
,就能夠有不一樣的變化,而透過組合,將多個擁有不同狀態的直線串接在一起,再加上時間因子作為變數去改變狀態,就可以是一個簡單的生成藝術。
程式碼也很簡短:
先設定一個 5x5 的表格:
div.grid
div
div
// 略 ... 共 25 個 div
現在利用 CSS grid 可以很輕易控制表格的呈現,接著我們可以將之後預期要來拿隨機變化的屬性以 css variable 的方式設定:
:root {
--theme: #FF9800;
--deg: 45deg;
--gride-size: 50px;
}
.grid {
width: calc(var(--gride-size) * 5);
margin: 0 auto;
display: grid;
border: 5px solid var(--theme);
box-shadow: 0 0 18px 1px var(--theme) inset;
grid-template-columns: repeat(5, 1fr);
grid-auto-rows: var(--gride-size);
overflow: hidden;
}
.grid div {
border: 1px solid var(--theme);
background-color: var(--theme);
width: 1px;
transition: all 1s ease-in;
transform: skew(var(--deg));
transform-origin: top;
}
JS 的部分就只需要在固定的 time interval 中間賦以隨機產生的數值到 CSS variable 中:
const getRandomBoolean = () => Math.floor(Math.random() * Math.floor(2));
const randomColor = () => {
const color = Math.floor(Math.random()*16777215).toString(16);
return color.length !== 6 ? `f${color}` : color;
}
let sign = '+';
let theme = randomColor();
const root = document.documentElement;
const divs = document.querySelectorAll('.grid > div');
setInterval(() => {
theme = randomColor();
divs.forEach(div => {
sign = (getRandomBoolean() === 1) ? '+' : '-';
div.style.setProperty('transform', `skew(${sign}45deg)`);
root.style.setProperty('--theme', `#${theme}`);
});
}, 2000);
以這樣的基本想法出發,就可以修改成不同的變化,像是加入 clip-path
來進一步操作畫面中的圖案元素:
帶入一點數學,可以產生更多不同的 polygon:
上面這範例中我是隨便用三角函數設定一個公式來跑,但在演講中,袁川有提及 lissajous curves 這個古老的數學公式,他發現非常適合用在 clip-path
上頭,搭配 poylgon
的 fill-rule
屬性,可以做出以下的效果,像是許多特殊的海洋生物一般:
(出處:https://youtu.be/mEpocRIc3q8?t=737)
發揮更多想像力
妥善使用 迴圈、pattern、隨機性這三個要素,就可以有許多的創意組合,除了上面的線條和 clip-path
外,CSS 繪圖常用到的 border-style
, border-image
, gradient
, box-shadow
等等都能拿來嘗試。
附上幾個袁川在 codepen 上的作品:
- border-image: https://codepen.io/yuanchuan/pen/aQjKwO
- background-image + linear-gradient: https://codepen.io/yuanchuan/pen/NLXZLm
- radial-gradient: https://codepen.io/yuanchuan/pen/LXPJOW
這只是其作品的冰山一角,有興趣的讀者可以到他的 codepen 上欣賞各種絢麗的畫作。
像是這個用 z-index 堆疊出的城市天際線圖,不得不佩服他的創造力:
另外,在袁川的演講中,讓我特別印象深刻的是他利用 text-shadow
的效果,將一個括號,運用在生成藝術中,你沒看錯,就是 (
這個括號。
我找不到袁川影片中的範例,但自己依照他的介紹用純 css/js 實作了一個版本:
除了 text-shadow
,上面範例中也用上了 filter
, rotation
, font-size
等等的隨機屬性變化,來生成這幅圖案。
CSS-Doodle
上面的範例都是用 pure css/js 完成的,但袁川其實製作了一個 web component,把許多製作 generative art 需要的一些功能幫你包好成多個函式,像是想要產生 grid,不用再到 html 內複製一大堆 div,也不用自己用 JS createElement,只要透過 css-doodle,一句話就能做到:
:doodle {
@grid: 1x10 / 85%;
}
css-doodle 是基於 Shadow DOM v1 和 Custom Elements v1 實作的 web component,基本上目前主流瀏覽器都能支援。
像上面的例子,透過設定 css-doodle web component 的 shadow-dom 屬性,可以讓他幫你產生 grid layout 的 divs。而在 component 內除了能撰寫一般的 CSS 外,也能利用他提供的 utility function 快速達到一些 random、pick one 等等的效果。
我很喜歡作者在官網上很霸氣地一句話:The limit is the limit of CSS itself.
從他的作品集來看,所言不假。
?css-doodle 的官網就是一個詳細的使用手冊,每個 function 與屬性的旁邊都有對應的實際範例幫助你理解。提供的函式說多不多,說少也不少,一個一個看完也不一定能馬上記住,還是得要在實作時邊對照查詢。
所以說做中學還是最快的,今天文章最後就來解析一個袁川利用 css-doodle 製作的作品,一方面能臨摹大神的創意,另一方面也能比較深刻的了解這些函式的用法。
css-doodle 作品解析
挑一個我很喜歡的作品,非常的漂亮!
這個作品分成兩個 component,一個是背後不斷滑落的線條,另一個是類似不斷旋轉的 DNA 螺旋。
我們單就 DNA 螺旋來看,程式碼非常簡短:
:doodle {
@grid: 45x1 / 40vmin;
position: relative;
z-index: 1;
}
:container {
transform: translate(50%, 33vmin)
}
:after, :before {
content: '';
@place-cell: center;
@size: 100%;
background: radial-gradient(
@p(#FFFDE1, #FB3569) @r(70%),
transparent 0
)
@pn(30% 50%, 70% 50%, 50% 60%) /
@r(.1vmin, 5vmin) @lr()
no-repeat;
}
@place-cell: centerr;
@size: 100%;
will-change: transform;
animation: r 4s linear infinite;
animation-delay: calc(-4s / @size() * @i());
--translate: translateY(calc(-66vmin / @size() * @i()));
@keyframes r {
from { transform: var(--translate) rotate(0) }
to { transform: var(--translate) rotateZ(-1turn) }
}
要使用 css-doodle 的話,上面這段 css 是要放在 <css-doodle>
component 內的:
<css-doodle>
<!-- Your css -->
</css-doodle>
:doodle
:doodle
是針對 <css-doodle>
這個元素本身的 selector,範例中設定了 position
與 z-index
,比較特別的是 @grid
的使用。
@grid
@grid
是用來定義 css-doodle 的 grid layout,你也可以直接設定在 <css-doodle grid="5">
上,但是 @grid
的屬性設定會有比較高的優先權。
@grid: 45x1 / 40vmin;
代表的是 doodle size 為 41vmin,且其中有 45 x 1 的 grid。(vmin
代表的是當前 vh
與 vw
中最小的值)
當你設定了 grid
後,css-doodle
的 shadow-dom
會長出類似如下的結構:
會產生一個 <div class="container">
,並且在裡面產生對應你所設定的 grid
數量的 <div cell>
。
其中有個重點是,在 <css-doodle>
元件內設定的 CSS 會套用到每一個 <div cell>
,以 [cell]:nth-of-type(1)
這樣的 css selector 把生成的 css style 個別應用到 DOM 上。
:container
:container
代表的是 :doodle
內 grid layout 的 container,也就是上面說到的 <div class="container">
。範例中設置 transform: translate(50%, 33vmin)
也就只是把其偏移到畫面中間的位置。
接下來的 :after, :before
是整個圖形的重點:
:after, :before {
content: '';
@place-cell: center;
@size: 100%;
background: radial-gradient(
@p(#FFFDE1, #FB3569) @r(70%),
transparent 0
)
@pn(30% 50%, 70% 50%, 50% 60%) /
@r(.1vmin, 5vmin) @lr()
no-repeat;
}
@place-cell
@place-cell
是用來指定 cell 相對於整個 grid layout 中的位置,在此 component 的 :after, :before
都設定 @place-cell: center
,就代表 grid 中 cell 的 :after
與 :before
都置於相對於整個 grid 的中心位置。
@size
很單純就是同時設定 wdith
與 height
的值。
Background
作者利用 grid 內每個 <div cell>
的 :after
與 :before
,使用 background
與 radial-gradient
屬性來製造我們看到的圓點。
主要使用到 background-image
、background-position-x|y
、background-size
、background-repeat
四個屬性。
如果單純取其中一個 cell 來觀察的話,會長這樣:
若再加上 css-doodle
提供的一些 random (@r), pick(@p) 等 utility function,就可以造成每一個 cell div 都各自擁有兩個不同顏色、大小、位置的圓點。
接下來針對 background
屬性所使用到的 utility function 作介紹。
@p, @pick(v1, v2,...)
@p
為 @pick
的 alias。它會從給定的 list 中隨機挑選數值出來:
@p(#FFFDE1, #FB3569)
就會從這兩個顏色中隨機挑選一個。
@r, @rand(start [,end])
@r
為 @rand
的 alias。接受至少一個參數,作為區間的頭與尾,它會從給定的區間中隨機挑選兩個數值。
因此範例中,background-image
的 radial-gradient
屬性:
radial-gradient( @p(#FFFDE1, #FB3569) @r(70%), transparent 0 )
第一層的設定等同於隨機選取兩個顏色(紅、白),並從 0 ~ 70% 之間選取一個百分比數值作為大小。
@pn, @pick-n(v1, v2,...)
@pn
為 @pick-n
的 alias。會從給定的 list 中,一個一個取出,對應到 grid
中的 cell
。
以範例來說,就會依序設置 cell
的 background-position
值為 30% 50%,70% 50% 和 50% 60%。(依照我觀察,似乎不會保證依照列表的順序,但還是會一個一個對應設置到 cell
上)
@lr, @last-rand
至於 background-size
,範例使用 @r(.1vmin, 5vmin) @lr()
,其中 @r(.1vmin, 5vmin)
就是從 .1vim 到 5vmin 中取一個值,而 @lr
則代表取得最後一個 random 函式所取得的數值,也就是 @r(.1vmin, 5vmin)
的結果。最終的 background-size
就會是兩個相同的隨機值。
到目前為止,設定出來的圖形會長這樣:
所有的點會集中在一起,因為我們的 grid 是 45x1
,也就是只有一個 column。
範例中,作者用 translateY
的方式來將每個點隨機向 Y 軸移動,並加上 @keyframe
與 rotate
的效果,就完成了螺旋的生成圖案:
animation: r 4s linear infinite;
animation-delay: calc(-4s / @size() * @i());
--translate: translateY(calc(-66vmin / @size() * @i()));
@keyframes r {
from { transform: var(--translate) rotate(0) }
to { transform: var(--translate) rotateZ(-1turn) }
}
@i, @index
唯一用到的 utility function 是 @i
,代表當前套用到該 css 的 cell
的 index。
螺旋的部分到此為止,而另一個滑落效果的 doodle 運用的技巧也差不多,但是效果卻完全不同,有興趣的讀者可以研究 codepen 上的原始碼。
結論
生成藝術除了要有創意外,擁有好的實作工具也很重要,?css-doodle
算是給了想嘗試生成藝術的人一個好的開頭,像是文章一開始所製作的簡單生成藝術,也都能夠透過 css-doodle
來實作,程式碼會簡短很多。
而透過 processing 等工具能做出更多效果,甚至能搭配音樂來做出不同變化,今天只是透過 css-doodle
的啟發,練習了一些簡單的生成藝術,體驗了一下,非常有趣,推薦大家一起來試試!
袁川的影片後半段還有很多使用絢麗技巧的生成藝術,非常推薦大家花個四十分鐘把影片看完,相信會有不少收穫,就算不想自己嘗試,欣賞他所創作的作品也是一種享受。