Light Leak
Light Leak

Animated Background

A fully customizable, accessible and versatile animated background component with multiple gradient styles, optional grain effect and options for custom graidnet styles.

Introduction

The ZyfloAnimatedBackground component is a versatile and customizable animated background that can be easily integrated into your Next.js project. It offers a range of preset gradient styles, custom gradient options, and an optional grain effect for added texture.

Add The Component

Add the following component to your project in the /components/zyflo directory:

"use client"

import React, { useEffect, useRef } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { motion, Variants, HTMLMotionProps } from "framer-motion"
import Image from "next/image"

export const PossibleZyfloAnimatedBackgroundVariant = [
  "purple-haze",
  "blue-skies",
  "pink-dreams",
  "ocean-breeze",
  "lavender-mist",
  "aqua-marine",
  "emerald-glow",
  "mint-chocolate",
  "pastel-paradise",
  "lime-light",
  "shadow-dance",
  "custom"
] as const

export type ZyfloAnimatedBackgroundVariant =
  (typeof PossibleZyfloAnimatedBackgroundVariant)[number]

const backgroundVariants = cva("w-full h-full", {
  variants: {
    variant: {
      "purple-haze": "bg-[#8a2be2]",
      "blue-skies": "bg-[#87ceeb]",
      "pink-dreams": "bg-[#ff99d3]",
      "ocean-breeze": "bg-[#99ccff]",
      "lavender-mist": "bg-[#a899ff]",
      "aqua-marine": "bg-[#7fffd4]",
      "emerald-glow": "bg-[#50c878]",
      "mint-chocolate": "bg-[#3eb489]",
      "pastel-paradise": "bg-[#ffd1dc]",
      "lime-light": "bg-[#bfff00]",
      "shadow-dance": "bg-[#000000]",
      custom: "bg-transparent"
    }
  },
  defaultVariants: { variant: "purple-haze" }
})

