DEV Community

Cover image for Get a striped background using D3 without gradients
Cole Kennethy
Cole Kennethy

Posted on

Get a striped background using D3 without gradients

Ready for stripes?

Usually, we have two options for a striped background on a website:

  • define a gradient in CSS;
  • or repeat a bitmap image tile.

Both have problems. CSS gradients, which use color stops immediately next to one another to produce the effect without gradation, use awkward syntax, have limited support for combined stripes, and (presently) produce rough edges at the colors' boundaries because of sub-pixel antialiasing.

Serrated or jagged color boundaries are especially visible on non-Retina-scale monitors.

And a tiled raster image has all the problems of a bitmap:

  • A gif has no capacity for adjustment outside the image editor that created it;
  • designing a seamless tile takes special consideration for the image's edges;
  • and the pixel density of the user's monitor affects the tile's resolution.

A third option for building stripes is a vector pattern employing D3.

We will attempt the third option.

Start a background

First things first

First things are first. Start by adding a container to contain the background.

Add a container to contain the background

The generator of the container's background is written in JS.

Having defined this fact, a few things are first:

  1. defining a class to implement the container;
  2. retrieving the parent dimensions for the container (here, the browser is the container's parent directly);
  3. appending a new SVG element to be the container;
  4. and finally, applying the correct width and height.
Define the class

Call a new class DynamicBackground and give it a constructor:

class DynamicBackground
{
    constructor()
    {
        const selector = '#container';
    }
}
Enter fullscreen mode Exit fullscreen mode

This constructor defines a const to store its main CSS selector.

Create the background element

Using D3, select the document body and append an SVG.

Then, set the id attribute of the SVG to 'container':

constructor()
{
    const selector = '#container';

    d3.select('body')
        .append('svg')
        .attr('id', selector.slice(1)); //slice() deletes '#' from '#container'
}
Enter fullscreen mode Exit fullscreen mode
Get the dimensions to use

Next, save the browser's width and height as instance properties:

constructor()
{
    const selector = '#container';

    d3.select('body')
        .append('svg')
        .attr('id', selector.slice(1));

    this.config = {
        window: {width: window.innerWidth, height: window.innerHeight, },
    };
}
Enter fullscreen mode Exit fullscreen mode
Set the dimensions and a starting background

And to complete the class constructor, set the starting attributes of the SVG:

constructor()
{
    const selector = '#container';

    d3.select('body')
        .append('svg')
        .attr('id', selector.slice(1));

    this.config = {
        window: {width: window.innerWidth, height: window.innerHeight, },
    };

    this.container = d3.select(selector)
        .attr('width', this.config.window.width)
        .attr('height', this.config.window.height)
        .style('display', 'block') //the browser defaults SVGs to display:inline
        .style('background', 'linear-gradient(45deg, lightskyblue, darkslateblue)');
}
Enter fullscreen mode Exit fullscreen mode

Run the class

Compose the class completely and test the object it creates:


class DynamicBackground
{
    constructor()
    {
        const selector = '#container';

        d3.select('body')
            .append('svg')
            .attr('id', selector.slice(1));

        this.config = {
            window: {width: window.innerWidth, height: window.innerHeight, },
        };

        this.container = d3.select(selector)
            .attr('width', this.config.window.width)
            .attr('height', this.config.window.height)
            .style('display', 'block')
            .style('background', 'linear-gradient(45deg, lightskyblue, darkslateblue)');
    }
}

new DynamicBackground;
Enter fullscreen mode Exit fullscreen mode

This blue gradient was rendered dynamically, but something is wrong.

This blue gradient was rendered dynamically, but something is wrong.

Install a method for automatic resizing

This gradient looks great, but try resizing the browser window.

The background fails to resize properly.

Loading an undersized browser window and resizing it back to normal ignores the background size.

Loading an undersized browser window and resizing it back to normal ignores the background size.

The background's failure to resize with the window occurs because nothing updates the background's dimensions to match the window dimensions if the window dimensions are changed by the user.

DynamicBackground needs to be aware of resizing so it can update itself if it happens.

Add an event listener

Solve this by using addEventListener.

Use an event listener in the constructor

Define the method for resizing the background, resizeContainer, and relocate the definitions for width and height to resizeContainer.

Deploy resizeContainer in each of constructor and window.resize:

class DynamicBackground
{
    constructor()
    {
        const selector = '#container';

        d3.select('body')
            .append('svg')
            .attr('id', selector.slice(1));

        this.config = {};

        this.container = d3.select(selector)
            .style('display', 'block')
            .style('background', 'linear-gradient(45deg, lightskyblue, darkslateblue)');

        this.resizeContainer();

        window.addEventListener('resize', () => this.resizeContainer());
    }

    resizeContainer()
    {
        this.config.window = {
            width: window.innerWidth,
            height: window.innerHeight,
        };

        this.container
            .attr('width', this.config.window.width)
            .attr('height', this.config.window.height);
    }
}

new DynamicBackground;
Enter fullscreen mode Exit fullscreen mode

resizeContainer defines the dimensions on initializtion and again if the window resizes.

With the resizing method in two places, the background resizes correctly if the window dimensions change.

With the resizing method in two places, the background resizes correctly if the window dimensions change

By working with addEventListener, resizeContainer will be called each time a change to the shape or size of the browser window is detected.

Construct a better constructor

Refactor the initiators into several methods

So far, the constructor looks like this:

constructor()
{
    //the SVG container's name is defined literally, which is not modular
    const selector = '#container';

    //this part creating the container could have its own method
    d3.select('body')
        .append('svg')
        .attr('id', selector.slice(1));

    //this definition of the starting configuration can stay where it is
    this.config = {};

    //these lines for 'display' and 'background' combine structure with style
    this.container = d3.select(selector)
        .style('display', 'block')
        .style('background', 'linear-gradient(45deg, lightskyblue, darkslateblue)');

    //and the way resizeContainer is called twice could be better arranged
    this.resizeContainer();

    window.addEventListener('resize', () => this.resizeContainer());
}
Enter fullscreen mode Exit fullscreen mode

This constructor’s tasks would be easier to manage if they were handled in separate methods.

Argue a parameter

A useful change to constructor, in addition to its reorganization, would be to make use of the method's ability to take arguments. We could parameterize selector to enable its definition through new and let the name of the SVG be arbitrary:

constructor(selector, config = {}) {} //constructor now with parenthesized parameters

//...

new DynamicBackground('#container'); //the SVG's name is not inside the constructor
Enter fullscreen mode Exit fullscreen mode

This way, we can later create background objects with different names.

Define specific initiators

The constructor is the first big initiator. Let it branch into smaller, more specific initiators.

Initiate the container

The first little initiator to write is the one that defines placement of the empty SVG.

Call this initContainer:

initContainer()
{
    this.container = 
        d3.select('body')
            .append('svg') //this.container holds an 'svg'
            .attr('id', this.config.selector.slice(1)) //then its 'id' is set via config
            .style('display', 'block');//...then it receives an important browser override

    this.resizeContainer();

    window.addEventListener('resize', () => this.resizeContainer());
}
Enter fullscreen mode Exit fullscreen mode

initConntainer calls resizeContainer.

Refactor what resizes the container

The second little initiator to write is what sets width and height for the SVG; call it resizeContainer.

resizeContainer has a dedicated method because the program calls its functionality more than once, and using a dedicated method for resizing makes for clearer methodology:

resizeContainer()
{
    this.config.window = {
        width: window.innerWidth,// config uses the browser's values
        height: window.innerHeight,
    };

    this.container
        .attr('width', this.config.window.width)// container uses the config
        .attr('height', this.config.window.height);

    this.renderBackground();
}
Enter fullscreen mode Exit fullscreen mode

resizeContainer calls renderBackground.

Atomize the background effect

Next, we need to write renderBackground, because now initContainer calls resizeContainer and resizeContainer calls renderBackground.

The true background renderer is actually a single CSS attribute, so its method can be a single line:

renderBackground()
{
    this.container.style('background', 'linear-gradient(45deg, lightskyblue, darkslateblue)');
}
Enter fullscreen mode Exit fullscreen mode

With our three initiators initContainer, resizeContainer, and renderBackground, we can build a better constructor.

Rebuild the constructor

The updated constructor's chain of events in order is that

  1. initContainer calls resizeContainer, then attaches resizeContainer to the window listener;
  2. resizeContainer calls renderBackground;
  3. and renderBackground renders the background.

With config paired with selector as two parameters, the refactored constructor delegates to an initiator:

constructor(selector, config = {})
{
    //the ... enables config properties to override any
    //default configs stored within the constructor
    //(here, no defaults are employed)
    this.config = {
        selector, 
        //colorStart: 'black',//potentially a default config property
        ...config, 
    };

    this.initContainer();
}
Enter fullscreen mode Exit fullscreen mode

Refactoring the constructor to call a chain of initiators yields an improved design:


class DynamicBackground
{
    constructor(selector, config = {})
    {
        this.config = {
            selector, 
            ...config, 
        };

        this.initContainer();
    }

    initContainer()
    {
        this.container = 
            d3.select('body')
                .append('svg')
                .attr('id', this.config.selector.slice(1))
                .style('display', 'block');

        this.resizeContainer();

        window.addEventListener('resize', () => this.resizeContainer());
    }

    resizeContainer()
    {
        this.config.window = {
            width: window.innerWidth,
            height: window.innerHeight,
        };

        this.container
            .attr('width', this.config.window.width)
            .attr('height', this.config.window.height);

        this.renderBackground();
    }

    renderBackground()
    {
        this.container.style('background', 'linear-gradient(45deg, lightskyblue, darkslateblue)');
    }

}

new DynamicBackground('#container');
Enter fullscreen mode Exit fullscreen mode

This creates the same result as its previous version, but the class is now easier to read.

Recognize object-oriented inheritance

Our class, DynamicBackground, still does not render a striped background, but this is a good start.

DynamicBackground renders only a simple (and now, replaceable) gradient of lightskyblue and darkslateblue.

DynamicBackground is the correct name for this class because the class provides a foundation for a dynamically-generated background; its default incarnation renders a simple linear-gradient(45deg, lightskyblue, darkslateblue).

We need stripes

At this point, to change the background effect that DynamicBackground generates, only its rendering method, renderBackground, needs changing.

But DynamicBackground is serving its purpose; a container for a dynamic background is being provided.

To implement stripes, should DynamicBackground be changed, or should it be extended?

Extend DynamicBackground

Instead of using DynamicBackground as the source of a stripes effect, we can use it as the container.

Later, we might want something other than stripes to employ dynamic resizing, like a flowery floral print, or vector noise. DynamicBackground works better if its main functionality is repurposable for different effects.

We should start another class.

Start a new class

Start a new class, call it BackgroundStripes, and declare that it extends DynamicBackground:

class BackgroundStripes extends DynamicBackground {}
Enter fullscreen mode Exit fullscreen mode

Defining a new class, BackgroundStripes, that extends the previous class, DynamicBackground, without any difference to the methods causes no change:

new BackgroundStripes('#container'); //the same object is created
Enter fullscreen mode Exit fullscreen mode

Extend the constructor and provide instance configuration

As it is, our class extension is using the same constructor as its parent.

A good reason to extend constructor is to set it to render unconfigured stripes, which would be rendered if the extension is run without any specifics.

And anyway, there is no way to use this later in the class without the constructor for BackgroundStripes invoking the constructor for its parent; we need this.

Use super to enable an extension of constructor

super is JavaScript's function for calling a parent.

To call the parent's constructor directly, we just call super and pass any arguments that the parent's constructor would take.

To call the parent's constructor with nothing new, we would write the constructor for BackgroundStripes to use super and pass only selector and config:

constructor(selector, config = {})
{
    super(selector, config);
}
Enter fullscreen mode Exit fullscreen mode

But to call the parent's constructor with something new, we would write constructor for BackgroundStripes to include something new, like default stripe options, merge what is new with any further constructor arguments, and pass that as one larger configuration instead:

constructor(selector, configExtended = {})
{
    const config = {
        stripes: [ //an array default stripe effects
            {
                angle: 45,
                width: 25,
                gap: 50,
                color: 'rgba(0,0,0,.25)',
            },
        ],
    };

    super(selector, {...config, ...configExtended});// these configs merge
}
Enter fullscreen mode Exit fullscreen mode

The set of options for one striped background lists the stripes' uniform color, angle, and width, and the uniform space between each stripe.

Redefine renderBackground

renderBackground must be overridden so that it can render stripes instead of a gradient.

Rather than modifying renderBackground in DynamicBackground, we can overwrite the method entirely through class extension.

Provide a new renderBackground in our new class, BackgroundStripes:

renderBackground()
{
    this.container.selectAll('*').remove();

    const defs = this.container.append('defs');

    this.config.stripes.forEach((stripe, r) => {

        let pattern = defs.append('pattern')
            .attr('id', `StripesEffect${r}`)
            .attr('patternUnits', 'userSpaceOnUse')
            .attr('width', stripe.width + stripe.gap)
            .attr('height', stripe.width + stripe.gap)
            .attr('patternTransform', `rotate(${stripe.angle})`);

        pattern.append('rect')
            .attr('x', 0)
            .attr('y', (stripe.offset ?? 0))
            .attr('width', stripe.width + stripe.gap)
            .attr('height', stripe.width)
            .attr('fill', stripe.color);

        this.container.append('rect')
            .attr('width', this.config.window.width)
            .attr('height', this.config.window.height)
            .attr('fill', `url(#StripesEffect${r})`);
    });
}
Enter fullscreen mode Exit fullscreen mode

This extension to renderBackground, which overwrites the previous renderBackground, performs five actions to achieve the stripe effect:

It removes anything that might already exist

renderBackground is called every time the browser window is resized. The container is emptied before the effect is rendered to avoid stacking multiple backgrounds, which otherwise would cause interference with translucent color:

this.container.selectAll('*').remove();
Enter fullscreen mode Exit fullscreen mode
It defines a repeating tile pattern

Our SVG can optionally include special XML, <defs>.

<defs> renders nothing on its own; its contents are descriptive references.

This addendum to <defs> specifies an SVG-referenceable repeating pattern:

let pattern = defs.append('pattern')
    .attr('id', `StripesEffect${r}`)
    .attr('patternUnits', 'userSpaceOnUse') //base the units on the entire SVG
    .attr('width', stripe.width + stripe.gap) //set the width of each tile
    .attr('height', stripe.width + stripe.gap) //set the height
    .attr('patternTransform', `rotate(${stripe.angle})`); //rotate the tile itself
Enter fullscreen mode Exit fullscreen mode
It uses the configuration object to define the stripes effect

The stripe to be repeated is drawn in the top-left corner of the tile.

Because the tile is rotated and repeated, not the stripe, the stripe's rectangle remains static inside the tile:

pattern.append('rect') //append a rectangle to the pattern
    .attr('x', 0)
    .attr('y', 0)
    .attr('width', stripe.width + stripe.gap) //the stripe width is the tile width
    .attr('height', stripe.width) //the stripe height is less than the tile height
    .attr('fill', stripe.color);
Enter fullscreen mode Exit fullscreen mode
It fills the SVG with the pattern

A single big rectangle is installed to cover every pixel of the SVG container.

Finally, this big rectangle's fill is set to the pattern named in <defs>:

this.container.append('rect')
    .attr('width', this.config.window.width)
    .attr('height', this.config.window.height)
    .attr('fill', `url(#StripesEffect${r})`);
Enter fullscreen mode Exit fullscreen mode
And it performs these steps for each set of stripe definitions

These three blocks, let pattern, pattern.append, and this.container.append, are repeated for every set of stripe properties defined in the array of sets at this.config.stripes:

const defs = this.container.append('defs');

this.config.stripes.forEach((stripe, r) => {

    let pattern = defs.append('pattern')
        .attr('id', `StripesEffect${r}`) // a unique id using $r
        .attr('patternUnits', 'userSpaceOnUse')
        .attr('width', stripe.width + stripe.gap)
        .attr('height', stripe.width + stripe.gap)
        .attr('patternTransform', `rotate(${stripe.angle})`);

    pattern.append('rect')
        .attr('x', 0)
        .attr('y', (stripe.offset ?? 0))
        .attr('width', stripe.width + stripe.gap)
        .attr('height', stripe.width)
        .attr('fill', stripe.color);

    this.container.append('rect')
        .attr('width', this.config.window.width)
        .attr('height', this.config.window.height)
        .attr('fill', `url(#StripesEffect${r})`);// the same unique id
});
Enter fullscreen mode Exit fullscreen mode

Run the extended renderBackground

Pair the two classes in the same script

Put the two classes in the same script and run the extension with new.


class DynamicBackground
{
    constructor(selector, config = {})
    {
        this.config = {
            selector, 
            ...config, 
        };

        this.initContainer();
    }

    initContainer()
    {
        this.container = 
            d3.select('body')
                .append('svg')
                .attr('id', this.config.selector.slice(1))
                .style('display', 'block');

        this.resizeContainer();

        window.addEventListener('resize', () => this.resizeContainer());
    }

    resizeContainer()
    {
        this.config.window = {
            width: window.innerWidth,
            height: window.innerHeight,
        };

        this.container
            .attr('width', this.config.window.width)
            .attr('height', this.config.window.height);

        this.renderBackground();
    }

    renderBackground()
    {
        this.container.style('background', 'linear-gradient(45deg, lightskyblue, darkslateblue)');
    }
}

class BackgroundStripes extends DynamicBackground
{
    constructor(selector, configExtended = {})
    {
        const config = {
            stripes: [
                {
                    angle: 45,
                    width: 25,
                    gap: 50,
                    color: 'rgba(0,0,0,.25)',
                },
            ],
        };

        super(selector, {...config, ...configExtended});
    }

    renderBackground()
    {
        this.container.selectAll('*').remove();

        const defs = this.container.append('defs');

        this.config.stripes.forEach((stripe, r) => {

            let pattern = defs.append('pattern')
                .attr('id', `StripesEffect${r}`) // a unique id using $r
                .attr('patternUnits', 'userSpaceOnUse')
                .attr('width', stripe.width + stripe.gap)
                .attr('height', stripe.width + stripe.gap)
                .attr('patternTransform', `rotate(${stripe.angle})`);

            pattern.append('rect')
                .attr('x', 0)
                .attr('y', (stripe.offset ?? 0))
                .attr('width', stripe.width + stripe.gap)
                .attr('height', stripe.width)
                .attr('fill', stripe.color);

            this.container.append('rect')
                .attr('width', this.config.window.width)
                .attr('height', this.config.window.height)
                .attr('fill', `url(#StripesEffect${r})`);// the same unique id
        });
    }
}

new BackgroundStripes('#container');
Enter fullscreen mode Exit fullscreen mode
Render stripes instead of a gradient by using the class extension

Both the stripes and their properties are defined inside  raw `renderBackground` endraw .

Both the stripes and their properties are defined inside renderBackground.

Extend BackgroundStripes into StripesViolet

BackgroundStripes is working; we have achieved a striped background using D3.

We can go a step further and make BackgroundStripes into a template of a template.

This would yield a derivative class extension for purple stripes: StripesViolet.

Extend it again

Remember what constructor in DynamicBackground does?

It takes a selector, merges whatever config it receives, and stores the result:

class DynamicBackground
{
    constructor(selector, config = {})
    {
        this.config = {selector, ...config, };
    }
}
Enter fullscreen mode Exit fullscreen mode

And what the constructor of its direct descendent, BackgroundStripes, does?

It also takes a selector, merges whatever config it receives, and stores the result:

class BackgroundStripes extends DynamicBackground
{
    constructor(selector, configExtended = {})
    {
        const config = {stripes: [], };

        super(selector, {...config, ...configExtended});
    }
}
Enter fullscreen mode Exit fullscreen mode

BackgroundStripes embodies the structure of the stripes, but not their style.

Use BackgroundStripes as the extensible class that it wants to be and extend it:

class StripesViolet extends BackgroundStripes
{
    constructor(selector, configExtended = {})
    {
        const config = {stripes: [], };

        super(selector, {...config, ...configExtended});
    }
}
Enter fullscreen mode Exit fullscreen mode

But this time, extend it violetly:

class StripesViolet extends BackgroundStripes
{
    constructor(selector, configExtended = {})
    {
        const config = {
            base: {
                color: '#eee'
            },
            stripes: [
                {
                    angle: 45,
                    color: 'rgba(48, 8, 99, .5)',
                    width: 50,
                    gap: 25,
                },
            ]
        };

        super(selector, {...config, ...configExtended});
    }

    renderBackground()
    {
        super.renderBackground();

        this.container.style('background', this.config.base.color ?? 'none');
    }
}
Enter fullscreen mode Exit fullscreen mode

renderBackground is extended to enable specification of a CSS background beneath the stripes.

Examples

Instantiate a new background object with new StripesViolet('#container'):

Many effects are possible.

Many effects are possible.

Try reversing the red and green values
const config = {
    base: {
        color: '#eee'
    },
    stripes: [
        {
            angle: 45,
            color: 'rgba(8, 48, 99, .33)',
            width: 50,
            gap: 25,
        },
    ],
};
Enter fullscreen mode Exit fullscreen mode

Blue stripes are produced with rgb(8, 48, 99).

Try a reversed angle with a black base
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: -45,
            color: 'rgba(48, 8, 99, .67)',
            width: 25,
            gap: 50,
            offset: 50
        },
    ],
};
Enter fullscreen mode Exit fullscreen mode

