1## 写在前面
css-doodle 是什么
简单来说,css-doodle 是一个基于Grid布局,帮助我们在网页上绘制各种图案的 Web Component。
为什么要学习 css-doodle
使用css-doodle,我们用很少的代码就能绘制出相当复杂和美观的图案,这是因为css-doodle在css的基础上添加了很多强大的属性和函数 (有点像scss,但有时候比scss更方便!)
如何获取css-doodle
通过 CDN 导入:
<script src="https://esm.sh/css-doodle/css-doodle.min.js?raw"></script>
通过 ES Modules 导入:
<script type="module">
import 'https://esm.sh/css-doodle'
/* ... */
</script>
通过 npm 导入:
npm install css-doodle
/* index.js */
import 'css-doodle'
其它资料
官方网站:css-doodle
Github: https://github.com/css-doodle/css-doodle?tab=readme-ov-file
学习资料:
- An Introduction to css-doodle, by Yuan Chuan
- Arte generativo con CSS, by Sonia Ruiz
- How to Draw Patterns with CSS Using CSS Doodle, by Adi Purdila
快速入门
css-doodle的主要代码是在html标签<css-doodle>
中书写的,这些代码的格式与css一样。
下面是一个示例:
- :doodle 是一个选择器,代表整个
css-doodle
元素。 - @grid 是一个属性,与css中的grid一样,指定网格布局的列数、行数。
- @size 也是一个属性,与css中的size一样,指定了宽度和高度。这里由于@size是写在选择器:doodle里面的,因此它指定了整个
css-doodle
元素的宽高。 - background 则是原生css属性。你可以在css-doodle中书写几乎所有的css样式 。这里background也是写在选择器:doodle里面的,因此它指定了整个
css-doodle
元素的背景颜色。 - border 同样是原生css属性。尤其注意这里border写在了:doodle外,这意味着它会应用于该2x2网格的每一个网格元素。
选择器
:doodle
这个选择器表示整个css-doodle
元素。
下面是一个示例,我们在 :doodle 中设置border-radius: 50%
,于是整个元素呈现圆形轮廓。
:doodle(:hover)
这个选择器表示当鼠标位于整个css-doodle
元素上时。
下面是一个示例。(尝试把鼠标移入右边的图案)
:hover
这个 css 选择器可以在 css-doodle 中使用,表示当鼠标移入一个网格元素时。
下面是一个示例。(尝试把鼠标移入右边的图案)
:container
这个选择器表示整个网格容器。
:container包含于:doodle。下面两个例子能直观地说明这一点:
@nth()
类似于:nth-child(这个选择器在css-doodle
中仍然是可以使用的)。
- @nth(i) 选择第i个网格元素。
- @nth(a,b,c,...) 选择第a,b,c,...个元素
- @nth(an+b) 选择第 an+b 个元素(n = 0,1,2,...)
- 你还能组合上面的第二点和第三点
@even(), @odd()
分别为:选择第偶数个、第奇数个网格元素。等同于@nth(2n)和@nth(2n+1)。
@at(i,j)
选择第i行第j列的网格元素。
@random(ratio)
按照概率ratio(
@row(i), @col(j)
选择第i行/第j列的网格元素。
@match()
根据括号内的布尔表达式匹配元素。在括号内,@x、@y、@i、@X、@Y、@I 等函数,以及各种数学函数,可以省略@号。
这是最强的选择器,用途非常广泛。例如,@match(i % 2 == 0)
相当于 @even
,@match(x == 2)
相当于 @row(2)
...
下面是一些示例。
属性
@grid
该属性指定了网格的列数、行数。例如,@grid:3x5
指定了一个5行3列的网格,@grid:4
指定了一个4行4列的网格。
@grid 类似于css中的background,是一个简写属性,你还可以在 @grid 中指定 @size 和 background 。每种数值之间要用/号隔开。
例如,@grid:5 / 200px 300px #eee
表示一个宽200px,高300px,背景色为#eee,5行5列的网格。
css-doodle 规定行、列最多为64,也就是说你最多定义一个64x64的网格:@grid: 64;
,不过,如果只有一行或者一列,那么剩下的列/行最多可以定义为4096(
如果在:doodle 选择器中只有 @grid 一个属性,那么 :doodle 可以省略。例如,下面的
@size
该属性指定了当前元素(可以是整个doodle,也可以是一个网格元素)的宽高。
每个网格元素默认与网格单元的大小一致。如果通过 @size 改变其大小,默认与网格单元的left和top对齐,如下:
如果你希望改变大小后的每个网格元素位于网格单元的中心,可以使用 margin:auto
:
@content
设置当前网格的文本内容,默认居于网格单元的中心。
@place
指定当前网格元素相对于整个 doodle 的位置,原理是修改left和top。最常用的是,@place:center
会把网格元素置于 doodle 的中心。
下面是一个示例,请注意,为了使得超出 doodle 区域的部分显示,需要在 @grid 中指定 no-clip
。(或者 overflow:visible
和 contain: none
)
关于css属性contain
,参考https://www.w3cschool.cn/article/59179304.html。
@seed
css-doodle 使用一个种子来生成所有随机数(@r, @R)。通过 @seed 属性可以指定随机数种子。
点击右边的图案,不会刷新成不同的样式。
函数
@[Math]
css-doodle 支持 javascript 中内置对象 Math
的所有静态属性和方法,不过前面需要加上 @ 号(如果在 @match 中书写则不需要加)。
关于内置对象 Math
的所有静态属性和方法,参见 MDN: Math。
这些数学函数有一种简化的嵌套语法,例如 @sin(@cos(@tan(@x)))
可以写成 @sin.cos.tan.x
。(事实上 css-doodle 的其它函数也支持这种语法)
@index, @size, @i, @I, @iI
@index 返回当前网格元素的索引(从1开始)。简写为 @i,作为一个函数,它的括号可以省略,即 @i() 可以简写为 @i 。
@size 返回最大的索引,即网格的总个数。简写为 @I 。同样地,@I() 可以简写为 @I 。
@iI 返回 @i / @I ,即当前索引占总数的比例。
- 一个有用的特性:@i + n 可以写成 @i(n),@i * n 可以写成 @i(*n),(@i + a) * b 可以写成 @i(a, *b) ,对于其它运算同理。@iI,@I,以及后面要讲的@x,@y等也有这个特性,之后不再赘述了。
下面有两个示例:
@i + 1 和 @i(1) 这两种写法的区别不仅在于简易程度上,例如当 @i = 3 时,前者在解析的时候会把字符串替换为 3 + 1,而后者会直接替换为 4,这一点从下面的例子可以直观看出:
在 @match() 中,@i、@I、@iI 可以省略 @ 号。后面要讲的 @x,@y 等函数也有这个特性,之后不再赘述了。
@col, @size-col, @x, @X, @xX, @dx
@ 返回当前网格元素所在的列数,简写为 @x 。
@size-col 返回网格的总列数,简写为 @X 。
@xX 返回 @x / @X,即当前网格元素的列数占总列数的比例。
@dx 返回 @x - @X/2,即当前网格元素的列数到中间列的差。
下面是一些示例。
@row, @size-row, @y, @Y, @yY, @dy
@row 返回当前网格元素所在的行数,简写为 @y 。
@size-row 返回网格的总行数,简写为 @Y 。
@yY 返回 @y / @Y,即当前网格元素的行数占总行数的比例。
@dy 返回 @y - @Y/2,即当前网格元素的行数到中间行的差。
境符「波与粒的境界」
用鼠标躲弹幕(
神必数字 18.0518626734 是解关于
得到的,目的是为了使得前后两次动画看起来是无缝的。
@t, @ts
@t 返回经过的时间(以毫秒计)。
@ts 返回经过的时间(以秒计),相当于 @t(/1000)。
经测试,这两个函数的返回值似乎不能作为 css-doodle 提供的各种数学函数的输入,例如 @sin(@ts) 就是不合法的。
初看会觉得这是个非常厉害的函数,但实践后发现性能堪忧。
还是尽量用 @keyframes 吧,嗯,流畅多了。
@rand, @r
@rand(i,j) 返回一个区间(i,j)内的随机值。简写为 @r 。
@r(0, a) 可以简写为 @ra,例如 @r(0,.25) 简写为 @r.25 。
默认值 @r, @r() 返回 (0,1) 内的随机数,等同于 @r(0,1)。
这是 css-doodle 中最常用的函数之一,随机的引入大大丰富了我们能创造的效果。
@pick, @p
@pick(a,b,c,...) 在 a,b,c,... 中随机选择一个(离散的 @r)。简写为 @p 。
下面是一些示例。(尝试点击右边的图案)
@p([0-9]) 表示从0到9中随机选一个,@p([0-9a-z]) 表示从0到9和a到z中随机选一个。
下面是一些示例。(尝试点击右边的图案)
如果我有一个css变量--a,怎样用@p选择--a的值呢?事实上,直接用@p(--a)
就行了,而不是@p(var(--a))
。这个技巧还有一个妙用,看下面的例子:
@pick-n, @pick-l, @pick-r, @pick-d, @pn, @pl, @pd, @pr
@pick是从列表中随机选择,而 @pick-n 则从列表中依次选择,简写为 @pn 。一个示例如下:
@pick-l 与 @pick-n 一模一样。简写为 @pl 。
@pick-r 从列表中倒序选择("r" 代表 "reverse")。
@pick-d 则是按照列表的一种全排列顺序选择,简写为 @pd 。一个示例如下(尝试点击右边的图案):
@last-rand, @last-pick, @lr, @lp
@last-rand 返回上一次 @r 的值,简写为 @lr 。
@R, @rn
@R(i,j) 返回区间 (i,j) 的随机值,与 @r 的区别在于,@r 用白噪声生成随机数,而 @R 用柏林噪声(Perlin Noise)。
@rn 是 @R 的别名。
当然,@R 或 @rn 也会计入 @lr 。
@R(0,a) 可以简写为 @Ra。
观察下面两幅灰度图,显然用 @R 得到的图更加连续。(尝试点击两幅图案)
其实 @R 还有第三个参数,即频率。默认频率是 1 。
@repeat, @rep
@repeat(num, value),其中 num 是重复次数,value 是被重复的值。
例如,@repeat(6,f) 会被解析成 ffffff,@repeat(3, 0 0) 会被解析成 0 0 0 0 0 0(包括空格)。
但是一些行为是不允许的,例如 @repeat(3,,) 不会解析成,,, @repeat(3, 00) 和 @repeat(3,00 ) 都会解析成 000000 (不包括空格)。
可以简写为 @rep 。
@rep 内当然也是可以使用 @p、@r 这样的函数的,能发挥非常强大的功效。
你甚至能随机生成重复的次数!(尝试点击右边的图案)
还有,@rep 支持嵌套循环,语法是 @rep(mxn, value),可以类比下面的代码(注意 m 在内层循环中):
for (int i = 0; i < n; ++i){
for (int j = 0; j < m; ++j){
...
}
}
例如,@rep(2x3, 0) 会被解析为 000000 ,你可能会想这与 @rep(6, 0) 有什么区别?其实嵌套循环要配合后面介绍的 @nx,@ny 使用,这里我们暂且按下不表。
比较可惜的是@rep不能像scss的@for那样,在循环中定义css变量。比如下面的代码:
:doodle{
@grid: 1 / 15vmax;
@rep(3, --a-@n: @n;);
}
并不会被解析为
:doodle{
@grid: 1 / 15vmax;
--a-1: 1;
--a-2: 2;
--a-3: 3;
}
@multiple, @m, @Multiple, @M
@multiple 与 @repeat 的区别在于:@repeat在重复的时候没有任何分割符,而 @multiple 会用逗号隔开。
例如,@multiple(3, 100% 0%) 会被解析成 100% 0%,100% 0%,100% 0% 。
@m 是 @multiple 的简写。
@m(3, value) 可以简写为 @m3(value),@m(12, value) 可以简写为 @m12(value),等等。
下面是一个示例。
:doodle{
@grid: 1 / 15vmax;
border-radius: 50%;
}
background:
conic-gradient(
hsl(360, 100%, 50%)),
hsl(359, 100%, 50%)),
hsl(358, 100%, 50%)),
...
hsl(1, 100%, 50%)),
hsl(0, 100%, 50%))
);
因此,这段代码创建了一个色轮。
@Multiple 与 @multiple 的区别是,前者的分割符为空格。例如,@Multiple(3, 100% 0%) 会被解析成 100% 0% 100% 0% 100% 0% 。
@M 是 @Multiple 的简写。
下面是官网的一个示例。(我加了点动画)
此外,@m 和 @M 也是可以嵌套循环的。例如 @m(3x5, hello),@M(5x3, 6) 。
嵌套循环也是可以简写的,上面的代码可以写成:
@n, @N,@nx, @ny
这四个变量用在循环函数@rep, @m,@M 中。
@n 表示当前的循环计数(从 1 开始)
@N 表示循环次数,这个一般用于循环次数是通过 @p 等函数随机产生的时候(尝试点击右边的图案)
@nx 和 @ny 分别表示当前循环的列与行。这两个变量用在嵌套循环中。
下面是一些示例。
类比下面的 C 代码:
for (int i = 1; i <= 3; ++i){
for (int j = 1; j <= 2; ++j){
printf("%d", j);
}
}
类比下面的 C 代码:
for (int i = 1; i <= 3; ++i){
for (int j = 1; j <= 2; ++j){
printf("%d", i);
}
}
@shaders
这个函数用在background
属性中,你可以在其中编写 GLSL 代码以生成一张背景图。
语法如下:
background: @shaders(
/* 纹理图 */
texture_0{
/* 你可以在这里书写 css-doodle 代码,以创建一张纹理图*/
}
texture_1{
/* 你可以在这里书写 css-doodle 代码,以创建一张纹理图*/
}
/* texture_2, texture_3, ... */
/* 顶点着色器 */
vertex{
/* 你可以在这里书写顶点着色器代码 */
}
/* 片元着色器 */
fragment{
/* 你可以在这里书写片元着色器代码 */
}
);
如果只有片元着色器,那么可以简写为:
background: @shaders(
/* 片元着色器代码 */
/* void main(){
....
} */
);
此外,css-doodle 提供了一个内置变量:u_resolution,与 shadertoy 中的 iResolution 一样。
@shaders 让我们能做很多 css 中做不到的事情(学 shader 的理由又多了一个
下面是一些示例。
@doodle
这个函数用在background
属性中,你可以在其中编写 css-doodle 代码,以创建一张背景图。
说白了,就是把每个网格当成一个<css-doodle>
元素。
一些示例如下:
注意下面这个例子的细节:
- 使用 css 变量
--a
储存外层 doodle 的 @y,再用前面提过的技巧@p(--a)
实现内层 doodle 的列数随 @y 增加而增加; @abs(@dx)
可以简写为@abs.dx
。