Home

How To Make Seamless Patterns

04 January 2021

Sometimes, when we make seamless patterns out of several objects, we need to deal with the situations where the objects are on the borders. Usually we create copies of the objects and place them at proper positions to make the tiling work with no seams.

In CSS, there's a possible way to do it without creating copies manually or doing any calculations to the positions, by utilizing the CSS background property.

Background in CSS is repeatable by default, if we create objects using background or background-image, they can be tiled automatically regardless of their positions.

element {
  background-image:
    radial-gradient(#6155a6 30%, transparent 0);

  /* default */
  background-size: 100% 100%;
  background-repeat: repeat;
}

Pattern fragment

We start with specifying the size of the fragment which is being used for tiling. This is an essential step since it determines the repeatable space of the pattern.

element {
  width: 180px;
  height: 180px;
}

Create a circle object using radial-gradient in the background.

element {
  width: 180px;
  height: 180px;
  background-image:
    radial-gradient(#6155a6 30%, transparent 0);
}

The circle is being tiled properly even though you can't see the edges. Move it to somewhere else using background-position. No need to worry about getting over the edges, the browser has taken care of it for us.

element {
  width: 180px;
  height: 180px;
  background-image:
    radial-gradient(#6155a6 30%, transparent 0);
  background-position: -110px -20px;
}

We can add more objects by adding more background images.

element {
  width: 180px;
  height: 180px;
  background-image:
    radial-gradient(#6155a6 30%, transparent 0),
    radial-gradient(#6155a6 20%, transparent 0),
    radial-gradient(#6155a6 10%, transparent 0);
  background-position:
    -110px -20px,
    -20px -75px,
    20px 40px;
}

This element can be used to make a seamless pattern by tiling. The only problem is that the element itself cannot be tiled as easily as background images. We need to create many identical elements and place them in the grid. Although taking a screenshot of the element and saving it in PNG format is a quick solution.

Using css-doodle

One of the exciting features css-doodle provides is the ability to generate background image from another css-doodle element with the @doodle function. If we create the pattern fragment in css-doodle, it can be used for background image. Which means the pattern fragment can be tiled.

/* container size */
@grid: 1 / 100% 180px;

/* pattern dimension */
background-size: 180px 180px;

/* pattern fragment */
background-image: @doodle(
  @grid: 1 / 100%;
  background-image:
    radial-gradient(#6155a6 30%, transparent 0),
    radial-gradient(#6155a6 20%, transparent 0),
    radial-gradient(#6155a6 5%, transparent 0);
  background-position:
    -110px -20px,
    -20px -75px,
    20px 40px;
);

@grid: 1 / 100% 180px; border: 1px solid #6155a6; background-size: 180px 180px; background-image: @doodle( @grid: 1 / 100%; background-image: radial-gradient(#6155a6 30%, transparent 0), radial-gradient(#6155a6 20%, transparent 0), radial-gradient(#6155a6 5%, transparent 0); background-position: -110px -20px, -20px -75px, 20px 40px; );

Using gradients to create shapes is rather limited. Fortunately, the @doodle function can be nested. For example, we can replace one of the circle objects created from radial-gradient with another css-doodle element, inside which is a heart shape with rotation by 30 degree.

/* pattern fragment */
background-image: @doodle(
  background-image:
    @doodle(
      @grid: 1 / 100%;
      @size: 80px;
      margin: auto;
      background: pink;
      @shape: heart;
      transform: rotate(30deg);
    ),
    radial-gradient(#6155a6 20%, transparent 0),
    radial-gradient(#6155a6 5%, transparent 0);
  background-position:
    -110px -20px,
    -20px -75px,
    20px 40px;
);

@grid: 1 / 100% 180px; background-size: 180px 180px; border: 1px solid #6155a6; background-image: @doodle( @grid: 1 / 100%; background-image: @doodle( @grid: 1 / 100%; @size: 80px; margin: auto; background: pink; @shape: heart; transform: rotate(30deg); ), radial-gradient(#6155a6 20%, transparent 0), radial-gradient(#6155a6 5%, transparent 0); background-position: -110px -20px, -20px -75px, 20px 40px; );

Do it again

I know it's a bit confusing at first. So let's take another example from this demo on CodePen, and recreate the pattern #11 step by step.

We still start from the pattern fragment, add its background color, setup its width and height both to 200px. Note again that putting the objects inside background image is an important step. So here's the skeleton.

@grid: 1 / 200px;

background-color: #005874;
background-image:
  /* objects */;
background-position:
  /* positions */;
@grid: 1 / 200px; background: #005874;

Add objects

We're going to create a new shape for the elements in this pattern. The @shape() function returns a generated string of polygon() used by clip-path.

clip-path: @shape(
  split: 50;
  scale: .3;
  x: cos(t) + cos(2t) * 2;
  y: sin(2t) + sin(t) * 2;
);
@grid: 1 / 200px 150px; :after { content: ''; @size: 100px; background: #ffbe00; margin-left: -20px; clip-path: @shape( split: 50; scale: .3; x: cos(t) + cos(2t) * 2; y: sin(2t) + sin(t) * 2; ); }

Place the shape to the background-image with @doodle function. It's the first object we added to the pattern.

@grid: 1 / 200px;

background-color: #005874;

/* objects */
background-image: @doodle(
  @grid: 1 / 50px;
  background: #ffbe00;
  clip-path: @shape(
    split: 50;
    scale: .3;
    x: cos(t) + cos(2t) * 2;
    y: sin(2t) + sin(t) * 2;
  );
);
/* position */
background-position: 0 0;
@grid: 1 / 200px; background-color: #005874; background-image: @doodle( @grid: 1 / 50px; background: @p(#65d6ce,#1c819e,#e6e6d4,#ffbe00); clip-path: @shape( split: 50; scale: .3; x: cos(t) + cos(2t) * 2; y: sin(2t) + sin(t) * 2; ); ); background-position: 0 0;

Once we have a good understanding of how this works, add more objects and place each of them with background-position. Setting random background colors and transfomations will make them look better.

@grid: 1 / 200px;

background-color: #005874;

/* objects */
background-image: @m10.@doodle(
  @grid: 1 / 50px;
  background: @p(#65d6ce,#1c819e,#e6e6d4,#ffbe00);
  transform: rotate(@r(360deg)) scale(@r(.5, 1));
  clip-path: @shape(
    split: 50;
    scale: .3;
    x: cos(t) + cos(2t) * 2;
    y: sin(2t) + sin(t) * 2;
  );
);
/* place each objects */
background-position:
  -25px 45px, 50px 75px, 90px 95px,
  160px 165px, 20px 0, 22px -65px,
  110px -18px, 120px 34px, 130px 125px,
  5px 90px;
@grid: 1 / 200px; background-color: #005874; background-image: @m10.@doodle( @grid: 1 / 50px; background: @p(#65d6ce,#1c819e,#e6e6d4,#ffbe00); transform: rotate(@r(360deg)) scale(@r(.5, 1)); clip-path: @shape( split: 50; scale: .3; x: cos(t) + cos(2t) * 2; y: sin(2t) + sin(t) * 2; ); ); background-position: -25px 45px, 50px 75px, 90px 95px, 160px 165px, 20px 0, 22px -65px, 110px -18px, 120px 34px, 130px 125px, 5px 90px;

Tiling

Now we put the whole pattern fragment we just made into background image for tiling, with the @doodle function again.

@grid: 1 / 100% 240px;

/* pattern dimension */
background-size: 200px 200px;

/* pattern fragment */
background-image: @doodle(
  @grid: 1 / 100%;
  background-color: #005874;
  background-image: @m10.@doodle(
    @grid: 1 / 50px;
    background: @p(#65d6ce,#1c819e,#e6e6d4,#ffbe00);
    transform: rotate(@r(360deg)) scale(@r(.5, 1));
    clip-path: @shape(
      split: 50;
      scale: .3;
      x: cos(t) + cos(2t) * 2;
      y: sin(2t) + sin(t) * 2;
    );
  );
  background-position:
    -25px 45px, 50px 75px, 90px 95px,
    160px 165px, 20px 0, 22px -65px,
    110px -18px, 120px 34px, 130px 125px,
    5px 90px;
);

@grid: 1 / 100% 240px; background-size: 200px 200px; background-image: @doodle( @grid: 1 / 100%; background-color: #005874; background-image: @m10.@doodle( @grid: 1 / 50px; background: @p(#65d6ce,#1c819e,#e6e6d4,#ffbe00); transform: rotate(@r(360deg)) scale(@r(.5, 1)); clip-path: @shape( split: 50; scale: .3; x: cos(t) + cos(2t) * 2; y: sin(2t) + sin(t) * 2; ); ); background-position: -25px 45px, 50px 75px, 90px 95px, 160px 165px, 20px 0, 22px -65px, 110px -18px, 120px 34px, 130px 125px, 5px 90px; );

How to use it in production

I'd suggest to to create pattern fragments with design tools like PS and AI, but it's still interesting to create something directly from the code. So here are two ways to apply it to your websites:

  • Create pattern fragment in CSS or css-doodle and take screenshot of the DOM element, or use the export() API of css-doodle to save the pattern fragment as image.

    .container {
      background-image: url(pattern.png);
      background-size: /* fragment size */;
    }
  • Import and use css-doodle directly.

    <style>
      css-doodle {
        --pattern: (
          /* your code */
        );
      }
    </style>
    <css-doodle use="var(--pattern)"></css-doodle>

I hope the article explains well how we can make use of CSS background repeat and css-doodle to create seamless backgound patterns. There are coutless ways to form the objects so I can imagine this is a quite useful technique.