< 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 latest Chrome and Safari 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.

Getting Started

Include the script directly from cdnjs and everything is ready:

If cdnjs is not ready, just use:

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%' ));

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 following formats of grid value are 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));

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. It's styled using Grid Layout. And 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 it's why this also works:

@even

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

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

@odd

Select cells like :nth-child(odd).

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

@nth(n)

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

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

@at(row, col)

Select cell at specific row and column.

:doodle { @size: 8em; } background: #f5f5f5; margin: .5px; @at(4, 2) { background: #60569e; :after { content: '@row(), @col()'; font-size: .5em; color: #fff; } }

@random

Select cells randomly.

:doodle { @size: 8em; } background: #f5f5f5; transition: .2s; @random { background: #60569e; :after { content: '@index()'; font-size: .8em; color: #fff; } } margin: .5px;

The selector can be applied multiple times.

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

@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; } }

Properties

@grid

Another way to set the grid attribute and the rule 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;

@use

Import styles from custom properties. It lets you write styles from normal css files.

@use: var(--my-rule);

You can add multiple rules in a natural way:

Or define it in the use attribute on the element.

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

Set width and height in one place.

:doodle { @grid: 1 / 8em; } @size: 10em; @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

Return a css shape 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
: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(@multi(2, @r(-50%, 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; }

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; }

@max-row

Returns the max row number of the grid.

:doodle { @grid: 4x2 / 8em 9em; } background: #60569e; margin: .5px; :after { content: '@row() / @max-row()'; color: #fff; font-size: .8em; }

@col

Returns the current column number of the cell.

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

@max-col

Returns the max column number of the grid.

:doodle { @grid: 2x4 / 8em 9em; } background: #60569e; margin: .5px; :after { content: '@col()/@max-col()'; color: #fff; font-size: .8em; }

@size

Returns the total cells number of the grid.

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

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

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

@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; }

Alias: @p

@pick-by-turn(v1, v2,...)

Pick a value from the given list one by one.

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

Alias: @pick-n or @pn

@rand(start [,stop] [,step])

Returns a random value from the range of 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%) );

It also recognizes the letter range:

:doodle { @size: 8em; } background: #60569e; margin: .5px; :after { font-size: @r(.4em, 1.2em, .01); content: '@rand(a, z)'; color: #fff; }

Alias: @r

@last-pick, @last-rand

Returns the last value of @pick or @pick-by-turn and @rand.

:doodle { @size: 8em; } margin: .5px; background: linear-gradient( @rand(360deg), @pick(#60569e, #e6437d, #ebbf4d) 0, @last-pick() 33.33%, @pick(#60569e, #e6437d, #ebbf4d) 33.33%, @last-pick() 66.66%, @pick(#60569e, #e6437d, #ebbf4d) 66.66%, @last-pick() 100% ); background-repeat: no-repeat;

Alias: @lp and @lr, respectively.

@repeat(times, value)

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

:doodle { @size: 8em; } background: #60569e; margin: .5px; border-radius: @repeat(4, @rand(20%, 100%)); :after { content: ''; position: absolute; left: @rand(20%, 80%); top: @rand(20%, 80%); @size: 5px; border-radius: 50%; background: #@repeat(6, @pick(@rand(0, 9), @rand(a, f))); }

@multiple(times, value)

Same as @repeat(), but seperated with comma.

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

Alias: @multi

@n()

Used only inside @repeat and @multiple functions to indicate the current repeating count.

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

@svg(svg)

Use SVG directly as background image. The recommended way to use SVG is through with the @use property.

@<Math>

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

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

:doodle { @grid: 60x1 / 8em 15em; } @size: 75.8% 1px; justify-self: center; background: #60569e; opacity: calc(1 - @i() / @size()); transform: rotate(-15deg) translateX(calc(@sin(@i() / 4) * @PI() * 10px));

@calc(expr)

Evaluate calculations.

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

@var(expr)

Same as var(). Used to prevent the browser from evaluating the value inside nested vars.

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

@hex(num)

Transform a number to the hex format.

:doodle { @size: 8em; } margin: .5px; :after { content: '\@hex(@rand(9632, 9687))'; color: #60569e; }

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

JS API

grid

Getter/setter for the grid attribute on the element.

use

Getter/setter for the use attribute on the element.

update([styles])

Re-render the component (with given styles).

@size: .8em; background: hsla(calc(360 / 60 * @index()), 80%, 80%, .4); clip-path: polygon( 50% 0, 100% 100%, 0 100%); transform: rotate(@rand(360deg)) scale(@rand(.6, 1, .1)) translate( @rand(-10em, 10em), @rand(-10em, 10em) )