const gradientConfigs = {
  "purple-haze": [
    { x: 80, y: 88, color: "hsla(270,60%,50%,1)" },
    { x: 28, y: 26, color: "hsla(280,70%,60%,1)" },
    { x: 88, y: 90, color: "hsla(260,80%,70%,1)" },
    { x: 44, y: 29, color: "hsla(290,75%,55%,1)" },
    { x: 14, y: 92, color: "hsla(250,65%,65%,1)" },
    { x: 19, y: 57, color: "hsla(240,85%,75%,1)" },
    { x: 31, y: 3, color: "hsla(300,90%,80%,1)" }
  ],
  "blue-skies": [
    { x: 16, y: 5, color: "hsla(200,80%,70%,1)" },
    { x: 68, y: 79, color: "hsla(220,90%,60%,1)" },
    { x: 43, y: 7, color: "hsla(180,100%,85%,1)" },
    { x: 37, y: 94, color: "hsla(240,70%,50%,1)" },
    { x: 10, y: 88, color: "hsla(190,60%,90%,1)" },
    { x: 14, y: 86, color: "hsla(210,80%,40%,1)" },
    { x: 65, y: 71, color: "hsla(170,100%,70%,1)" }
  ],
  "pink-dreams": [
    { x: 2, y: 79, color: "hsla(328,60%,78%,1)" },
    { x: 38, y: 22, color: "hsla(313,75%,63%,1)" },
    { x: 78, y: 48, color: "hsla(348,71%,71%,1)" },
    { x: 89, y: 55, color: "hsla(0,75%,62%,1)" },
    { x: 60, y: 89, color: "hsla(31,82%,61%,1)" },
    { x: 30, y: 94, color: "hsla(346,96%,74%,1)" },
    { x: 81, y: 27, color: "hsla(304,97%,71%,1)" }
  ],
  "ocean-breeze": [
    { x: 8, y: 69, color: "hsla(200,80%,60%,1)" },
    { x: 0, y: 94, color: "hsla(180,100%,50%,1)" },
    { x: 1, y: 13, color: "hsla(220,70%,70%,1)" },
    { x: 94, y: 79, color: "hsla(240,90%,55%,1)" },
    { x: 20, y: 78, color: "hsla(190,100%,85%,1)" },
    { x: 24, y: 5, color: "hsla(210,100%,40%,1)" },
    { x: 4, y: 11, color: "hsla(170,80%,60%,1)" }
  ],
  "lavender-mist": [
    { x: 7, y: 89, color: "hsla(270,71%,64%,1)" },
    { x: 28, y: 50, color: "hsla(290,71%,71%,1)" },
    { x: 25, y: 31, color: "hsla(250,78%,77%,1)" },
    { x: 91, y: 30, color: "hsla(230,64%,62%,1)" },
    { x: 59, y: 21, color: "hsla(310,72%,64%,1)" },
    { x: 80, y: 90, color: "hsla(280,93%,77%,1)" },
    { x: 9, y: 79, color: "hsla(260,60%,60%,1)" }
  ],
  "aqua-marine": [
    { x: 80, y: 37, color: "hsla(180,100%,50%,1)" },
    { x: 77, y: 99, color: "hsla(200,72%,56%,1)" },
    { x: 96, y: 21, color: "hsla(220,100%,50%,1)" },
    { x: 10, y: 14, color: "hsla(160,100%,50%,1)" },
    { x: 3, y: 5, color: "hsla(190,100%,75%,1)" },
    { x: 76, y: 72, color: "hsla(170,100%,45%,1)" },
    { x: 46, y: 32, color: "hsla(210,89%,70%,1)" }
  ],
  "emerald-glow": [
    { x: 81, y: 20, color: "hsla(140,80%,50%,1)" },
    { x: 29, y: 57, color: "hsla(160,90%,40%,1)" },
    { x: 16, y: 75, color: "hsla(180,100%,45%,1)" },
    { x: 91, y: 92, color: "hsla(120,85%,55%,1)" },
    { x: 3, y: 34, color: "hsla(200,95%,60%,1)" },
    { x: 3, y: 11, color: "hsla(100,100%,65%,1)" },
    { x: 94, y: 38, color: "hsla(220,90%,70%,1)" }
  ],
  "mint-chocolate": [
    { x: 78, y: 8, color: "hsla(160,50%,70%,1)" },
    { x: 2, y: 75, color: "hsla(30,60%,25%,1)" },
    { x: 35, y: 27, color: "hsla(140,70%,80%,1)" },
    { x: 11, y: 29, color: "hsla(20,80%,20%,1)" },
    { x: 54, y: 75, color: "hsla(150,60%,75%,1)" },
    { x: 16, y: 63, color: "hsla(10,70%,30%,1)" },
    { x: 88, y: 79, color: "hsla(170,80%,85%,1)" }
  ],
  "pastel-paradise": [
    { x: 54, y: 18, color: "hsla(350,100%,85%,1)" },
    { x: 13, y: 71, color: "hsla(180,100%,85%,1)" },
    { x: 58, y: 83, color: "hsla(60,100%,85%,1)" },
    { x: 53, y: 65, color: "hsla(270,100%,85%,1)" },
    { x: 9, y: 94, color: "hsla(120,100%,85%,1)" },
    { x: 20, y: 23, color: "hsla(30,100%,85%,1)" },
    { x: 30, y: 80, color: "hsla(210,100%,85%,1)" }
  ],
  "lime-light": [
    { x: 80, y: 88, color: "hsla(82,100%,60%,1)" },
    { x: 28, y: 26, color: "hsla(100,100%,60%,1)" },
    { x: 88, y: 90, color: "hsla(90,100%,70%,1)" },
    { x: 44, y: 29, color: "hsla(95,100%,65%,1)" },
    { x: 14, y: 92, color: "hsla(98,100%,75%,1)" },
    { x: 19, y: 57, color: "hsla(80,100%,85%,1)" },
    { x: 31, y: 3, color: "hsla(78,100%,90%,1)" }
  ],
  "shadow-dance": [
    { x: 0, y: 0, color: "hsla(0,0%,0%,1)" },
    { x: 100, y: 100, color: "hsla(0,0%,100%,1)" },
    { x: 50, y: 50, color: "hsla(0,0%,50%,1)" },
    { x: 25, y: 25, color: "hsla(0,0%,25%,1)" },
    { x: 75, y: 75, color: "hsla(0,0%,75%,1)" },
    { x: 33, y: 33, color: "hsla(0,0%,33%,1)" },
    { x: 66, y: 66, color: "hsla(0,0%,66%,1)" },
    { x: 10, y: 10, color: "hsla(0,0%,10%,1)" }
  ]
}

