< css-doodle />

A web component for drawing patterns with CSS

Introduction

<css-doodle /> is based on Shadow DOM v1 and Custom Elements v1. You can use it on all major browsers right now without polyfills.

The component will generate a grid of divs by the rules (plain CSS) inside it. You can easily manipulate those cells using CSS to come up with a graphic pattern or an animated graph.

The limit is the limit of CSS itself.

Donate

Support us by becoming a Backer or Sponsor on open collective.

Getting Started

Download the latest version or include it directly from a CDN:

You can also install it from npm and import the module in JavaScript:

:doodle { @grid: 18 / 100vmax; background: #0a0c27; } --hue: calc(180 + 1.5 * @row * @col); background: hsl(var(--hue), 50%, 70%); margin: -.5px; transition: @r(.5s) ease; clip-path: polygon(@pick( '0 0, 100% 0, 100% 100%', '0 0, 100% 0, 0 100%', '0 0, 100% 100%, 0 100%', '100% 0, 100% 100%, 0 100%' ));

Attributes

Grid

The number of rows and columns in the grid is defined by the grid attribute on the element, ranged from 1 to 32. It's default to be 1x1 when no value or 0 is given.

:doodle { grid-gap: 1px; @size: 8em; } background: #60569e;

The row or column is limited up to 1024 only when the grid is 1-dimensional:

:doodle { grid-row-gap: 1px; @size: 8em 12em; } transition: .2s ease @rand(500ms); background: #60569e; will-change: width; width: @rand(5%, 100%);

The n x m follows the order of (col, row) or (x, y). The following formats of grid value are all recognizable:

  • grid = "0"
  • grid = "5"
  • grid = "20"
  • grid = "5x7"
  • grid = "5 x 7"
  • grid = "5,7"

There's an alternative way to set up the grid by using the @grid property.

:doodle { @grid: 5; @size: 8em; } background: #60569e; transform: scale(@r(.2, .9));

use

Import rules from CSS custom properties (CSS variables).

It's highly recommended to write the rules this way if you want to use css-doodle in production websites. So it won't break when the network is slow or when the browser does not support Web Component.

seed

All random values will be regenerated to the same value as last time based on the seed value. It's quite useful if you want to keep a snapshot.

@grid: 5 / 8em; background: #60569e; transform: scale(@rand(.2, .9));

A random seed will be generated by default if you don't specify explictly, and you can always get the generated value with JS API later.

@grid: 1 / 100vw 100vh; background-color: #0a0c27; background-size: 200px 200px; background-image: @doodle( @grid: 6 / 100%; @size: 4px; font-size: 4px; color: hsl(@r240, 30%, 50%); box-shadow: @m3x5( calc(4em - @nx * 1em) calc(@ny * 1em) @p(@m3(currentColor), @m2(transparent)), calc(2em + @nx * 1em) calc(@ny * 1em) @lp ); );

Selectors

:doodle

The :doodle is a special selector indicates to the component element itself. Note that the styles would be over-written by your normal CSS files outside. (try to hover on the doodle)

:doodle { @grid: 5 / 8em; --s: 0; grid-gap: 1px; } :doodle(:hover) { --s: 1; } --offset: calc(var(--s) * 100%); transform: translateY(var(--offset)); will-change: transform; transition: .5s cubic-bezier(.175, .885, .32, 1.275); transition-delay: @rand(500ms); transform-origin: 50% 50%; background: #60569e;

:container

The :container is the container element that holds all the cells, which is using Grid Layout. You may want to set grid-gap inside it.

:doodle { @grid: 8 / 8em; overflow: hidden; } :container { grid-gap: 1px; transform: rotate(45deg) scale(1.5); } background: #60569e;

It inherits all the grid properties from :doodle so that's why this also works:

@nth(n, ...)

Select the nth cell like :nth-child(n) does.

:doodle { @size: 8em; grid-gap: 1px; } background: #f5f5f5; :nth-child(1) { background: #60569e; } @nth(5) { background: #60569e; } @nth(3n + 8) { background: #e6437d; } @nth(1, 5, 3n + 8) { :after { content: @index; font-size: .8em; color: #fff; } }

@even, @even(cross)

Select cells like :nth-child(even) but shorter.

:doodle { @size: 8em; } @even { background: #60569e; :after { content: @index; font-size: .8em; color: #fff; } }

The cross option makes sure the selections don't collapse to the same row or column.

:doodle { @size: 8em; } @even(cross) { background: #60569e; :after { content: @index; font-size: .8em; color: #fff; } }

@odd, @odd(cross)

Select cells like :nth-child(odd).

:doodle { @size: 8em; } @odd { background: #60569e; :after { content: @index; font-size: .8em; color: #fff; } }

@at(col, row)

Select cell at specific column and row.

:doodle { @size: 8em; grid-gap: 1px; } background: #f5f5f5; @at(4, 2) { background: #60569e; :after { content: @x, @y; font-size: .5em; color: #fff; } }

@random([ ratio ])

Select cells randomly. The ratio accepts value between 0 and 1. Defaults to 0.5.

:doodle { @size: 8em; grid-gap: 1px; } background: #f5f5f5; transition: .2s; @random { background: #60569e; :after { content: @index; font-size: .8em; color: #fff; } }

The selector can be applied multiple times.

:doodle { @grid: 16 / 8em; overflow: hidden; } margin: -.5px; @random { border-top: 1px solid #60569e; } @random { border-left: 1px solid #60569e; } @random(.2) { :after { content: ''; background: hsl(@rand(360), 60%, 70%); @size: @rand(3px); } }

@row(n, ...)

Select the nth row of the grid.

:doodle { @size: 8em; } background: #f5f5f5; margin: .5px; @row(3) { background: #60569e; :after { content: @row; font-size: .8em; color: #fff; } }

The odd and even is supported.

:doodle { @size: 8em; } background: #f5f5f5; margin: .5px; @row(even) { background: #60569e; :after { content: @row; font-size: .8em; color: #fff; } } @row(even) { :after { content: @row; font-size: .8em; color: #fff; } }

@col(n, ...)

Select the nth column of the grid.

:doodle { @size: 8em; } background: #f5f5f5; margin: .5px; @col(3) { background: #60569e; :after { content: @col; font-size: .8em; color: #fff; } }

You can use odd and even too.

:doodle { @size: 8em; } background: #f5f5f5; margin: .5px; @col(odd) { background: #60569e; :after { content: @col; font-size: .8em; color: #fff; } }

@grid: 1 / 100vw 100vh; background-size: 83px 135px; background-color: #D24B45; background-image: @doodle( @grid: 2 / 100%; background: @pn(#3C2B34, #F7F0E9, #F7F0E9); transform-origin: @pn(100% 100%, 0 100%, 100% 0, 0 0); transform: rotateX(45deg) skewY(@pn(34deg, -34deg, -34deg)); );

Properties

@grid

This is another way to define the grid value and it has higher priority.

:doodle { @grid: 3 / 8em; } background: #60569e; margin: .5px;

Set grid and doodle size at the same time:

:doodle { @grid: 8 / 8em; grid-gap: 1px; } background: #60569e;

The :doodle selector can be ommited if there's no other rules for it.

@use

Like @use attribute but as part of the rule.

@use: var(--rule-a), var(--rule-b);

@size, @min-size, @max-size

Set width and height in one place.

@grid: 1 / 8em; @size: 4em 5em; @min-size: 8em; @max-size: 8em; background: #60569e; :after { content: 8em x 8em; color: #fff; font-size: .8em; }

@place-cell

Place cells relative to the grid.

:doodle { @grid: 1x5 / 8em; border: 1px solid #60569e; } @size: 1.6em; background: rgba(72, 74, 142, .8); @nth(1) { @place-cell: 0 top; } @nth(2) { @place-cell: right 25%; } @nth(3) { @place-cell: center; } @nth(4) { @place-cell: .8em calc(100% - .8em); } @nth(5) { @place-cell: 75% 80%; } :after { content: @index; color: #fff; font-size: .8em; }

@shape

Turns the element into a shape which is generated with clip-path and polygon().

:doodle { @grid: 7 / 8em; @shape: circle; } @even { @shape: hypocycloid 4; background: #60569e; transform: scale(2) rotate(-60deg); }

Basic shapes

For more shapes you can use @shape() function.

:doodle { @grid: 7 / 100vmax; background: #0a0c27; } @shape: clover 5; background: hsla( calc(360 - @i * 4), 70%, 68%, @r.8 ); transform: scale(@r(.2, 1.5)) translate(@m2.@r(±50%));

Functions

@index

Returns the current index value of the cell.

:doodle { @size: 8em; } background: #60569e; margin: .5px; :after { content: @index; color: #fff; font-size: .8em; }

The parentheses can be omitted if the function has no parameters.

@grid: 5x1 / 8em; @place-cell: center; @size: calc(100% / @size() * @i); z-index: calc(@size - @i); opacity: calc(1.1 - 1 / @size * @i); background: #60569e; :after { content: @index(); position: absolute; top: .2em; right: .2em; color: #fff; font-size: .6em; }

Alias: @i

@row

Returns the current row number of the cell.

:doodle { @size: 8em; } background: #60569e; margin: .5px; :after { content: @row; color: #fff; font-size: .8em; }

Alias: @y

@col

Returns the current column number of the cell.

:doodle { @size: 8em; } background: #60569e; margin: .5px; :after { content: @col; color: #fff; font-size: .8em; }

Alias: @x

@size-row

Returns the max row number of the grid.

@grid: 4x3 / 8em; background: #60569e; margin: .5px; :after { content: @row/@size-row; color: #fff; font-size: .8em; }

Alias: @Y

@size-col

Returns the max column number of the grid.

@grid: 4x3 / 8em; background: #60569e; margin: .5px; :after { content: @col/@size-col; color: #fff; font-size: .8em; }

Alias: @X

@size

Returns the total cells count of the grid.

@grid: 3 / 8em; background: #60569e; margin: .5px; :after { content: @i / @size; color: #fff; font-size: .8em; }

These numbers can be used to generate dynamic values together with calc().

@grid: 5/8em; --alpha: calc(@row * @col / @size); background: rgba(96, 86, 158, var(--alpha)); :after { content: @calc(@row * @col); color: #fff; font-size: .8em; }

Alias: @I

@pick(v1, v2,...)

Randomly pick a value from the given list.
(try to click on the doodle)

:doodle { @size: 8em; } opacity: @pick(1, .6, .3, .1); background: #60569e; :after { content: @pick(1, 2, 3, 4); color: #fff; font-size: .8em; }

It supports range format like this: [0-9a-z]

:doodle { @size: 8em; } opacity: @pick(1, .6, .3, .1); background: #60569e; :after { content: @pick([a-z]); color: #fff; font-size: .8em; }

Alias: @p

@pick-n(v1, v2,...)

Pick a value from the given list one by one.

:doodle { @size: 8em; } opacity: @pick-n(1, .6, .3, .1); background: #60569e; :after { content: @pick-n([1-4]); color: #fff; font-size: .8em; }

Alias: @pn

@pick-d(v1, v2,...)

Pick a value like @pick-n(), but with distinct random order.

:doodle { @size: 8em; } opacity: @pick-d(1, .6, .3, .1); background: #60569e; :after { content: @pick-d([a-z]); color: #fff; font-size: .8em; }

Alias: @pd

@rand(start [,end])

Returns a random value between two numbers.
(try to click the doodle)

:doodle { @size: 8em; } background: rgba(96, 86, 158, @rand(.9)); transition: .2s ease @rand(200ms); will-change: transform; transform: rotate(@rand(360deg)); clip-path: polygon( @rand(100%) 0, 100% @rand(100%), 0 @rand(100%) );

Alias: @r

@last-pick, @last-rand

Returns the last value of @pick, @pick-n, @pick-d and @rand.

@grid: 5 / 8em; background: linear-gradient( @pick-d(45deg, -45deg), @pick(#60569e, #e6437d), rgba(255, 255, 255, 0), @last-pick() );

Alias: @lp and @lr, respectively.

@repeat(times, value)

Compose the given value multiple times.
(try to click the doodle)

:doodle { @size: 8em; grid-gap: 1px } background: #60569e; transition: .2s; transform: scale(.95); border-radius: @repeat(4, @rand(40%, 60%)); :after { content: ''; position: absolute; transition: .2s; left: @rand(20%, 80%); top: @rand(20%, 80%); @size: 5px; border-radius: 50%; background: #@repeat(6, @p([0-9a-f])); }

Alias: @rep

@multiple(times, value)

Same as @repeat(), but seperated with commas. Use capitalized @Multiple to make the values seperated with spaces.

:doodle { @size: 8em; } background: linear-gradient( @rand(360deg), @m3( @pick-n(#60569e, #e6437d, #ebbf4d) calc(@n(-1) * 100% / 3), @lp calc(@n * 100% / 3) ) );

The number closely after the function name will be regarded as the first parameter of the function, which will make the code more concise.

:doodle { @size: 8em; } background: #60569e; margin: 20%; box-shadow: @m5( calc(@n() * 2px) calc(@n * 2px) 0 0 #e6437d );

Alias: @m, @M

@n, @nx, @ny, @N

Used only inside @repeat and @m function to indicate the current repeating count: @n, current column count: @nx, current row count: @ny and the max count value: @N.

:doodle { @size: 8em; } background: radial-gradient( circle at @r(100%) @r(100%), @m20( @p(#60569e, #ebbf4d) calc(@n(-1) * 100% / @N), @lp calc(@n * 100% / @N) ) );

@stripe(color [size], ...)

Make stripe with gradients.

@grid: 5 / 8em; background: linear-gradient( @r(360deg), @stripe( #60569e, #e6437d, #ebbf4d ) );

The size for each step is optional.

:doodle { @size: 8em; } background: linear-gradient( 45deg, @stripe( #60569e 50%, #e6437d, #ebbf4d, #60569e ) );

:doodle { @size: 8em; } border-radius: 50%; background: conic-gradient( @stripe( #60569e 10%, #e6437d 20%, #ebbf4d 30%, #321f35 ) );

See more about stripe function in css-doodle.

@svg(svg)

Use SVG directly as background image.

You can also write SVG with the syntax of CSS.

@svg-filter(filter)

Apply SVG filters.

@<Math>

All Math functions and constants are available prefixed with '@'.

:doodle { @size: 8em; } --alpha: calc(@abs(@abs(@row - 3) + @abs(@col - 3) - 5) / 5); background: rgba( 96, 86, 158, var(--alpha) ); will-change: transform; transform: rotate(15deg) scale(var(--alpha));

You can also use π directly.

@grid: 1x60 / 8em 15em; @size: 65% 1px; justify-self: center; background: #60569e; opacity: calc(1 - @i / @size()); transform: rotate(-15deg) translateX(calc(@sin(@i / 4) * π * 10px));

@calc(expr)

Evaluate calculations.

@grid: 5 / 8em; background: #60569e; :after { content: @calc(@i * @i); color: #fff; font-size: .5em; } @odd { transform: scale(.75); }

@var(expr)

Same as native var(). Used to prevent the browser from evaluating the value inside nested vars. The example below won't work since the var(--bg) has no definition outside.

Use @var() instead.

:doodle { @grid: 5 / 8em; } --bg: #60569e; background: linear-gradient( @r(360deg), @var(--bg) 50%, transparent 0 );

@hex(num)

Transform a number into hex format.

:doodle { @size: 8em; grid-gap: 1px } :after { content: \@hex(@rand(9632, 9687)); color: #60569e; }

@doodle(code)

Generate url() image with css-doodle code.

@grid: @p(2, 3) / 8em; background-image: @doodle( @grid: @r4 / 100%; background: @doodle( @grid: @r4 / 100%; background: @svg( viewBox: 0 0 1 1; path { d: M 0 0 L 1 0 L 1 1; fill: #60569e; } ); ); );

See: https://yuanchuan.dev/whats-new-in-css-doodle

@shaders(code)

Generate url() image with GLSL code. Currently only one uniform value is added: u_resolution.

@grid: 1 / 8em; background: @shaders( void main() { vec2 p = gl_FragCoord.xy / u_resolution.xy; gl_FragColor = vec4(p.yx, .8, 1.); } );

The function accepts three parts. If there's no explict fragment part, all the code will be treated as fragment shader like the example above.

Multiple texture are accepted if they prefix with texture and the texture uniform names will be injected automatically.

@grid: 1 / 8em; background: @shaders( texture1 { @grid: 1 / 100%; background: linear-gradient( 45deg, @stripe.@m20.@p(#FFF4E0, #F8B501, #06ACB5, #17191D, #FC3D3C) ); } fragment { void main() { vec2 p = gl_FragCoord.xy / u_resolution.xy; vec2 c = vec2(.5, .5); vec2 uv = p.xy - c; float R = 3.141593; float len = length(uv * vec2(u_resolution.x / u_resolution.y, 1.)); float angle = atan(uv.y, uv.x) + R * smoothstep(.5, 0., len); float r = length(uv) + .1; vec2 coords = vec2(r * cos(angle), r * sin(angle)) + c; gl_FragColor = texture2D(texture1, coords); } } );

See: https://yuanchuan.dev/add-shaders

@shape(commands)

Generate polygon() in string used by clip-path. For example, to make a hexagon:

@grid: 1 / 8em; background: #60569e; clip-path: @shape( points: 6; scale: .8; );

If the points value is big enough it approximately to be a circle.

@grid: 1 / 8em; background: #60569e; clip-path: @shape( points: 100; scale: .8; );

All commands
  • fill-rule nonzero | evenodd;

  • frame number for frame size;

  • points number between 3 - 3600;

  • rotate number in degree for rotation;

  • scale number for scale factor;

  • origin a pair of value for setting coordinate origin;

  • turn the angle between start/end point, defaults to be 1;

  • x x coordinate for cartesian equation;

  • y y coordinate for cartesian equation;

  • r polar equation;

Operations in equations
  • Operations available: +, -, *, /, %, ^.

  • All JavaScript Math functions and constants are supported. Such as sin, cos, tan, abs, pow, PI or π etc.

@grid: 1 / 8em; background: #60569e; clip-path: @shape( points: 300; r: cos(4t); scale: .8; );

See more examples here: https://css-doodle.com/shapes.

@point(commands)

Generate two values in percent and seperated with space. The syntax is the same with @shape.

@grid: 1 / 8em; background: @m50( radial-gradient(#60569e 50%, transparent 0) @point(r: .8) / 5px 5px no-repeat );

@grid: 50x1 / 8em; @size: 5px; border-radius: 50%; border: 1px solid #60569e; @place-cell: @point( r: sin(t/2); origin: 0 -.3; rotate: 90; );

:doodle { @grid: 20 / 100vmax; background: #0a0c27; font-family: sans-serif; overflow: hidden; } :after { content: \@hex.@r(0x2500, 0x257f); color: hsla(@r360, 70%, 70%, @r.9); font-size: 8vmin; }

JS API

grid

Getter/setter for the grid attribute on the element.

use

Getter/setter for the use attribute on the element.

seed

Getter/setter for the seed attribute on the element.

update([styles])

Re-render the component (with given styles).

export([options])

Export css-doodle element as an image. Returns a Promise object which holds the information of the exported image.

Options
  • scale: Integer Scale factor of the exported image. Default 1.

  • detail: Boolean Returns detailed information of the image, eg. blob size. Default false.

  • download: Boolean Download the image or not. Default false.

  • name: String Saved name for download. Defaults to be the current timestamp.

@grid: 7 / 8em; background: @p(#FFF4E0, #F8B501, #06ACB5, #17191D, #FC3D3C); :after { content: ''; @size: 100%; position: absolute; background: @m(4, radial-gradient( circle at @p(-40% -40%, 140% 140%, 140% -40%, -40% 140%), @p(#FFF4E0, #F8B501, #06ACB5, #17191D, #FC3D3C) 50%, transparent 50% )), radial-gradient( @p(#FFF4E0, #F8B501, #06ACB5, #17191D, #FC3D3C) @r(10%, 40%), transparent 0 ) }