Replicate Domain Expansion Effect on React Typescript

So i successfully replicate loader effect that similar like Gojo Domain on site https://jujutsukaisen.jp/ to HTML CSS & JS. take a look here: https://codepen.io/Lietreun/pen/zYVamgM.

then i try to replicate it into typescript, the effect is not quite the same, so it like rendered the black animation => blue bg => closed. While the HTML black animation => blue bg => black animation for closing.

i am quite new to web programming and i already try solve it for hours and heat my head up so i give up and asking for help so Please anyone help me XD thank you so much therefore.

NB: Can you make more good the domain expansion effect? its really cool and fancy and JJK is literally hyped me up lately as i really love the anime now. So i kinda wanted to see more hehe.

import React, { useEffect } from 'react';
import $ from 'jquery';
import '../index.css';

const Loader: React.FC = () => {
  useEffect(() => {
    const firstAnimation = () => {
      const ww = $(window).innerWidth();
      if (ww > 768) {
        $("html,body").animate({ scrollTop: 0 }, 1, function() {
          $("#cMark").addClass("active");
          $("#cMark.active").on('webkittransitionend transitionend', function() {
            $("#topLoading, #topWrap").addClass("active");
            $("#topLoading.active").on('webkitAnimationEnd animationend', function() {
              $("#topLoading").remove();
              $("#blueBg").addClass("active");
              $("#blueBg.active").on('webkitAnimationEnd animationend', function() {
                $("#blueBg").remove();
              });
            });
          });
        });
      } else {
        $("#blueBg").remove();
        $("html,body").animate({ scrollTop: 0 }, 1, function() {
          $("#cMark").addClass("active");
          $("#cMark.active").on('webkittransitionend transitionend', function() {
            $("#topLoading, #topWrap").addClass("active");
            $("#topLoading.active").on('webkitAnimationEnd animationend', function() {
              $("#topLoading").remove();
            });
          });
        });
      }
    };

    // Run the animation on component mount
    firstAnimation();

    // Cleanup function to remove event handlers
    return () => {
      $("#cMark").off('webkittransitionend transitionend');
      $("#topLoading.active").off('webkitAnimationEnd animationend');
      $("#blueBg.active").off('webkitAnimationEnd animationend');
    };
  }, []);

  return (
    <>
      <div id="topLoading">
        <div className="topLoading__mask"></div>
      </div>
      <div id="cMark">
        <span>Loading...</span>
        <i></i>
      </div>
      <div id="blueBg">
        <div className="blueBg__mask"></div>
      </div>
      <div id="topWrap">
        <h1>Welcome to the Site</h1>
        <p>SUKUNAAA</p>
      </div>
    </>
  );
};

export default Loader;

I already tried Googling but hadnt found any match keyword so it kinda stuck at place, i tried GPT the result is not quite as i wanted because the effect not exactly same as the original.

TypeScript

import React, { useState, useEffect } from 'react';
import styles from './LoadingMask.module.css'; // Use CSS-in-JS

interface LoadingMaskProps {
  isActive: boolean;
  onAnimationEnd: () => void;
}

const LoadingMask: React.FC<LoadingMaskProps> = ({ isActive, onAnimationEnd }) => {
  const [isAnimating, setIsAnimating] = useState(false);

  useEffect(() => {
    if (isActive) {
      setIsAnimating(true);
    }
  }, [isActive]);

  useEffect(() => {
    if (isAnimating) {
      const handleAnimationEnd = () => {
        setIsAnimating(false);
        onAnimationEnd(); // Call callback to trigger next animation
      };

      const animationElement = document.getElementById('loading-mask');
      animationElement.addEventListener('animationend', handleAnimationEnd);

      return () => animationElement.removeEventListener('animationend', handleAnimationEnd);
    }
  }, [isAnimating, onAnimationEnd]);

  return <div className={`${styles.loadingMask} ${isAnimating ? styles.active : ''}`} id="loading-mask"></div>;
};

export default LoadingMask;

BlueBackground.tsx: (Similar structure to LoadingMask.tsx )

ContentWrapper.tsx: (Similar structure, state for visibility)

GojoDomainLoader.tsx:

TypeScript

import React, { useState, useEffect } from 'react';
import LoadingMask from './LoadingMask';
import BlueBackground from './BlueBackground';
import ContentWrapper from './ContentWrapper';
import styles from './GojoDomainLoader.module.css';

const GojoDomainLoader: React.FC = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [showBlueBackground, setShowBlueBackground] = useState(false);
  const [showContent, setShowContent] = useState(false);

  useEffect(() => {
    const handleLoadingMaskEnd = () => setShowBlueBackground(true);
    const handleBlueBackgroundEnd = () => setShowContent(true);
    const handleContentLoad = () => setIsLoading(false);

    // Simulate loading logic (replace with actual content loading)
    setTimeout(() => {
      handleContentLoad();
    }, 1000); // Adjust timeout as needed

    return () => {
      // Cleanup event listeners (if necessary)
    };
  }, []);

  return (
    <div className={styles.container}>
      {isLoading && <LoadingMask isActive={isLoading} onAnimationEnd={handleLoadingMaskEnd} />}
      {showBlueBackground && <BlueBackground isActive={showBlueBackground} onAnimationEnd={handleBlueBackgroundEnd} />}
      {showContent && <ContentWrapper />}
    </div>
  );
};

export default GojoDomainLoader;

Explanation:

  • Each component manages its own animation state and event handlers.
  • GojoDomainLoader orchestrates the animation sequence using state variables.