export interface ZyfloAnimatedBackgroundProps
  extends HTMLMotionProps<"div">,
    VariantProps<typeof backgroundVariants> {
  disableAnimations?: boolean
  triggerWhenInView?: boolean
  srOnly?: string
  label?: string
  customGradients?: Array<{ x: number; y: number; color: string }>
  grain?: boolean
  grainClassName?: string
  children?: React.ReactNode
}

const ZyfloAnimatedBackground: React.FC<ZyfloAnimatedBackgroundProps> = ({
  className,
  variant = "purple-haze",
  disableAnimations = false,
  triggerWhenInView = true,
  srOnly,
  label,
  customGradients,
  grain = false,
  grainClassName,
  children,
  ...props
}) => {
  const containerRef = useRef<HTMLDivElement>(null)
  useEffect(() => {
    const container = containerRef.current
    if (!container) return

    const gradients =
      variant === "custom" && customGradients
        ? customGradients
        : gradientConfigs[variant as keyof typeof gradientConfigs]

    if (!gradients) return

    const createGradientLayer = () => {
      return gradients
        .map(
          (g) =>
            `radial-gradient(at ${g.x}% ${g.y}%, ${g.color} 0px, transparent 50%)`
        )
        .join(",")
    }

    const layer1 = createGradientLayer()
    const layer2 = createGradientLayer()

    container.style.backgroundImage = `${layer1}, ${layer2}`
    container.style.backgroundSize = "400% 400%, 400% 400%"

    if (!disableAnimations) {
      container.style.animation = "gradientAnimation 10s ease infinite"
    } else {
      container.style.backgroundPosition = "0% 0%, 100% 100%"
    }

    if (!disableAnimations) {
      const style = document.createElement("style")
      style.textContent = `
        @keyframes gradientAnimation {
          0%, 100% { background-position: 0% 0%, 100% 100%; }
          25% { background-position: 100% 0%, 0% 100%; }
          50% { background-position: 100% 100%, 0% 0%; }
          75% { background-position: 0% 100%, 100% 0%; }
        }
      `
      document.head.appendChild(style)

      return () => {
        document.head.removeChild(style)
      }
    }
  }, [variant, disableAnimations, customGradients])

  const containerVariants: Variants = {
    initial: { opacity: 0 },
    animate: {
      opacity: 1,
      transition: { duration: 0.6, ease: "easeInOut" }
    }
  }

  const content = (
    <>
      {grain && (
        <Image
          src="/grain.png"
          alt="grain"
          draggable={false}
          className={cn(
            "absolute inset-0 left-0 top-0 z-10 size-full scale-150 select-none object-cover opacity-50 mix-blend-soft-light",
            grainClassName
          )}
          width={1200}
          height={1200}
        />
      )}
      {srOnly && <span className="sr-only">{srOnly}</span>}
    </>
  )
  if (disableAnimations) {
    return (
      <div
        ref={containerRef}
        className={cn(
          "relative overflow-hidden",
          variant === "custom"
            ? backgroundVariants({
                variant: variant as ZyfloAnimatedBackgroundVariant,
                className
              })
            : className
        )}
        aria-label={label}
        {...(props as React.HTMLAttributes<HTMLDivElement>)}
      >
        {content}
        {children}
      </div>
    )
  }

  return (
    <motion.div
      ref={containerRef}
      className={cn(
        "relative overflow-hidden",
        variant === "custom"
          ? backgroundVariants({
              variant: variant as ZyfloAnimatedBackgroundVariant,
              className
            })
          : className
      )}
      variants={containerVariants}
      initial="initial"
      animate={triggerWhenInView ? undefined : "animate"}
      whileInView={triggerWhenInView ? "animate" : undefined}
      viewport={triggerWhenInView ? { once: true } : undefined}
      aria-label={label}
      {...props}
    >
      {content}
      {children}
    </motion.div>
  )
}

export default ZyfloAnimatedBackground

Usage

Here's a basic example of how to use the ZyfloAnimatedBackground component:

