Shifting static information both above and below a 100vh element

At the moment, I have a stationary image in the center of my screen that moves horizontally when scrolling with the mouse. Now, I want to include a screen above and below this element, each with a height of 100vh. However, when I attempt to do so, the fixed components shift to the top screen, which I intended to leave empty. I've tried adding a margin-top: 100vh to the image, but it ends up moving off-screen.

I am faced with two challenges here. Firstly, how can I position this component in the middle of the screen after a 100vh window, and secondly, how can I ensure that the animations only commence once the user has reached that specific component? Currently, in my project setup, the image and text move with the user's scroll, so introducing a 100vh screen above triggers the scroll and thus initiates my animations prematurely.

For a better understanding, view the code on CodeSandbox in fullscreen mode: https://codesandbox.io/s/interesting-bouman-lokt5?file=/src/App.js

Code:

{/* <div style={{height:"100vh"}}></div> */}
      <div className="App" ref={ref}>
        <h1 id="title" className="titles">
          Random Title
        </h1>
        <section id="block1" className="blocks"></section>
        <figure id="passport">
          <img
            alt="passport"
            src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
          />
        </figure>
        <h2 id="text1" className="titles text1">
          Random Text 1
        </h2>
        <section id="block2" className="blocks"></section>
        <h2 id="text2" className="titles text2">
          Random Text 2
        </h2>
        <section id="block3" className="blocks"></section>
        <h2 id="text3" className="titles text3">
          Random Text 3
        </h2>
        <section id="block4" className="blocks"></section>
      </div>
      {/* Stop Scrolling Animation */}
      {/* <div>Content</div> */}

Answer №1

These steps should be followed:

  1. Retrieve the height of the initial element.
  2. Conceal both the image and the first title.
  3. Determine when to trigger the action.
  4. Include additional checks when displaying or hiding elements.

Explore a sample in the sandbox via this link