These dark stripes look great.

Try setting the width to 1.5× the gap
const config = {
    base: {
        color: '#111'
    },
    stripes: [
        {
            angle: 45,
            color: 'rgba(48, 8, 99, .67)',
            width: 75,
            gap: 50,
        },
    ],
};
Enter fullscreen mode Exit fullscreen mode

These look good too.

Try layering blue and violet
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: -45, //add 90°
            color: 'rgba(8, 48, 99, .67)',
            width: 25,
            gap: 50,
            offset: 50
        },
        {
            angle: -45,
            color: 'rgba(48, 8, 99, .5)',
            width: 75,
            gap: 75,
        },
        {
            angle: -45,
            color: 'rgba(48, 8, 99, .5)',
            width: 150,
            gap: 150,
            offset: 150 + 37.5,
        }
    ],
};
Enter fullscreen mode Exit fullscreen mode

Capacity for layering property sets is the strength of BackgroundStripes.

I like this one.

Do something resembling rhythm

Try multiples of 20
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: -45,
            color: 'rgba(5, 30, 80, 0.75)',
            width: 20,
            gap: 40,
            offset: 40
        },
        {
            angle: -45,
            color: 'rgba(70, 20, 120, 0.45)',
            width: 60,
            gap: 80,
        },
        {
            angle: -45,
            color: 'rgba(120, 60, 180, 0.25)',
            width: 120,
            gap: 160,
            offset: 100,
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

This one has texture.

Try multiples of 15
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: -45,
            color: 'rgba(30, 80, 160, 0.4)',
            width: 30,
            gap: 60,
            offset: 60
        },
        {
            angle: -45,
            color: 'rgba(60, 40, 140, 0.35)',
            width: 90,
            gap: 90,
        },
        {
            angle: -45,
            color: 'rgba(100, 60, 180, 0.25)',
            width: 180,
            gap: 180,
            offset: 135,
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

Jazzy!

The Violet Beauregarde
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: -45,
            color: 'rgba(0, 120, 255, 0.7)',
            width: 20,
            gap: 40,
            offset: 40
        },
        {
            angle: -45,
            color: 'rgba(120, 0, 255, 0.6)',
            width: 70,
            gap: 70,
        },
        {
            angle: -45,
            color: 'rgba(200, 100, 255, 0.35)',
            width: 140,
            gap: 140,
            offset: 105,
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

“A competitive gum chewer who ignores the Factory's warnings and becomes a giant blueberry”

Grape Glass
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: -45,
            color: 'rgba(20, 40, 100, 0.25)',
            width: 40,
            gap: 100,
            offset: 100
        },
        {
            angle: -45,
            color: 'rgba(80, 40, 120, 0.2)',
            width: 120,
            gap: 140,
        },
        {
            angle: -45,
            color: 'rgba(140, 80, 180, 0.15)',
            width: 240,
            gap: 240,
            offset: 180,
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

This one has movement to it.

Blue Basket
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: 45,
            color: 'rgba(30, 90, 200, 0.45)',
            width: 40,
            gap: 80,
            offset: 80
        },
        {
            angle: -45,
            color: 'rgba(10, 40, 120, 0.5)',
            width: 40,
            gap: 80,
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

This one is something created by Microsoft.

Rainy Frontier
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: -45,
            color: 'rgba(5, 50, 130, 0.65)',
            width: 30,
            gap: 60,
            offset: 60
        },
        {
            angle: -45,
            color: 'rgba(40, 120, 255, 0.35)',
            width: 90,
            gap: 120,
        },
        {
            angle: -45,
            color: 'rgba(100, 160, 255, 0.2)',
            width: 180,
            gap: 180,
            offset: 135
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

This one would have been difficult to achieve in Photoshop.

Kind of Cool
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: 30,
            color: 'rgba(80, 160, 255, 0.3)',
            width: 60,
            gap: 120,
            offset: 120
        },
        {
            angle: -45,
            color: 'rgba(20, 60, 140, 0.35)',
            width: 120,
            gap: 160,
        },
        {
            angle: 75,
            color: 'rgba(140, 200, 255, 0.2)',
            width: 200,
            gap: 220,
            offset: 150
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

Kind of mathematically innacurate.

That's All
const config = {
    base: {
        color: '#000'
    },
    stripes: [
        {
            angle: 45,
            color: 'rgba(255, 0, 0, 0.33)',
            width: width,
            gap: width * 3,
            offset: 0
        },
        {
            angle: 45,
            color: 'rgba(0, 0, 255, 0.17)',
            width: width,
            gap: width * 3,
            offset: width * 2
        },
        {
            angle: -45,
            color: 'rgba(255, 255, 255, 0.25)',
            width: width * 4,
            gap: width * 3,
            offset: width * 8
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

No blend modes were used in the making of this tutorial.

Conclusion

Hopefully you enjoyed working with background imagery as much as I did.

rgba(8, 48, 99, .33), #808080

Top comments (0)