Building a Cool Front End Thing Generator

Avatar of John Polacek
John Polacek on

Grow sales with a smart marketing platform. Try Mailchimp today.

Mailchimp tracking pixel

Whether you are just starting out on the front end, or you’ve been doing it for a long time, building a tool that can generate some cool front-end magic can help you learn something new, develop your skills and maybe even get you a little notoriety.

You might have run across some of these popular online generators:

I’ve had fun building a few of these myself over the years. Basically, any time you run across some cool front-end thing, there might be an opportunity to make an interactive generator for that thing.

In this case, we are going to make an Animated Background Gradient Generator.

Scaffolding the project in Next

A nice thing about these projects is that they’re all yours. Choose whatever stack you want and get going. I’m a big fan of Next.js, so for this project, I’m going to start as a basic Create Next App project.

npx create-next-app animated-gradient-background-generator

This generates all the files we need to get started. We can edit pages/index.js to be the shell for our project.

import Head from "next/head"
import Image from "next/image"
export default function Home() {
  return (
    <>
      <Head>
        <title>Animated CSS Gradient Background Generator</title>
        <meta name="description" content="A tool for creating animated background gradients in pure CSS." />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <h1>
          Animated CSS Gradient Background Generator
        </h1>
      </main>
    </>
  )
}

Animated gradients?

At the time I’m writing this article, if you do a search for animated CSS gradient background, the first result is this Pen by Manuel Pinto.

Let’s take a look at the CSS:

body {
  background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
  background-size: 400% 400%;
  animation: gradient 15s ease infinite;
}