function App() {
  const [screen, setScreen] = React.useState(false);

  const headerRef = React.useRef(null);
  const mainRef = React.useRef(null);

  // Adjust value for image placement closer to edges or center
  const setImageLimitMovement = 1;

  const setTextLimitMovement = 4;
  const opacityRange = 400;
  // Speed of text movement
  const speed = 1; // .5

  React.useEffect(() => {
    window.addEventListener('resize', () => {
      if (window.innerWidth !== 0 || window.innerHeight !== 0) {
        setScreen(window.innerWidth);
      }
    });
  }, []);

  React.useEffect(() => {
    const app = [...mainRef.current.children];
    const titles = app.filter(el => el.matches('.titles') && el);
    const blocks = app.filter(el => el.matches('.blocks') && el);
    const img = app.find(el => el.matches('#passport') && el);

    const headerHeight = headerRef.current.getBoundingClientRect().height;

    // Retrieve center points of blocks in an array
    const centerPoints = blocks.map((blockEl, idx) => {
      const blockindex = idx + 1;
      const blockHeight = Math.floor(blockEl.getBoundingClientRect().height);
      const blockHalf = blockHeight / 2;
      return blockHeight * blockindex - blockHalf;
    });

    const leftMoveLimitImg = -centerPoints[0] / setImageLimitMovement;
    const rightMoveLimitImg = centerPoints[0] / setImageLimitMovement;

    const textLimit = centerPoints[0] / setTextLimitMovement;
    // Hide elements upon component mounting
    titles[0].style.transform = `scale(0)`;
    img.style.transform = `scale(0)`;

    const changeBackground = () => {
      const value = window.scrollY;

      // Initialization
      const startAction = headerHeight * 0.8;

      // Scale action to display content
      if (centerPoints[3] + startAction > value && startAction < value) {
        titles[0].style.transform = `scale(${
          (value - startAction) / 100 > 1 ? 1 : (value - startAction) / 100
        })`;
        
        img.style.transform = `scale(${
          (value - startAction) / 100 > 1 ? 1 : (value - startAction) / 100
        })`;
        
      }

      // Hide image & titles if header is visible
      if (startAction > value) {
        titles[0].style.transform = `scale(0)`;
        img.style.transform = `scale(0)`;
      }

      // Hide 'Random Text' on scrolling above block level
      if (headerHeight > value) {
        titles[1].style.transform = `translateX(0px)`;
        titles[1].style.opacity = 0;
      }

      if (headerHeight < value) {
        // Start scroll animation
        const mainValue = value - headerHeight; 
        titles[0].style.transform = `translateY(-${mainValue * speed}px)`;

        // IMAGE BOUNCE
        // Move to <==
        if (centerPoints[0] > mainValue) {
          img.style.transform = `translateX(-${
            mainValue * (1 / setImageLimitMovement)
          }px)`;

          titles[1].style.transform = `translateX( ${
            0 + mainValue / setTextLimitMovement
          }px)`;
          return;
        }

        // Move to ==>
        if (centerPoints[1] > mainValue) {
          const moveTextToRight =
            centerPoints[1] / setTextLimitMovement - textLimit;
          const hideText = centerPoints[0] / opacityRange;
          const checkDirection = Math.sign(
            textLimit + (textLimit - mainValue / setTextLimitMovement)
          );

          const moveImageToRight =
            (mainValue - centerPoints[0]) / setImageLimitMovement;
          img.style.transform = `translateX(${
            leftMoveLimitImg + moveImageToRight
          }px)`;

          if (checkDirection === -1) {
            titles[1].style.opacity = 0;
            titles[1].style.transform = `translateX(${0}px)`;

            titles[2].style.opacity =
              Math.abs(hideText - mainValue / opacityRange) - 1;
            titles[2].style.transform = `translateX(${
              moveTextToRight - mainValue / setTextLimitMovement
            }px)`;
            return;
          }
          if (checkDirection === 1) {
            titles[1].style.opacity = 1 + (hideText - mainValue / opacityRange);
            titles[1].style.transform = `translateX(${
              textLimit + (textLimit - mainValue / setTextLimitMovement)
            }px)`;

            titles[2].style.opacity = 0;
            titles[2].style.transform = `translateX(${0}px)`;
          }
          return;
        }

        // Move to <==
        if (centerPoints[2] > mainValue) {
          const moveTextToLeft =
            centerPoints[2] / setTextLimitMovement - textLimit;
          const hideText = centerPoints[1] / opacityRange;
          const checkDirection = Math.sign(
            moveTextToLeft - mainValue / setTextLimitMovement
          );

          const moveImageToLeft =
            (-mainValue + centerPoints[1]) / setImageLimitMovement;
          img.style.transform = `translateX(${
            rightMoveLimitImg + moveImageToLeft
          }px)`;

          if (checkDirection === -1) {
            titles[2].style.opacity = 0;
            titles[2].style.transform = `translateX(${0}px)`;

            titles[3].style.opacity =
              Math.abs(hideText - mainValue / opacityRange) - 1;
            titles[3].style.transform = `translateX(${Math.abs(
              moveTextToLeft - mainValue / setTextLimitMovement
            )}px)`;
          }

          if (checkDirection === 1) {
            titles[2].style.opacity = 1 + (hideText - mainValue / opacityRange);
            titles[2].style.transform = `translateX(-${
              moveTextToLeft - mainValue / setTextLimitMovement
            }px)`;

            titles[3].style.opacity = 0;
            titles[3].style.transform = `translateX(${0}px)`;
          }
          return;
        }

        // Move to ==>
        if (centerPoints[3] > mainValue) {
          const moveTextToRight =
            centerPoints[3] / setTextLimitMovement - textLimit;
          const hideText = centerPoints[2] / opacityRange;
          const checkDirection = Math.sign(
            moveTextToRight - mainValue / setTextLimitMovement
          );

          const moveImageToRight =
            (mainValue - centerPoints[2]) / setImageLimitMovement;
          img.style.transform = `translateX(${
            leftMoveLimitImg + moveImageToRight
          }px)`;

          if (checkDirection === -1) {
            titles[3].style.opacity = 0;
            titles[3].style.transform = `translateX(${0}px)`;

            const reduceOpacity = Math.abs(
              (centerPoints[0] * 0.7 + (mainValue - centerPoints[3])) / 100
            );
            const checkReduceOpacity = Math.sign(
              (centerPoints[0] * 0.7 + (mainValue - centerPoints[3])) / 100
            );

            if (checkReduceOpacity === -1) {
              img.style.transform = `scale(${
                reduceOpacity > 1 ? 1 : reduceOpacity
              })`;
            }
            if (checkReduceOpacity === 1) {
              img.style.transform = `scale(0)`;
            }
          }

          if (checkDirection === 1) {
            titles[3].style.opacity = 1 + (hideText - mainValue / opacityRange);
            titles[3].style.transform = `translateX(${
              moveTextToRight - mainValue / setTextLimitMovement
            }px)`;
          }

          return;
        }
      }
    };
    window.addEventListener('scroll', changeBackground);

    return () => window.removeEventListener('scroll', changeBackground);
  }, [screen]);

  return (
      <div className="App">
        <div id="header" ref={headerRef}>
          Header
        </div>
        <main ref={mainRef}>
          <h1 id="title" className="titles">
            Random Title
          </h1>
          <section id="block1" className="blocks">
            block 1
          </section>
          <figure id="passport">
            <img
              alt="passport"
              src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
            />
          </figure>
          <h2 id="text1" className="titles text1">
            Random Text 1
          </h2>
          <section id="block2" className="blocks">
            block
          </section>
          <h2 id="text2" className="titles text2">
            Random Text 2
          </h2>
          <section id="block3" className="blocks">
            block
          </section>
          <h2 id="text3" className="titles text3">
            Random Text 3
          </h2>
          <section id="block4" className="blocks">
            block 4
          </section>
        </main>
        {/* Stop Scrolling Animation */}
        <div id="footer">Footer</div>
      </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render( <
  App / > ,
  rootElement
);
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

.App {
  font-family: sans-serif;
  width: 100%;
  background-color: hsl(220, 65%, 16%);
}
#header,
#footer {
  height: 100vh;
  display: grid;
  place-items: center;
  font-size: 4em;
  color: aliceblue;
  background-color: hsl(220, 65%, 45%);
}
main {
  position: relative;
}
figure {
  width: 280px;
  height: max-content;
  position: fixed;
  inset: 0;
  margin: auto;
  z-index: 100;
}
img {
  width: 100%;
}

.blocks {
  height: 100vh;
  display: flex;
  position: relative;
  grid-column: 1 / -1;
  color: grey;
}

.titles {
  width: max-content;
  height: max-content;
  position: fixed;
  inset: 0;
  margin: auto;
  color: white;
  z-index: 99;
}

h1 {
  font-size: 3.5em;
}
h2 {
  display: flex;
  opacity: 0;
  font-size: 2.5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

the power of using keywords and prototypes

Greetings! I am currently delving into the realm of JavaScript, hailing from a C++ background. The transition has proven to be quite perplexing for me. Below is a snippet of code that I have been troubleshooting: var someArray = []; nameCompare = function ...

Navigate through AJAX Live search using arrow keys and mouse controls

I have encountered an issue with the autocomplete feature on my webpage. Although I am able to input suggestions, I am unable to navigate through them using the mouse pointer initially. Oddly enough, if I press the up/down keys once after the list items ar ...

Encountering an issue when passing a response using coverage.js

I am utilizing coverage.js to showcase data. When I pass my variable containing the coverage response into an HTML file, similar to how it's done in Angular to display expressions, I encounter a syntax error: <div class="container" style="margin- ...

When clicking on a div with the CSS property user-select set to none, the selected text in a contenteditable element is

I'm currently working on a unique JavaScript rich text editor that utilizes div elements with the CSS property user-select: none instead of traditional buttons. While this approach works perfectly in Firefox, I've encountered a major issue with G ...

Removing commas and non-numeric symbols from a string using JavaScript

Stripping both a comma and any non-numeric characters from a string can be a bit tricky, especially with RegExs involved :). Is there anyone who could provide assistance on how to achieve this? I need to remove commas, dashes, and anything that is not a ...

Tips for breaking apart elements of a JavaScript array when a specific delimiter is present in an object property

I am facing a challenge with an array of objects where some properties contain commas. My goal is to split these properties into new objects and recursively copy the rest of the properties into new array elements. For example, consider this original array ...

Transfer the Vue query parameter from the current route to the router-link

Why is it so challenging to detect and pass query parameters in Vue components? I'm trying to access the router from my component, but nothing seems to be working. How can I achieve this? <router-link :to="{ path: '/', query: { myQue ...

Styling with CSS: The Art of Showcasing Initials or Images of Individuals

By following this elegant HTML and CSS example, I am able to showcase my initials over my photo. While this is wonderful, I would like the initials to be displayed only if the image does not exist; if the image is present, the person's initials shoul ...

The error page is requesting a root-layout, which indicates that having multiple root layouts is not feasible

My issue is as follows: The not-found page located in my app directory requires a root-layout which I have added to the same directory. However, this setup prevents me from using multiple root layouts in the structure below. How can I resolve this? It see ...

Can next.js rewrites be configured with environment variables?

Currently, I am in the process of developing my application with a next.js frontend and express.js backend. During development, I simply run the relevant dev servers in the terminal. However, now I am looking to deploy my app using Docker for production. I ...

What is the correct way to set up a nested router using react-router-dom?

I need to have pages on my site that include a header, while others do not. Pages like email verification and oauth do not require a header, but the main application does need one with a search bar, links, logos, etc. I am attempting to separate these usin ...

Code has been loaded successfully for react-loadable Chunks, however it has not been

Encountering a problem while trying to implement chunks in my React app using react-loadable Everything functions perfectly on webpack-dev-server in development mode. However, when I build the project and deploy it to the server, async components are ...

Troubleshooting issue with Angular's ng-class functionality malfunctioning

I am currently working on displaying a class using ng-class in the box element of my HTML code. However, it does not seem to be functioning as expected. Can someone please help me identify what I might be doing wrong? Check out this link for reference. ...

Issues with the plugin for resizing text to fit the parent div's scale

I've spent the last hour attempting to get this script to function properly, but no luck yet. Check out the correct jsfiddle example here: http://jsfiddle.net/zachleat/WzN6d/ My website where the malfunctioning code resides can be found here: I&apo ...

Storing the path of a nested JSON object in a variable using recursion

Can the "path" of a JSON object be saved to a variable? For example, if we have the following: var obj = {"Mattress": { "productDelivered": "Arranged by Retailer", "productAge": { "ye ...

Mandatory press for a function known as a click

After dealing with a previous question on this matter, I have encountered yet another issue. My goal is to rotate an element clockwise by 22 degrees and then back to its initial state of 0 degrees on click. The first function executes correctly, but the ...

Is it necessary to dispose of node.js domains? When is the appropriate time to do so?

In my Express application, I utilize domains for each incoming request. To ensure that all subsequent middlewares are executed within a domain, I've implemented a middleware. app.use(function(req, res, next) { var d = domain.create(); d.req ...

What is the best way to delay a recursive JavaScript function for 3 seconds?

Before writing this post, I have already come across the following questions: how-to-pause-a-settimeout-call how-to-pause-a-settimeout-function how-to-pause-a-function-in-javascript delay-running-a-function-for-3-seconds Question The below code snipp ...

Error encountered during Jest snapshot testing: Attempting to destructure a non-iterable object which is invalid

I am currently facing an issue with my React codebase where I am attempting to create snapshot tests for a component. However, Jest is showing an error indicating that I am trying to destructure a non-iterable instance. Despite thoroughly reviewing the cod ...

Link together a series of AJAX requests with intervals and share data between them

I am currently developing a method to execute a series of 3 ajax calls for each variable in an array of data with a delay between each call. After referring to this response, I am attempting to modify the code to achieve the following: Introduce a del ...