In 1969, German artist Max Ernst created a beautiful painting in celebration of the Moon-landing:
Entitled Naissance d'une Galaxie (Birth of a Galaxy), I can imagine it must've taken months to complete, using stencils for all the dots.
Luckily, we can code it much faster!
Radial Gradient
First, we need a background-layer with a radial background:
main {
background: #06101D radial-gradient(
#245898,
#2C67AB,
#244D7A 15%,
#475F66,
#93A7A2 65%,
#234E85 66%,
#06101D 70%
) no-repeat;
}
Fluffy Clouds Filter
We need some "fluffy clouds" to simulate the painted layers, so we'll use the "fluffy" filter from my CSS Filter Editor:
We add an ::after
-pseudo-element to the same layer as the radial background, inheriting that same background, but also using the "fluffy"-filter:
main {
&::after {
background: inherit;
mix-blend-mode: hard-light;
content: "";
display: block;
filter: url(#fluffy) blur(5px);
inset: 0;
opacity: 0.33;
position: absolute;
}
}
And now we have:
Markup
Before we continue with the dots, let's add an SVG-element in the markup — we'll add the innerHTML
later:
<svg viewBox="0 0 1000 1000" class="moon"></svg>
Dots
To hardcode the dots would probably take as long time as the original painting, so let's use JavaScript and some Math to help us out!
To start with, we need a function, dots()
, with a configuration-object:
function dots(config =
{
dotsring: 8,
dotsize: 3,
radius: 2,
rings: 50,
rotate: 6,
spread: 10,
x: 1000,
y: 1000
}) {
...
}
Explainer:
- dotsring. Dots per ring. This number is added to each new ring.
-
dotsize. The relative dotsize (see SVG
viewBox
) - radius. The start radius-index. In this case, we want to skip the first one, creating a larger "hole" in the center of the moon.
- rings. The total amount of rings
- rotate. Amount to rotate each ring
- spread. The distance between the dots in a ring
-
x. The
viewBox
x-width of the SVG. -
y. The
viewBox
y-width of the SVG.
Next, we need arrays of coordinates for each ring. For that, we need a small helper-method:
const coords = (number) => {
const frags = 360 / number
return Array.from({ length: number + 1 },
(_, i) => (frags / 180) * i * Math.PI)
}
Fill
If you inspect the original painting, you'll notice that most of the dots have the same, light-yellow color. A few, random dots are light blue or lavender.
To help us fill-in random dots with these values, we need a random-method:
const random = (min, max) =>
Math.floor(Math.random() * (max - min) + min);
... and a fill()
-method, where we parse-in the current dot-index
as number
:
const fill = (number) =>
['#74BAA0', '#B3A0C1'][number % random(1, 100)] ||
'#BCD2A9';
Rendering the dots
To render the dots, we need two loops:
One for the rings. For each ring, we output a
<g>
-element, to<g>
roup all the dots within a ring.One for the dots within a single ring. In this inner loop, we calculate the
x
andy
position of each dot from the coordinates we created usingcoords()
.
After that, we simply output a <circle>
for each entry.
let s = '';
for (let i = config.radius; i <= config.rings; i++) {
const r = config.spread * i;
const ring = coords(config.dotsring * i);
s += `<g style="--r:${i * config.rotate}deg">${ring
.map((value, index) => {
const x =
config.x / 2 - Math.round(r * Math.cos(value));
const y =
config.y / 2 - Math.round(r * Math.sin(value));
return `<circle cx="${x}" cy="${y}" r="${
config.dotsize
}" fill="${fill(i + index)}" />`;
})
.join('')}</g>`;
}
return s;
Let's add that output to the SVG-element, we created earlier:
document.querySelector('.moon').innerHTML = dots()
... and voilà:
Could this be done in CSS instead of SVG?.
Yes, usingsin()
andcos()
in CSS, this is definitely possible. I just find it easier to work with SVG and it'sviewBox
when working with such large "datasets" as this.
Combining the Layers
Let's combine the dots with the radial, fluffy-clouds-background:
And we're done! ... but what about the lower part of the painting — below the Moon?
I tried using the drops
-filter (from the CSS Filter Editor above) and a bunch of overlaying gradients, but simply couldn't get the right "look and feel".
Adding Poster Texts
Let's turn the painting into a poster, and add some texts.
We'll use cqi
-units for the texts, so they scale nicely to the width of our poster:
Framing the Poster
Posters look better in frames, so let's add a nice wooden frame!
CSS has a border-style
called ridge
which does the job — so with:
body {
border: 1cqi ridge #D39E85;
}
— we get:
Wallpaper
Finally, it's time to hang our framed poster on a wall.
Let's add a wallpaper from CSS Patterns to the <html>
-element — and a box-shadow
to the <body>
-element:
Beautiful, isn't it? Hope your CPU could manage the 10241 dots!
Demo
Top comments (7)
Wonderful, thank you very much.
Look at this : css-doodle
Regards
I have forgotten : CSS-DoodleDemos :-)
👍
Great Share ❤👍
Thank you!
Very cool - thanks for sharing.
Thank you!