import ZyfloAnimatedBackground from "@/components/zyflo/animated-background"
export default function MyPage() {
    return (
        <ZyfloAnimatedBackground
            variant="blue-skies"
            className="w-full h-screen"
            grain={true}
        >
            {/* Your content goes here */}
        </ZyfloAnimatedBackground>
    )
}

Examples

Here are some examples of how to use the ZyfloAnimatedBackground component with different variants and customizations:

Purple Haze (Default)

Zyflo Window Preview
grain

Content goes here

Blue Skies

Zyflo Window Preview
grain

Content goes here

Pink Dreams

Zyflo Window Preview
grain

Content goes here

Ocean Breeze

Zyflo Window Preview
grain

Content goes here

Lavender Mist

Zyflo Window Preview
grain

Content goes here

Aqua Marine

Zyflo Window Preview
grain

Content goes here

Emerald Glow

Zyflo Window Preview
grain

Content goes here

Mint Chocolate

Zyflo Window Preview
grain

Content goes here

Pastel Paradise

Zyflo Window Preview
grain

Content goes here

Lime Light

Zyflo Window Preview
grain

Content goes here

Shadow Dance

Zyflo Window Preview
grain

Content goes here

Custom Gradient

Zyflo Window Preview
grain

Content goes here

Without Grain Effect

Zyflo Window Preview

Content goes here

With Disabled Animations

Zyflo Window Preview
grain

Content goes here

Props

Quick Props Overview

ZyfloAnimatedBackground Component Props
PropDescription
variantDefines the gradient style of the background
disableAnimationsIf true, disables all animations
triggerWhenInViewIf true, animations trigger when the component is in view
srOnlyText for screen readers only
labelAccessible label for the background
customGradientsCustom gradient configuration for the "custom" variant
grainIf true, adds a grain texture to the background
grainClassNameAdditional CSS classes for the grain texture
classNameAdditional CSS classes for the background container

Detailed Props Overview

variant

  • Type: ZyfloAnimatedBackgroundVariant
  • Default: "purple-haze"
  • Possible values: "purple-haze", "blue-skies", "pink-dreams", "ocean-breeze", "lavender-mist", "aqua-marine", "emerald-glow", "mint-chocolate", "pastel-paradise", "lime-light", "shadow-dance", "custom"

Defines the gradient style of the background.

disableAnimations

  • Type: boolean
  • Default: false

If set to true, disables all animations in the background.

triggerWhenInView

  • Type: boolean
  • Default: true

If set to true, the animation will be triggered when the background is in the viewport.

srOnly

  • Type: string
  • Optional

Adds a span with the sr-only class containing the provided text. This text will be invisible on screen but readable by screen readers.

label

  • Type: string
  • Optional

Adds an aria-label attribute to the background element. This provides an accessible name for the background, which can be useful for screen readers.

customGradients

  • Type: Array<{ x: number; y: number; color: string }>
  • Optional

Custom gradient configuration for the "custom" variant. Each object in the array defines a gradient point with its position (x, y) and color.

grain

  • Type: boolean
  • Default: false

If set to true, adds a grain texture overlay to the background.

grainClassName

  • Type: string
  • Optional

Additional CSS classes to be applied to the grain texture overlay.

className

  • Type: string
  • Optional

Additional CSS classes to be applied to the background container.

Customization

The component uses Tailwind CSS classes for styling. You can customize its appearance by modifying the CSS classes in the component's source code or by passing additional classes through the className prop.

Accessibility

The component is designed with accessibility in mind:

  • It uses semantic HTML elements for proper structure.
  • The srOnly prop allows you to provide additional context for screen readers without affecting the visual appearance.
  • The label prop provides a way to add an accessible name to the background, enhancing its description for screen readers.

Notes

  • The component uses Framer Motion for animations. Make sure you have Framer Motion installed in your project if you plan to use animations.
  • The grain effect is achieved using an image overlay. Ensure that the /grain.png image is available in your public directory.
  • Custom gradients are only applied when the variant is set to "custom".

Contributing

If you find any issues or have suggestions for improvements, please feel free to open an issue or submit a pull request on our GitHub repository. We appreciate your contributions and are always open to collaboration.

Thank you for considering contributing to Zyflo!