Back to Gallery
Prev

A Responsive Website

next

July 17th, 2024

The Project

The goal of this project was to create a website where visitors can explore some of the major projects I've worked on. Beyond serving as a web portfolio, I aimed to make the website itself a noteworthy project, showcasing my frontend development capabilities through various innovative elements.

My Role

I developed this project entirely by hand, coding every part of the website in HTML, CSS, and JavaScript without using any website creation services.

3D Element

The most interesting part of this website is on the landing page, where an interactive 3D model of myself appears behind the main title. This model was created using a Gaussian splatting scan, a process that involves taking multiple photos and generating a model with software like Polycam.

To embed the model into the website, I used the JavaScript library three.js. This required several steps:

  1. Creating a scene to place the model.

  2. Setting up a camera to view the model within the scene.

  3. Ensuring the renderer for the scene adjusted to the size of the container holding it.


            import * as THREE from "three";
            import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
            
            // Get container that will hold 3D render
            var myContainer = document.getElementById("container3D");
            
            // Set up the required elements for the renderer
            const scene = new THREE.Scene();
            const camera = new THREE.PerspectiveCamera(
              75,
              window.innerWidth / window.innerHeight,
              0.1,
              1000
            );
            
            // Declare 3D object
            let object;
            
            // Set initial mouse positions
            let mouseX = window.innerWidth / 2;
            let mouseY = window.innerHeight / 2;
            
            // Declare loader for .glb 3D files
            const loader = new GLTFLoader();
            
            // Load 3D file
            loader.load(
              "self_portrait.glb",
              function (gltf) {
                object = gltf.scene;
                scene.add(object);
                // Adjusting object position in container
                object.position.y = -0.8;
                object.position.x = -0.43;
              },
              // If failure send to console
              undefined,
              function (error) {
                console.error(error);
              }
            );
            
            // Declare renderer
            const renderer = new THREE.WebGLRenderer({
              alpha: true, // Set background to transparent
              antialias: true, // Makes edges smooth
            });
            
            // Set size of renderer to the container width and height
            renderer.setSize(myContainer.clientWidth * 1.5, myContainer.clientHeight);
            // renderer.setSize(window.innerWidth, window.innerHeight);
            
            // Add renderer to container
            document.getElementById("container3D").appendChild(renderer.domElement);
            
            // Distance from camera to subject
            camera.position.z = 1;
            
            // Light settings, ambient light gives equal lighting
            const light = new THREE.AmbientLight(0xffffff); // soft white light
            scene.add(light);

What makes this element particularly engaging is that it follows mouse movements. This was achieved by tracking mouse movement in the script and rotating the model accordingly, using events and functions.


            function animate() {
            requestAnimationFrame(animate);
          
            // Horizontal movement
            object.rotation.y = -2.9 + (mouseX / window.innerWidth) * 3;
          
            // Verticle movement, with lower stop
            if ((mouseY * 0.5) / window.innerHeight >= 1.4) {
              object.rotation.x = 1.4;
            } else {
              object.rotation.x = (mouseY * 0.5) / window.innerHeight;
            }
          
            // Render frame
            renderer.render(scene, camera);
          }
          
          // Event for mouse movement
          document.onmousemove = (e) => {
            mouseX = e.clientX;
            mouseY = e.clientY;
          };

Animated Features

One prominent feature on both the main page and the portfolio gallery page is the flip animation on the project cards. This was implemented using primarily CSS and HTML:

  • The HTML specifies the content for the front and back of the cards.

  • In CSS, the cards have a class that rotates them 180°, using an attribute called preserve-3d. Here is a snippet of the styling for the cards:

    
                    .project {
                      display: flex;
                      height: 25rem;
                      width: 100%;
                    
                      position: relative;
                      transition: 0.9s;
                      transform-style: preserve-3d;
                    }
    
                    .flip {
                      transform: rotateY(180deg);
                    }
                    
                    .front,
                    .back {
                      position: absolute;
                      top: 0;
                      left: 0;
                      width: 100%;
                      height: 100%;
                      color: #ffffff;
                      text-align: center;
                      backface-visibility: hidden;
                      transition: 0.9s;
                      transform-style: preserve-3d;
                    }
                  

In JavaScript, an event listener on the button adds the flip class to the card when clicked. The code is streamlined so that instead of each card needing its own event listener, the function identifies the button number and matches it to the corresponding card.


            document.addEventListener("DOMContentLoaded", function () {
              // Select all buttons
              const buttons = document.querySelectorAll(".card-btn");
            
              // Add click event listener to each button
              buttons.forEach((button) => {
                button.addEventListener("click", function () {
                  // Get the associated project using the data attribute
                  const projectNumber = this.getAttribute("data-btn");
                  const project = document.querySelector(
                    `.project[data-project="${projectNumber}"]`
                  );
            
                  // Toggle the 'flip' class on the associated project
                  project.classList.toggle("flip");
                });
              });
            });
          

Final Product

Everything here is the final product.