javascripthtmlcssanimationhover

Hover Effects are treated as click effects, but they work properly after resizing page


They were working before, but I was trying to add react based page linking and that seemed to have broken the hover effects and now I cannot get them to function properly again. They do the correct hover animation on click, and work perfectly after I resize the page. It also doesn't display the "clickable" mouse pointer hand on hovering over them and another button on the page until I resize. What is the problem here?

EDIT: It seems to work on a new page in chrome but not in a tab with many other tabs open - thats when the resize hover thing is happening.

my app page:

import React from "react";
import Home from "./pages/Home";
import "./Styles/index.css";

const App: React.FC = () => {
 return (
   <div className="app">
     <Home />
   </div>
 );
};

export default App;

my Main page:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./Styles/index.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

my home page:

import React from "react";
import TopBar from "../components/TopBar";
import IntroSection from "../components/IntroSection";
import "../Styles/DefaultPage.css";

const Home: React.FC = () => {
  return (
    <div className="home-screen">
      <TopBar />
      <IntroSection />
    </div>
  );
};

export default Home;

my top bar:

import React, { useEffect, useState } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import "../Styles/TopBar.css";

gsap.registerPlugin(ScrollTrigger);

const TopBar: React.FC = () => {
  const [darkMode, setDarkMode] = useState(false);

  useEffect(() => {
    // Add button swing animation
    const navButtons = document.querySelectorAll(".nav-button");
    console.log("useEffect fired - styles should be applied now");
    navButtons.forEach((button) => {
      const btn = button as HTMLElement;

      btn.addEventListener("mouseenter", () => {
        btn.style.animation = "none"; // Remove existing animation
        btn.offsetHeight; // Trigger reflow
        btn.style.animation = "swing 2s ease-in-out forwards"; // Reapply animation
      });
      btn.style.display = "none";
      btn.offsetHeight; // Trigger reflow
      btn.style.display = ""; // Reset display
    });

    return () => {
      navButtons.forEach((button) => {
        const btn = button as HTMLElement;
        btn.removeEventListener("mouseenter", () => {});
      });
    };
  }, []);

  const toggleDarkMode = () => {
    setDarkMode((prevMode) => !prevMode);
    document.body.classList.toggle("dark-mode");
  };

  return (
    <div className="top-bar">
      <div className="nav-buttons">
        <button className="nav-button">HOME</button>
        <button className="nav-button">DEV & DESIGN</button>
        <button className="nav-button">ACTING</button>
        <button className="nav-button">FILMMAKING</button>
      </div>
      <div className="dark-mode-toggle-container">
        <button className="dark-mode-toggle" onClick={toggleDarkMode}>
          {darkMode ? "Light Mode" : "Dark Mode"}
        </button>
      </div>
    </div>
  );
};

export default TopBar;

the top bar CSS:

.top-bar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 60px;
  background-color: white;
  z-index: 10;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Navigation buttons container */
.nav-buttons {
  display: flex;
  padding-left: 20px;
  gap: 20px; /* Space between buttons */
  height: 100%;
  padding-right: 20px;
}

/* Individual navigation button styling */
.nav-button {
  background: none;
  border: none;
  font-family: inherit;
  font-size: 1rem;
  cursor: pointer;
  color: #333;
  padding: 10px;
  transform-origin: top left;
  transition: transform 0.1s ease, color 0.3s ease;
  align-items: center;
}

/* Color change on hover */
.nav-button:hover {
  animation: wiggle 0.5s ease-in-out; /* Use the same animation keyframes */
  color: #555;
}

/* Swing animation class to add animation on hover */
.swing-animation {
  animation: swing 2s ease-in-out forwards;
}

/* Dark mode toggle button styling */
.dark-mode-toggle {
  background: none;
  border: 2px solid #333;
  font-family: inherit;
  font-size: 1rem;
  cursor: pointer;
  padding: 8px 12px;
  border-radius: 4px;
  transition: background-color 0.3s ease, color 0.3s ease;
}
.dark-mode-toggle-container {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-right: 20px;
}

.dark-mode-toggle:hover {
  background-color: #333;
  color: #fff;
}

/* Dark mode styles */
body.dark-mode {
  background-color: #333;
  color: #fff;
}

body.dark-mode .top-bar {
  background-color: #000; /* Solid black for dark mode */
}

body.dark-mode .nav-button {
  color: #ccc;
}

body.dark-mode .dark-mode-toggle {
  color: #fff;
}
@keyframes wiggle {
  0% {
    transform: rotate(0deg);
  }
  25% {
    transform: rotate(3deg);
  }
  50% {
    transform: rotate(-3deg);
  }
  75% {
    transform: rotate(2deg);
  }
  100% {
    transform: rotate(0deg);
  }
}
/* Swinging animation */
@keyframes swing {
  0% {
    transform: rotate(0deg);
  }
  10% {
    transform: rotate(15deg);
  }
  20% {
    transform: rotate(-10deg);
  }
  30% {
    transform: rotate(7deg);
  }
  40% {
    transform: rotate(-5deg);
  }
  50% {
    transform: rotate(3deg);
  }
  60% {
    transform: rotate(-2deg);
  }
  70% {
    transform: rotate(1deg);
  }
  80% {
    transform: rotate(-0.5deg);
  }
  90% {
    transform: rotate(0.2deg);
  }
  100% {
    transform: rotate(0deg);
  }
}

my current test build of the website:https://mattekranz.com/


Solution

  • The issue seems to be a combination of React's rendering quirks and animation handling. Thus, I'd try a combination of solutions:

    1. You're using inline styles for animations in JavaScript, which can interfere with CSS hover effects. Instead, use CSS classes:

      btn.addEventListener("mouseenter", () => {
        btn.classList.remove("swing-animation");
        void btn.offsetWidth; // Reflow to restart animation
        btn.classList.add("swing-animation");
      });
      btn.addEventListener("mouseleave", () => btn.classList.remove("swing-animation"));
      
    2. The buttons work after resizing because the browser recalculates layout. Id reflow how React handles these operations. Force this in useEffect:

      useEffect(() => {
        const navButtons = document.querySelectorAll(".nav-button");
        navButtons.forEach((btn) => btn.offsetHeight); // reflow
      }, []);
      
    3. If it works better on fewer tabs, the browser may be throttling resources. Add hardware acceleration:

      * {
        will-change: transform, opacity;
      }
      

    Obv, if the pointer cursor is missing, explicitly add it in CSS:

    .nav-button {
      cursor: pointer !important;
    }
    

    More generally, I'd run animations without GSAP to isolate the issue