A while ago I saw an interesting pattern generated by a pure mathematical rule, which inspired me to implement a function to recreate the pattern with minimal syntax.
The beauty lies in its rule. Simple yet the results are appealing.
I've then found several other patterns but soon the limitation of grid size 32x32
bothered me.
Creating so many DOM elements to represent cells in the grid is also a bit slow.
@grid: 21 / 9em;
@match(tan.cos.sin(x*y) > 1) {
background: #000;
}
Later one night, I came across another pattern from the book TAOCP Vol.4.
It uses a similar rule in a 256x256
grid.
I really wanted to draw this out myself, so I decided to look for other implementations.
Pattern function
I tried Canvas2D but using shader is much faster for large size grids. The only thing to deal with is to map each pixel to a defined grid cell.
background: @shaders(
void main() {
vec2 uv = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(1.0);
vec2 grid = vec2(256);
vec2 p = 1.0/grid;
float x = ceil(uv.x/p.x);
float y = ceil(grid.y - uv.y/p.y);
if (((int(y*y*x)>>11)&1) == 1) {
color = vec3(0.0);
}
FragColor = vec4(color, 1.0);
}
);
In order to do explorations more quickly I added another function that uses shader programs as the bottom layer. Now the above code can be simplified as:
background: @pattern(
grid: 256;
match(((int(y*y*x)>>11)&1) == 1) {
fill: #000;
}
);
Color
In shader programs, colors are in RGBA format. How to transform CSS color from all other formats into RGBA?
/* How to recongize the color "purple" ? */
fill: purple;
/* or HSL() ? */
fill: hsl(70, 80%, 85%);
A color library might help
but I noticed that the built-in getComputedStyle()
function will always normalize
colors into RGBA format. Life is much easier this way.
function rgba(el, color) {
el.style.color = color;
let [r, g, b, a = 1] = getComputedStyle(el).color
.replace(/rgba?\((.+)\)/, (_, v) => v)
.split(/,\s*/);
return {r, g, b, a};
}
Usage
The grid
property defines the grid dimension.
There's no gap
property yet.
background: @pattern(
grid: 10;
/* different columns and rows */
grid: 100x50;
);
The fill
property fills the grid cell with the given color value.
I didn't use color
because other properties may also need colors.
background: @pattern(
grid: 11;
fill: #000;
);
The third keyword is the match
selector.
It accepts an expression that will be passed to the underneath shader program.
background: @pattern(
grid: 10;
match((int(x+y)%2) == 0) {
fill: #000;
}
);
Examples
background: @pattern(
grid: 63;
fill: #143d59;
match(((int(x*x*y*y)>>5)%2) == 1) {
fill: #f4b51c;
}
);
background: @pattern(
grid: 71;
fill: #143d59;
match(((int(x*y*x*y*7.)>>4)&2) == 2) {
fill: #f4b51c;
}
);
Combining with trigonometric functions.
background: @pattern(
grid: 43;
fill: #f4b51c;
match(((int(sin(y)*sin(x)*6.)>>2)&1) == 1) {
fill: #143d59;
}
);
background: @pattern(
grid: 43;
fill: #f4b51c;
match(((int(cos(x)*cos(y)*5.)>>2)&1) == 1) {
fill: #143d59;
}
);
One more.
background: @pattern(
grid: 80;
fill: #f4b51c;
match((int(x*x + y*y)%80) > 17) {
fill: #143d59;
}
);
I'm not satisfied with the syntax inside the match
selector, as you can see the value types in the shader program are being strictly defined.
Anyway this is a good start.
I'm happy and excited when the idea comes true.