React-Portfolio/smooth-scroll-implementation-plan.md

11 KiB

Revised Smooth Scrolling Implementation Plan for React Portfolio

Overview

Based on the issues encountered with the previous implementation, this revised plan takes a simpler but more reliable approach to create a smooth scrolling portfolio with perfectly contained full-page sections. We'll implement a fullpage.js-inspired solution with proper section sizing, positioning, and transitions.

Technologies

  • React Fullpage: A simplified custom implementation inspired by fullpage.js
  • React Intersection Observer: To detect when sections enter/exit the viewport
  • Framer Motion: For smooth animations and transitions

Implementation Steps

1. Setup Dependencies

npm install framer-motion react-intersection-observer

2. Create FullPage Component

Create src/components/FullPage.js

import React, { useState, useEffect, useRef } from 'react';
import { motion } from 'framer-motion';

const FullPage = ({ children }) => {
  const [currentPage, setCurrentPage] = useState(0);
  const [isScrolling, setIsScrolling] = useState(false);
  const containerRef = useRef(null);
  const pagesCount = React.Children.count(children);
  
  // Handle page change
  const goToPage = (pageIndex) => {
    if (pageIndex >= 0 && pageIndex < pagesCount && !isScrolling) {
      setIsScrolling(true);
      setCurrentPage(pageIndex);
      
      // Get the corresponding section ID to update URL
      const sectionId = React.Children.toArray(children)[pageIndex].props.id;
      window.history.replaceState(null, null, `#${sectionId}`);
      
      setTimeout(() => {
        setIsScrolling(false);
      }, 1000); // Match this with your transition duration
    }
  };
  
  // Handle wheel events
  const handleWheel = (e) => {
    if (isScrolling) return;
    
    if (e.deltaY > 0) {
      // Scrolling down
      goToPage(currentPage + 1);
    } else {
      // Scrolling up
      goToPage(currentPage - 1);
    }
  };
  
  // Handle key events
  const handleKeyDown = (e) => {
    if (isScrolling) return;
    
    if (e.key === 'ArrowDown' || e.key === 'PageDown') {
      goToPage(currentPage + 1);
    } else if (e.key === 'ArrowUp' || e.key === 'PageUp') {
      goToPage(currentPage - 1);
    }
  };
  
  // Initialize event listeners
  useEffect(() => {
    const container = containerRef.current;
    if (container) {
      container.addEventListener('wheel', handleWheel, { passive: false });
      window.addEventListener('keydown', handleKeyDown);
      
      return () => {
        container.removeEventListener('wheel', handleWheel);
        window.removeEventListener('keydown', handleKeyDown);
      };
    }
  }, [currentPage, isScrolling]);

  // Check for hash in URL on initial load
  useEffect(() => {
    const hash = window.location.hash.substring(1);
    if (hash) {
      const childrenArray = React.Children.toArray(children);
      const initialPageIndex = childrenArray.findIndex(child => child.props.id === hash);
      if (initialPageIndex !== -1) {
        setCurrentPage(initialPageIndex);
      }
    }
  }, []);
  
  return (
    <div
      ref={containerRef}
      className="fullpage-container"
      style={{ overflow: 'hidden', height: '100vh', position: 'relative' }}
    >
      <div
        className="fullpage-sections"
        style={{
          height: '100%',
          width: '100%',
          transition: 'transform 1s cubic-bezier(0.645, 0.045, 0.355, 1.000)',
          transform: `translateY(-${currentPage * 100}vh)`
        }}
      >
        {React.Children.map(children, (child, index) => (
          <div 
            key={index} 
            className="fullpage-section"
            style={{ height: '100vh', width: '100%' }}
          >
            {child}
          </div>
        ))}
      </div>
      
      {/* Pagination Dots */}
      <div className="fullpage-pagination">
        {Array.from({ length: pagesCount }).map((_, index) => (
          <motion.div
            key={index}
            className={`pagination-dot ${currentPage === index ? 'active' : ''}`}
            initial={{ opacity: 0.5, scale: 0.8 }}
            animate={{
              opacity: currentPage === index ? 1 : 0.5,
              scale: currentPage === index ? 1 : 0.8
            }}
            onClick={() => goToPage(index)}
          />
        ))}
      </div>
    </div>
  );
};

export default FullPage;

3. Create Section Component

Create src/components/Section.js

import React from 'react';
import { motion } from 'framer-motion';

const Section = ({ children, id, className = '' }) => {
  return (
    <motion.section
      id={id}
      className={`fullpage-section-content ${className}`}
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={{ duration: 0.5 }}
      style={{
        height: '100%',
        width: '100%',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        position: 'relative'
      }}
    >
      {children}
    </motion.section>
  );
};

export default Section;

4. Update Navigation Component

Enhance Header Component

Update the header to work with our new FullPage component by handling section navigation.

// In src/header/index.js

