The Moon in 10241 Dots — Re-creating a 1969 Masterpiece

madsstoumann

Mads Stoumann

Posted on July 18, 2023

The Moon in 10241 Dots — Re-creating a 1969 Masterpiece

In 1969, German artist Max Ernst created a beautiful painting in celebration of the Moon-landing:

Original Poster

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;
}
Enter fullscreen mode Exit fullscreen mode

Radial Gradient


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;
  }
}
Enter fullscreen mode Exit fullscreen mode

And now we have:

Fluffy Filter


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>
Enter fullscreen mode Exit fullscreen mode

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
  }) {
  ...
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

... and a fill()-method, where we parse-in the current dot-index as number:

const fill = (number) =>
  ['#74BAA0', '#B3A0C1'][number % random(1, 100)] ||
  '#BCD2A9';
Enter fullscreen mode Exit fullscreen mode

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 and y position of each dot from the coordinates we created using coords().

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;
Enter fullscreen mode Exit fullscreen mode

Let's add that output to the SVG-element, we created earlier:

document.querySelector('.moon').innerHTML = dots()
Enter fullscreen mode Exit fullscreen mode

... and voilà:

Dots

Could this be done in CSS instead of SVG?.
Yes, using sin() and cos() in CSS, this is definitely possible. I just find it easier to work with SVG and it's viewBox when working with such large "datasets" as this.


Combining the Layers

Let's combine the dots with the radial, fluffy-clouds-background:

Combining

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:

Poster Text


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;
}
Enter fullscreen mode Exit fullscreen mode

— we get:

Poster with frame


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:

Wallpaper

Beautiful, isn't it? Hope your CPU could manage the 10241 dots!

Demo

💖 💪 🙅 🚩
madsstoumann
Mads Stoumann

Posted on July 18, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related