@keyframes gradient {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

This is a great example that we can use as the foundation for the generated animation.

A React component to describe an animated gradient

We can break out a few possible configurable options for the generator:

  • An array of gradient colors
  • The angle of the gradient
  • The speed of the animation

To put in context, we want to provide these settings as data throughout our little app using a higher-order component, context/SettingsContext.js, along with some defaults.

import React, { useState, createContext } from "react"

const SettingsContext = createContext({ colorSelection: [] })

const SettingsProvider = ({ children }) => {
  const [colorSelection, setColorSelection] = useState([
    "deepskyblue",
    "darkviolet",
    "blue",
  ])
  const [angle, setAngle] = useState(300)
  const [speed, setSpeed] = useState(5)
  
  return (
    <SettingsContext.Provider
      value={{
        colorSelection,
        setColorSelection,
        angle,
        setAngle,
        speed,
        setSpeed,
      }}
    >
      {children}
    </SettingsContext.Provider>
  )
}

export { SettingsContext, SettingsProvider }

For our generator’s components, we want to create:

  • a control components to adjust these settings,
  • a visual display component for generated animated gradient, and
  • a component for the CSS code output.

Let’s start with a Controls component that contains the various inputs we used to adjust the settings.

import Colors from "./Colors"

const Controls = (props) => (
  <>
    <Colors />
  </>
)

export default Controls

We can add our SettingsProvider and Controls components to pages/index.js:

import Head from "next/head"
import Image from "next/image"
import { SettingsProvider } from "../context/SettingsContext"
import Controls from "../components/Controls"
import Output from "../components/Output"

export default function Home() {
  return (
    <>
      <Head>
        ...
      </Head>

      <SettingsProvider>
        <main style={{ textAlign: "center", padding: "64px" }}>
          <h1>Animated CSS Gradient Background Generator</h1>
          <Controls />
          <Output />
        </main>
      </SettingsProvider>
    </>
  )
}

Our SettingsProvider begins with the three colors from our CodePen example as defaults. We can verify that we are getting the color settings via our SettingsContext in a new Colors component.

import React, { useContext } from "react"
import { SettingsContext } from "../context/SettingsContext"

const Colors = () => {
  const { colorSelection } = useContext(SettingsContext)
  return (
    <>
      {colorSelection.map((color) => (
        <div>{color}</div>
      ))}
    </>
  )
}

export default Colors

Let’s use the Colors component to display individual color swatches with a small button to delete via our SettingsContext.

import React, { useContext } from "react"
import { SettingsContext } from "../context/SettingsContext"

const Colors = () => {
  const { colorSelection, setColorSelection } = useContext(SettingsContext)

  const onDelete = (deleteColor) => {
    setColorSelection(colorSelection.filter((color) => color !== deleteColor))
  }

  return (
    <div>
      {colorSelection.map((color) => (
        <div
          key={color}
          style={{
            background: color,
            display: "inline-block",
            padding: "32px",
            margin: "16px",
            position: "relative",
            borderRadius: "4px",
          }}
        >
          <button
            onClick={() => onDelete(color)}
            style={{
              background: "crimson",
              color: "white",
              display: "inline-block",
              borderRadius: "50%",
              position: "absolute",
              top: "-8px",
              right: "-8px",
              border: "none",
              fontSize: "18px",
              lineHeight: 1,
              width: "24px",
              height: "24px",
              cursor: "pointer",
              boxShadow: "0 0 1px #000",
            }}
          >
            ×
          </button>
        </div>
      ))}
    </div>
  )
}

export default Colors

You may notice that we have been using inline styles for CSS at this point. Who cares! We’re having fun here, so we can do whatever floats our boats.

Handling colors

Next, we create an AddColor component with a button that opens a color picker used to add more colors to the gradient.

For the color picker, we will install react-color and use the ChromePicker option.

npm install react-color

Once again, we will utilize SettingsContext to update the gradient color selection.

import React, { useState, useContext } from "react"
import { ChromePicker } from "react-color"
import { SettingsContext } from "../context/SettingsContext"

const AddColor = () => {
  const [color, setColor] = useState("white")
  const { colorSelection, setColorSelection } = useContext(SettingsContext)

  return (
    <>
      <div style={{ display: "inline-block", paddingBottom: "32px" }}>
        <ChromePicker
          header="Pick Colors"
          color={color}
          onChange={(newColor) => {
            setColor(newColor.hex)
          }}
        />
      </div>
      <div>
        <button
          onClick={() => {
            setColorSelection([...colorSelection, color])
          }}
          style={{
            background: "royalblue",
            color: "white",
            padding: "12px 16px",
            borderRadius: "8px",
            border: "none",
            fontSize: "16px",
            cursor: "pointer",
            lineHeight: 1,
          }}
        >
          + Add Color
        </button>
      </div>
    </>
  )
}

export default AddColor

Handling angle and speed

Now that our color controls are finished, let’s add some components with range inputs for setting the angle and animation speed.

Here’s the code for AngleRange, with SpeedRange being very similar.

import React, { useContext } from "react"
import { SettingsContext } from "../context/SettingsContext"

const AngleRange = () => {
  const { angle, setAngle } = useContext(SettingsContext)

  return (
    <div style={{ padding: "32px 0", fontSize: "18px" }}>
      <label
        style={{
          display: "inline-block",
          fontWeight: "bold",
          width: "100px",
          textAlign: "right",
        }}
        htmlFor="angle"
      >
        Angle
      </label>
      <input
        type="range"
        id="angle"
        name="angle"
        min="-180"
        max="180"
        value={angle}
        onChange={(e) => {
          setAngle(e.target.value)
        }}
        style={{
          margin: "0 16px",
          width: "180px",
          position: "relative",
          top: "2px",
        }}
      />
      <span
        style={{
          fontSize: "14px",
          padding: "0 8px",
          position: "relative",
          top: "-2px",
          width: "120px",
          display: "inline-block",
        }}
      >
        {angle} degrees
      </span>
    </div>
  )
}

export default AngleRange

Now for the fun part: rendering the animated background. Let’s apply this to the entire background of the page with an AnimatedBackground wrapper component.

import React, { useContext } from "react"
import { SettingsContext } from "../context/SettingsContext"
const AnimatedBackground = ({ children }) => {
  const { colorSelection, speed, angle } = useContext(SettingsContext)
const background =
  "linear-gradient(" + angle + "deg, " + colorSelection.toString() + ")"
const backgroundSize =
  colorSelection.length * 60 + "%" + " " + colorSelection.length * 60 + "%"
const animation =
  "gradient-animation " +
  colorSelection.length * Math.abs(speed - 11) +
  "s ease infinite"
return (
  <div style={{ background, "background-size": backgroundSize, animation, color: "white" }}>
    {children}
  </div>
  )
}
export default AnimatedBackground

We’re calling the CSS animation for the gradient gradient-animation. We need to add that to styles/globals.css to trigger the animation:

@keyframes gradient-animation {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

Making it useful to users

Next, let’s add some code output so people can copy and paste the generated CSS and use in their own projects.

import React, { useContext, useState } from "react"
import { SettingsContext } from "../context/SettingsContext"
const Output = () => {
  const [copied, setCopied] = useState(false)
const { colorSelection, speed, angle } = useContext(SettingsContext)
const background =
  "linear-gradient(" + angle + "deg," + colorSelection.toString() + ")"
const backgroundSize =
  colorSelection.length * 60 + "%" + " " + colorSelection.length * 60 + "%"
const animation =
  "gradient-animation " +
  colorSelection.length * Math.abs(speed - 11) +
  "s ease infinite"
const code = `.gradient-background {
  background: ${background};
  background-size: ${backgroundSize};
  animation: ${animation};
}
@keyframes gradient-animation {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}`
return (
    <div
      style={{ position: "relative", maxWidth: "640px", margin: "64px auto" }}
    >
      <pre
        style={{
          background: "#fff",
          color: "#222",
          padding: "32px",
          width: "100%",
          borderRadius: "4px",
          textAlign: "left",
          whiteSpace: "pre",
          boxShadow: "0 2px 8px rgba(0,0,0,.33)",
          overflowX: "scroll",
        }}
      >
        <code>{code}</code>
        <button
          style={{
            position: "absolute",
            top: "8px",
            right: "8px",
            background: "royalblue",
            color: "white",
            padding: "8px 12px",
            borderRadius: "8px",
            border: "none",
            fontSize: "16px",
            cursor: "pointer",
            lineHeight: 1,
          }}
          onClick={() => {
            setCopied(true)
            navigator.clipboard.writeText(code)
          }}
        >
          {copied ? "copied" : "copy"}
        </button>
      </pre>
    </div>
  )
}
export default Output

Making it fun

It is sometimes fun (and useful) to add a button that sets random values on a generator like this. That gives people a way to quickly experiment and see what kinds of results they can get out of the tool. It is also an opportunity to look up cool stuff like how to generate random hex colors.

import React, { useContext } from "react"
import { SettingsContext } from "../context/SettingsContext"

const Random = () => {
  const { setColorSelection, setAngle, setSpeed } = useContext(SettingsContext)

  const goRandom = () => {
    const numColors = 3 + Math.round(Math.random() * 3)
    const colors = [...Array(numColors)].map(() => {
      return "#" + Math.floor(Math.random() * 16777215).toString(16)
    })
    setColorSelection(colors)
    setAngle(Math.floor(Math.random() * 361))
    setSpeed(Math.floor(Math.random() * 10) + 1)
  }

  return (
    <div style={{ padding: "48px 0 16px" }}>
      <button
        onClick={goRandom}
        style={{
          fontSize: "24px",
          fontWeight: 200,
          background: "rgba(255,255,255,.9)",
          color: "blue",
          padding: "24px 48px",
          borderRadius: "8px",
          cursor: "pointer",
          boxShadow: "0 0 4px #000",
          border: "none",
        }}
      >
        RANDOM
      </button>
    </div>
  )
}

export default Random

Wrapping up

There will be a few final things you’ll want to do to wrap up your project for initial release:

  • Update package.json with your project information.
  • Add some links to your personal site, the project’s repository and give credit where its due.
  • Update the README.md file that was generated with default content by Create Next App.

That’s it! We’re ready to release our new cool front end thing generator and reap the rewards of fame and fortune that await us!

You can see the code for this project on GitHub and the demo is hosted on Netlify.