UT Hero Section
The UT Hero section with sticky text and a parallax background
Installation
1
Install dependencies
npm install framer-motion tailwind-merge clsx
2
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
3
Copy the source code
components/ui/sticky-hero.tsx
'use client';
import React, { ReactNode, useRef, useState } from 'react'
import Image from 'next/image';
interface StickyHeroProps {
heroText: ReactNode;
srcHero: string;
mediaType: string;
srcBelow: string;
children?: ReactNode;
}
export function StickyHero({
heroText,
srcHero,
mediaType,
srcBelow,
children
}: StickyHeroProps) {
const [isPlaying, setIsPlaying] = useState<boolean>(true);
const videoRef = useRef<HTMLVideoElement | null>(null);
const togglePlay = () => {
if (mediaType === 'video' && videoRef.current) {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
setIsPlaying(!isPlaying);
}
};
return (
<div>
<div className='absolute h-screen flex items-center justify-start'>
{mediaType === 'video' ? (
<video
ref={videoRef}
className="h-full w-full object-cover"
autoPlay
muted
loop
playsInline
src={srcHero}
/>
) : (
<Image
className="h-full w-full object-cover"
width={1920}
height={1080}
src={srcHero}
alt="Hero background"
priority
/>
)}
{!isPlaying && (
<div className="absolute inset-0 bg-gradient-to-r from-black/40 to-transparent" />
)}
{mediaType === 'video' && (
<div className="absolute bottom-14 right-5 p-4 z-50">
<button
onClick={togglePlay}
className="rounded-full bg-white bg-opacity-40 p-3 hover:bg-opacity-60 transition-all cursor-pointer"
aria-label={isPlaying ? 'Pause' : 'Play'}
>
{isPlaying ? (
<svg className="w-8 h-8 text-black" viewBox="0 0 24 24" fill="currentColor">
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
</svg>
) : (
<svg className="w-8 h-8 text-black" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z" />
</svg>
)}
</button>
</div>
)}
</div>
<div className='relative h-[190vh]'>
<div className="sticky z-50 top-[30%] w-full flex justify-center mb-[5vh]">
<h1
className='z-50 text-left sm:text-[5rem] text-[3rem] max-w-6xl w-full leading-[0.8] font-black text-white mx-[2%]'
style={{ textShadow: '1px 1px 1px rgba(51, 63, 72, 0.4)' }}
>
{heroText}
</h1>
</div>
<Image
className="z-[-2] fixed left-0 right-0 bottom-0 h-screen object-cover"
width={1920}
height={1080}
src={srcBelow}
alt="Hero background"
priority
/>
<div className="absolute bottom-0 inset-x-0 h-screen bg-gradient-to-t from-[#fd7e14]/70 to-transparent" />
</div>
<div>
{children}
</div>
</div>
)
}
Props
Prop | Type | Description | Default Value |
---|---|---|---|
heroText | ReactNode | Main text for the hero | undefined |
srcHero | string | Source path or link to the image or video | undefined |
mediaType | string | "video" or "image" for the srcHero prop | undefined |
srcBelow | string | Source path or link to the image below the main image or video | undefined |
children | ReactNode | Components that will go under the heroText after its stickiness ends | undefined |