Portafolio 3D

Starter pack

Interfaz

Ahora que tenemos nuestra escena 3D principalmente terminada, podemos empezar a trabajar en la interfaz HTML.

Vamos a crear un componente Interface.jsx con las mismas 4 secciones que nuestra escena 3D:

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>
  );
};

Usaremos las clases section--bottom, section--right y section--left para posicionar el contenido dentro de las secciones.

Por el momento, solo hemos añadido los nombres de las secciones, agregaremos el contenido más tarde.

Vamos a agregar nuestro componente Interface en el componente App:

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;

Para estilizar nuestra interfaz HTML, usaremos CSS puro para ser lo más genéricos posible. Puedes usar tu framework de CSS favorito si lo deseas. (Yo usé TailwindCSS en la mayoría de mis proyectos)

Vamos a agregar los estilos predeterminados a nuestro archivo index.css:

@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;
}

Importamos la fuente Roboto Slab de Google Fonts y definimos algunas variables de colores.

El contenedor de las secciones está centrado y tiene un max-width de 1200px para mantener una buena legibilidad en pantallas grandes.

Las secciones tienen una height de 100vh (altura completa) y están centradas por defecto. Usaremos las clases section--top, section--bottom, section--right y section--left para posicionar el contenido dentro de las secciones.

¡Nuestra interfaz está lista, vamos a agregar el contenido!

Indicador de desplazamiento en la página de inicio

En la sección de inicio, añadiremos un indicador de desplazamiento para decirle al usuario que puede desplazarse para ver las otras secciones.

Primero, vamos a crear un estado para saber si el usuario ha hecho scroll:

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);
  });
  // ...
};

Luego, en la sección de inicio, podemos agregar un motion.div con un objeto variants para crear un indicador de desplazamiento animado:

// ...
import { motion } from "framer-motion";

export const Interface = () => {
  // ...
  return (
    <div className="interface">
      <div className="sections">
        {/* INICIO */}
        <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>
  );
};

Estamos usando framer motion para animar la opacidad y la posición de la rueda. Para hacer que la rueda se mueva hacia arriba y hacia abajo, usamos las propiedades repeat y repeatType.

End of lesson preview

To get access to the entire lesson, you need to purchase the course.