Portfólio 3D

Starter pack

Interface

Agora que temos a nossa cena 3D principalmente concluída, podemos começar a trabalhar na interface HTML.

Vamos criar um componente Interface.jsx com as mesmas 4 seções da nossa cena 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 as classes section--bottom, section--right e section--left para posicionar o conteúdo dentro das seções.

Por enquanto, apenas adicionamos os nomes das seções, adicionaremos o conteúdo mais tarde.

Vamos adicionar o nosso componente Interface no 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 nossa interface HTML, usaremos vanilla CSS para ser o mais genérico possível. Você pode usar seu framework CSS favorito, se preferir. (Utilizei TailwindCSS na maioria dos meus projetos)

Vamos adicionar os estilos padrão ao nosso arquivo 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 a fonte Roboto Slab do Google Fonts e definimos algumas variáveis de cores.

O contêiner das seções é centralizado e tem uma max-width de 1200px para manter uma boa legibilidade em telas grandes.

As seções têm uma height de 100vh (altura total) e são centralizadas por padrão. Usaremos as classes section--top, section--bottom, section--right e section--left para posicionar o conteúdo dentro das seções.

Nossa interface está pronta, vamos adicionar o conteúdo!

Indicador de rolagem na home

Na seção inicial, vamos adicionar um indicador de rolagem para informar ao usuário que ele pode rolar a página para ver as outras seções.

Primeiro, vamos criar um estado para saber se o usuário rolou a página:

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

Em seguida, na seção inicial, podemos adicionar um motion.div com um objeto variants para criar um indicador de rolagem animado:

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

export const Interface = () => {
  // ...
  return (
    <div className="interface">
      <div className="sections">
        {/* INÍCIO */}
        <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 a opacidade e a posição da roda. Para fazer a roda mover para cima e para baixo, usamos as propriedades repeat e repeatType.

End of lesson preview

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