import React, { useState, useContext } from 'react';
import "./style.css";
import { RiMenuLine, RiCloseLine } from "react-icons/ri";
import { logotext } from "../content_option";
import Themetoggle from "../components/themetoggle";
import { Socialicons } from "../components/socialicons";

const Headermain = () => {
  const [isActive, setActive] = useState("false");

  const handleToggle = () => {
    setActive(!isActive);
    document.body.classList.toggle("ovhidden");
  };

  const handleNavClick = (e, targetId) => {
    e.preventDefault();
    handleToggle();
    
    // Find all section elements
    const sections = document.querySelectorAll('.fullpage-section');
    const targetSection = Array.from(sections).findIndex(
      section => section.querySelector(`#${targetId}`) !== null
    );
    
    // Trigger a custom event that FullPage component will listen for
    if (targetSection !== -1) {
      const customEvent = new CustomEvent('navigateTo', {
        detail: { index: targetSection }
      });
      document.dispatchEvent(customEvent);
    }
  };
  
  // The rest of the component remains the same
}

5. Update App.js

Rewrite App.js to use our new FullPage and Section components.

import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import AnimatedCursor from "../hooks/AnimatedCursor";
import Headermain from "../header";
import { Home } from "../pages/home";
import { About } from "../pages/about";
import { Portfolio } from "../pages/portfolio";
import { ContactUs } from "../pages/contact";
import { Socialicons } from "../components/socialicons";
import FullPage from "../components/FullPage";
import Section from "../components/Section";
import "./App.css";

export default function App() {
  return (
    <div className="app-container">
      <div className="cursor__dot">
        <AnimatedCursor
          innerSize={15}
          outerSize={15}
          color="255, 255 ,255"
          outerAlpha={0.4}
          innerScale={0.7}
          outerScale={5}
        />
      </div>
      <Headermain />
      <FullPage>
        <Section id="home">
          <Home />
        </Section>
        <Section id="about">
          <About />
        </Section>
        <Section id="portfolio">
          <Portfolio />
        </Section>
        <Section id="contact">
          <ContactUs />
        </Section>
      </FullPage>
      <Socialicons />
    </div>
  );
}

6. Add Necessary CSS Styles

Update src/app/App.css

/* Reset styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  overflow: hidden;
  position: fixed;
  height: 100%;
  width: 100%;
}

/* Full page styles */
.app-container {
  position: relative;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

.fullpage-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

.fullpage-section {
  position: relative;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

.fullpage-section-content {
  padding-top: 60px; /* Account for header */
  width: 100%;
  height: 100%;
}

/* Navigation dots */
.fullpage-pagination {
  position: fixed;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.pagination-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: var(--text-color, #fff);
  cursor: pointer;
  transition: all 0.3s ease;
}

.pagination-dot.active {
  transform: scale(1.2);
}

/* Make sure header is above fullpage */
.site__header {
  z-index: 1000;
}

/* Social icons positioning */
.stick_follow_icon {
  position: fixed;
  bottom: 0;
  left: 0;
  z-index: 1000;
}

/* Responsive styles */
@media (max-width: 768px) {
  .fullpage-pagination {
    right: 10px;
    gap: 10px;
  }
  
  .pagination-dot {
    width: 8px;
    height: 8px;
  }
  
  .fullpage-section-content {
    padding-top: 50px;
  }
}

7. Modify FullPage Component to Listen for Navigation Events

Update the FullPage component to listen for custom navigation events from the header.

// Add this useEffect in the FullPage component

useEffect(() => {
  const handleNavigation = (e) => {
    const { index } = e.detail;
    goToPage(index);
  };
  
  document.addEventListener('navigateTo', handleNavigation);
  
  return () => {
    document.removeEventListener('navigateTo', handleNavigation);
  };
}, []);

Implementation Timeline

  1. Setup (30 minutes):

    • Install dependencies
    • Create necessary files
  2. Core Components (2 hours):

    • Implement FullPage component
    • Implement Section component
    • Setup basic navigation
  3. Styling and Refinement (1.5 hours):

    • Implement CSS styles
    • Add pagination dots
    • Ensure responsive behavior
  4. Navigation Integration (1 hour):

    • Connect header navigation with fullpage scrolling
    • Implement URL hash updates
  5. Testing and Debugging (1 hour):

    • Test on different browsers
    • Ensure smooth scrolling on different devices

Expected Results

  • Clean, full-viewport sections that are perfectly contained
  • Smooth transitions between sections
  • Navigation dots for quick section access
  • Header links that correctly navigate to sections
  • URL hash updates for direct section access
  • Keyboard navigation support
  • Responsive design that works on all devices

Future Enhancements

  • Add touch swipe support for mobile devices
  • Implement section-specific animations
  • Add scroll progress indicator
  • Create horizontal sliding sections for portfolio items
  • Implement background parallax effects