css-doodle学习笔记
首页
首页
  • css-doodle 学习笔记

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)。

但目前似乎有bug:
建议使用@nth(2n)和@nth(2n+1):

@at(i,j)

选择第i行第j列的网格元素。

@random(ratio)

按照概率ratio(0≤ratio≤1)随机选择网格元素。(尝试点击右边的图案)

默认概率是0.5。另外,你可以重复使用这个选择器,每个网格元素可以被重复选中:

@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(642)。
如果在:doodle 选择器中只有 @grid 一个属性,那么 :doodle 可以省略。例如,下面的

可以简写为:

@size

该属性指定了当前元素(可以是整个doodle,也可以是一个网格元素)的宽高。
每个网格元素默认与网格单元的大小一致。如果通过 @size 改变其大小,默认与网格单元的left和top对齐,如下:

如果你希望改变大小后的每个网格元素位于网格单元的中心,可以使用 margin:auto:

@content

设置当前网格的文本内容,默认居于网格单元的中心。

@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 是解关于 λ 的方程:

(64λ)2=(1λ)2+2kπ

得到的,目的是为了使得前后两次动画看起来是无缝的。

@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))。这个技巧还有一个妙用,看下面的例子:

其实这时候应该用 @p(--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 。一个示例如下(尝试点击右边的图案):

@pn([1-9a-z]),@pl([1-9a-z]),@pr([1-9a-z]) 和 @pd([1-9a-z]) 也是合法的。

@last-rand, @last-pick, @lr, @lp

@last-rand 返回上一次 @r 的值,简写为 @lr 。

@last-pick 返回上一次 @pick, @pick-n,@pick-d 的值,简写为 @lp 。
代码顺序会影响 @lr,@lp 的值:

@R, @rn

@R(i,j) 返回区间 (i,j) 的随机值,与 @r 的区别在于,@r 用白噪声生成随机数,而 @R 用柏林噪声(Perlin Noise)。
@rn 是 @R 的别名。
当然,@R 或 @rn 也会计入 @lr 。
@R(0,a) 可以简写为 @Ra。
观察下面两幅灰度图,显然用 @R 得到的图更加连续。(尝试点击两幅图案)

其实 @R 还有第三个参数,即频率。默认频率是 1 。

可惜 @grid 有 64x64 的限制,噪声频率比较大的时候看起来像素感明显。

@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),等等。
下面是一个示例。

上面这个例子中的 @n 表示循环计数,从 1 开始。代码实际上被解析为:
: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 。