- CTRL ALT News
- Posts
- Create a 3D MOON KNIGHT Landing Page using React, Three.js & Framer Motion [Source Code]
Create a 3D MOON KNIGHT Landing Page using React, Three.js & Framer Motion [Source Code]
In this guide, I will show you how to create a beautiful 3D landing page with a Moon Kight theme using React, Three.js and Framer Motion.

Grab the source code here
Download the 3D model here (Thank you to @rdcanime for this amazing model)
Setting Up the Project
The very first thing we need to do is set up our React project. Simply start by opening any console of your choice. After opening the console type the following command:
npm create vite@latest my-react-app --template react-tsYou will then be asked to select a few options for the project, you need to choose the following:
√ Select a framework: » React
√ Select a variant: » TypeScriptYour React project is now set up, but don’t close the console just yet.
TailwindCSS
This part can also be found in the original TailwindCSS docs, if you prefer it can be read here.
TailwindCSS is not set up by default when creating our project like this, so we need to do this ourselves. The first thing we need to do is CD’ing our console into our new React folder. If you haven’t already closed your console, you can type the following command:
> cd my-react-appIf you did close the console, simply open it up inside the React app.
Now we need to install the packages needed for TailwindCSS, simply type the following command:
> npm install -D tailwindcss postcss autoprefixer
> npx tailwindcss init -pYou should now have all the packages needed and a “tailwind.config.js” file in your React app.
After that you need to configure the template paths, this can be done inside your “tailwind.config.js” file. It has to look like this:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}The last thing we need to do is add our TailwindCSS directives to our CSS. Simply open your “index.css” file, and make sure it looks like this:
@tailwind base;
@tailwind components;
@tailwind utilities;TailwindCSS is now set up!
Installing the Packages
We need to install the rest of the packages, simply run one of the following commands depending on your package manager:
Getting the 3D Model
We need to download the 3D model separately and place it in our project. The model can be downloaded here: https://sketchfab.com/3d-models/moon-knight-17dea3bfa9fc41f9bf61cfdc7fc63abb#download (Thank you to @rdcanime for this amazing model), make sure to download the “gltf” format.
Inside our react “public” folder create a new folder called 3dModel, and place the downloaded files in here.
It is important to note that if you are building an application intended for real use, you should not have the model in your public folder. In this case, it is better to save it somewhere like an AWS S3 bucket, but for learning purposes, it is fine to leave it in the public folder.
Background Image
We also need a background image, the specific image used in this project can be found inside the source code linked at the top of this guide. The image should be called “bg.webp”, and placed inside the public folder. You can of course use your own image.
Creating the Webpage
Moon Knight Component
We need to create a single component for this page, it is tasked with managing our 3D Moon Knight model.
In your “src” folder, create a new folder called “Components”. Inside the components folder create a single file, call it “MoonKnight.tsx”. The code for the component looks as follows:
import { useState } from 'react'
import { useRef } from "react";
import { Mesh } from "three";
import * as THREE from 'three';
import { useGLTF } from '@react-three/drei';
import { useMotionValueEvent, useScroll, useSpring } from 'framer-motion';
const MoonKnight = () => {
const mesh = useRef<Mesh>(null!);
const { scene } = useGLTF("3dModel/scene.gltf")
const { scrollYProgress } = useScroll();
const scrollY = useSpring(scrollYProgress, { stiffness: 100, damping: 50 })
const [meshScale, setMeshScale] = useState<number>(5)
useMotionValueEvent(scrollY , "change", (latest) => {
const a = new THREE.Vector3( 0, 1, 0 );
mesh.current.setRotationFromAxisAngle(a, latest * 3.125)
setMeshScale(5 - (latest * 0.5))
})
return (
<mesh ref={mesh} scale={[-meshScale, meshScale, -meshScale]} position={[0, -7, 0]}>
<primitive object={scene}/>
</mesh>
)
}
export default MoonKnightIn the component, we import the 3D model inside our public/3dModel folder. We also keep track of the page scroll and update the scale and rotation of our model when the page is scrolled. Using Framer Motions “useSpring” hook we can make the model scale and rotate smoothly.
The Page
All the code for the page looks like this:
import { Canvas } from "@react-three/fiber";
import MoonKnight from "./Components/MoonKnight";
import { motion } from "framer-motion";
import { Parallax } from "react-scroll-parallax";
function App() {
return (
<div className="h-fit w-full bg-black relative flex flex-col scroll-smooth bg-[url('/bg.webp')] bg-bottom bg-no-repeat bg-cover ">
<div className="w-full h-screen flex items-end justify-center fixed z-10">
<div className="w-full h-full ">
<Canvas camera={{ fov: 75, near: 0.1, far: 1000, position: [0, 0, 5]}}>
<spotLight position={[-5, 0, -1]} intensity={Math.PI * 10} color="white" />
<spotLight position={[5, 0, -1]} intensity={Math.PI * 25} color="#FFFF00 " />
<spotLight position={[0, -5, -1]} intensity={Math.PI * 5} color="white" />
<hemisphereLight intensity={.25} />
<ambientLight intensity={.25} />
<MoonKnight />
</Canvas>
</div>
</div>
<motion.div className="w-full h-screen flex items-center justify-start flex-col gap-0"
initial={{ opacity: 0, y: -500 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 2 }}
>
<div className="flex">
<p className="text-[6rem] md:text-[10rem] lg:text-[15rem] xl:text-[20rem] font-bold text-white leading-[0.9]">MOON</p>
<p style={{ WebkitTextStroke: "white 2.5px" }} className="text-[6rem] md:text-[10rem] lg:text-[15rem] xl:text-[20rem] font-bold text-transparent leading-[0.9] absolute z-20">MOON</p>
</div>
<div className="flex">
<p className="text-[6rem] md:text-[10rem] lg:text-[15rem] xl:text-[20rem] font-bold text-white leading-[0.9]">KNIGHT</p>
<p style={{ WebkitTextStroke: "white 2.5px" }} className="text-[6rem] md:text-[10rem] lg:text-[15rem] xl:text-[20rem] font-bold leading-[0.9] absolute z-20 text-transparent">KNIGHT</p>
</div>
</motion.div>
<div className="w-full h-screen lg:p-48 md:p-24 p-12 overflow-x-hidden">
<Parallax speed={5}>
<motion.div className="w-full h-1/3 flex flex-col justify-center gap-4" initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ duration: 0.5 }}
variants={{
visible: { opacity: 1, x: 0 },
hidden: { opacity: 0, x: -200 }
}}
>
<h2 className="font-bold text-white text-7xl">Fist of Khonshu</h2>
<p className="max-w-80 text-white">As the avatar of the Egyptian moon god, Moon Knight doles out brutal nighttime justice.</p>
</motion.div>
</Parallax>
<Parallax speed={15}>
<motion.div className="w-full h-1/3 flex flex-col justify-center items-end text-right gap-4"
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ duration: 0.5 }}
variants={{
visible: { opacity: 1, x: 0 },
hidden: { opacity: 0, x: 200 },
exit: { opacity: 0, x: 200 }
}}
>
<h2 className="font-bold text-white text-7xl">Crescent Darts</h2>
<p className="max-w-80 text-white">He throws crescent-shaped darts and boomerangs reminiscent of the moon's shape.</p>
</motion.div>
</Parallax>
<Parallax speed={20}>
<motion.div className="w-full h-1/3 flex flex-col justify-center gap-4" initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ duration: 0.5 }}
variants={{
visible: { opacity: 1, x: 0 },
hidden: { opacity: 0, x: -200 }
}}
>
<h2 className="font-bold text-white text-7xl">White Cape</h2>
<p className="max-w-80 text-white">Moon Knight stalks criminals from the shadows dressed in an all-white costume with a sweeping cloak.</p>
</motion.div>
</Parallax>
</div>
<div className="w-full h-screen ">
<motion.div className="w-full h-full flex items-center flex-col" initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ duration: 0.5 }}
variants={{
visible: { opacity: 1, x: 0 },
hidden: { opacity: 0, x: -200 }
}}
>
<p className="font-bold text-white md:text-[10rem] text-7xl leading-[0.9] text-center mt-8">Watch Today</p>
<p style={{ WebkitTextStroke: "white 2.5px" }} className="md:text-[10rem] text-7xl font-bold text-center absolute z-20 text-transparent leading-[0.9] mt-8">Watch Today</p>
</motion.div>
</div>
</div>
)
}
export default AppNow what does this code do? Let me give you a quick breakdown.
First, we create the div that keeps all items inside it, this is also where we set a background image using TailwindCSS.
Next, we define the canvas using react-three-fiber, this is where everything regarding 3D is located. Inside the canvas, we create the lighting and import our MoonKnight component.
Next, we create the title section. We overlay the text on top of itself using position absolute and give the upper text a white outline. This makes the text readable even though the 3D model is covering the fully white text. The text also fades in when the page first loads, this is done using Framer Motions motion divs.
Next, we create the 3 pieces of text describing Moon Knight. These pieces fade in when you scroll to them, this is also achieved using Framer Motions motion divs. They also have a parallax effect using react-scroll-parallax, which makes the different pieces of text scroll at different speeds.
Last we define the “Watch Today” text. This text also fades in on scroll. It also has the same stroke effect as the title, so the text can be seen on top of the 3D model.
Viewing the Page
You should now be able to view the page by running the following command:
npm run dev