Fundamentals
Core
Master
Shaders
3D Portfolio
Interface
Now that we have our 3D scene mainly done, we can start working on the HTML interface.
Let's create an Interface.jsx
component with the same 4 sections as our 3D scene:
export const Interface = () => { return ( <div className="interface"> <div className="sections"> {/* HOME */} <section className="section section--bottom">HOME</section> {/* SKILLS */} <section className="section section--right">SKILLS</section> {/* PROJECTS */} <section className="section section--left">PROJECTS</section> {/* CONTACT */} <section className="section section--left">CONTACT</section> </div> </div> ); };
We will use the section--bottom
, section--right
and section--left
classes to position the content inside the sections.
For the moment we only added the sections names, we will add the content later.
Let's add our Interface
component into the App
component:
import { Scroll, ScrollControls } from "@react-three/drei"; import { Canvas } from "@react-three/fiber"; import { MotionConfig } from "framer-motion"; import { Experience } from "./components/Experience"; import { config } from "./config"; import { Interface } from "./components/Interface"; function App() { return ( <> <Canvas camera={{ position: [0, 0.5, 5], fov: 42 }}> <color attach="background" args={["#f5f3ee"]} /> <fog attach="fog" args={["#f5f3ee", 10, 50]} /> <ScrollControls pages={config.sections.length} damping={0.1} maxSpeed={0.2} > <group position-y={-1}> <MotionConfig transition={{ duration: 0.6, }} > <Experience /> </MotionConfig> </group> <Scroll html> <Interface /> </Scroll> </ScrollControls> </Canvas> </> ); } export default App;
To style our HTML interface, we will use vanilla CSS to be the more generic possible. You can use your favorite CSS framework if you want. (I used TailwindCSS on most of my projects)
Let's add the default styles to our index.css
file:
@import url("https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400;700&display=swap"); :root { --primary-color: #4668ee; --text-color: #1a202c; --text-light-color: #555; } #root { width: 100vw; height: 100vh; } body { margin: 0; font-family: "Roboto Slab", serif; } /* ... */ .interface { width: 100vw; display: flex; flex-direction: column; align-items: center; } .sections { max-width: 1200px; width: 100%; } .section { height: 100vh; display: flex; justify-content: center; align-items: center; } .section--top { align-items: flex-start; } .section--bottom { align-items: flex-end; } .section--right { justify-content: flex-end; } .section--left { justify-content: flex-start; }
We imported the Roboto Slab font from Google Fonts and defined some colors variables.
The sections container is centered and has a max-width
of 1200px
to keep a good readability on large screens.
The sections have a height
of 100vh
(full height) and are centered by default. We will use the section--top
, section--bottom
, section--right
and section--left
classes to position the content inside the sections.
Our interface is ready, let's add the content!
Home scroll indicator
On the home section, we will add a scroll indicator to tell the user that he can scroll to see the other sections.
First, let's create a state to know if the user has scrolled:
import { useScroll } from "@react-three/drei"; import { useFrame } from "@react-three/fiber"; import { useState } from "react"; export const Interface = () => { const scrollData = useScroll(); const [hasScrolled, setHasScrolled] = useState(false); useFrame(() => { setHasScrolled(scrollData.offset > 0); }); // ... };
Then in the home section, we can add a motion.div
with a variants
object to create an animated scroll indicator:
// ... import { motion } from "framer-motion"; export const Interface = () => { // ... return ( <div className="interface"> <div className="sections"> {/* HOME */} <section className="section section--bottom"> <motion.div className="scroll-down" initial={{ opacity: 0, }} animate={{ opacity: hasScrolled ? 0 : 1, }} > <motion.div className="scroll-down__wheel" initial={{ translateY: 0, }} animate={{ translateY: 4, }} transition={{ duration: 0.4, repeatDelay: 0.5, repeatType: "reverse", repeat: Infinity, }} ></motion.div> </motion.div> </section> {/* ... */} </div> </div> ); };
We are using framer motion to animate the opacity and the wheel position. To make the wheel move up and down, we use the repeat
and repeatType
properties.
End of lesson preview
To get access to the entire lesson, you need to purchase the course.