skip to Main Content

enter image description here

Trying to create these kind of screen transitions – a sticky image, that then gets replaced with a similar version but on a different theme colour.

What scroll formula/value is being used to control the height of the white-holder element so that it replaces the dark mode with the light.

I’ve tried to increase the height of the container to test this component.

https://jsfiddle.net/5fprngcL/14/

  $( document ).ready(function() {

    console.log("ready")
var myElement = $(".white-mode-holder");

function setHeight(yPos, el) {
  el.style.height = `${yPos}vh`;
}

$(document).scroll(function(e){
  
  console.log("e", e)
  
  let h = $("body").offset().top;
  console.log("h", h)
  
  setHeight(h*2, myElement[0])
  
});


  });
html {
    -ms-text-size-adjust: 100%;
    -webkit-text-size-adjust: 100%;
    font-family: sans-serif;
}
body {
    color: #fff;
    background-color: #000;
    font-family: Manrope, sans-serif;
    font-size: 14px;
    line-height: 1.2;
}

.container {
    max-width: 1170px;
    margin-left: auto;
    margin-right: auto;
    padding: 2em;
    height: 2000px;
}

img {
    overflow-clip-margin: content-box;
    overflow: clip;
}

* {
    box-sizing: border-box;
}

img {
    border: 0;
}


img {
    max-width: 100%;
    vertical-align: middle;
    display: inline-block;
}



.dark-and-light-mode-container {
    width: 100%;
    height: 100vh;
    position: -webkit-sticky;
    position: sticky;
    top: 0;
}

.dark-and-light-mode-sticky {
    width: 100%;
    height: 100vh;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    display: flex;
    position: relative;
}

.dark-mode-holder {
    z-index: 1;
    width: 100%;
    height: 100vh;
    background-color: #000;
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
    display: flex;
    position: relative;
    overflow: hidden;
}

.white-mode-holder {
    z-index: 1;
    width: 100%;
    height: 50vh;
    background-color: #fff;
    flex-direction: column;
    justify-content: flex-end;
    align-items: center;
    display: flex;
    position: absolute;
    top: auto;
    bottom: 0%;
    left: 0%;
    right: 0%;
    overflow: hidden;
}


.white-mode-holder{
    will-change: width, height;
    height: 0vh;
}


.center-iphone {
    width: 100vw;
    height: 100vh;
    min-height: 100vh;
    min-width: 100vw;
    text-align: center;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    display: flex;
    position: relative;
}



.dark-mode-hand-holder {
    z-index: 1;
    width: 400px;
    max-width: 400px;
    min-width: 400px;
    justify-content: center;
    align-items: center;
    margin: 80px 0 0;
    display: flex;
    position: relative;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}



.dark-mode-hand {
    z-index: 1;
    width: 400px;
    position: relative;
}


.dark-mode-app {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 20px;
}



.dark-mode-app-holder {
    padding: 2.6% 26% 26.2% 16%;
    position: absolute;
    top: 0%;
    bottom: 0%;
    left: 0%;
    right: 0%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>


<div class="container">

  <div class="dark-and-light-mode-container">
    <div class="dark-and-light-mode-sticky">
      <div class="dark-mode-holder">
        <div class="center-iphone">
          <div class="dark-mode-hand-holder"><img src="https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand.webp" loading="eager" sizes="(max-width: 479px) 300px, 400px" srcset="https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand-p-500.webp 500w, https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand-p-800.webp 800w, https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand.webp 848w" alt="" class="dark-mode-hand">
            <div class="dark-mode-app-holder"><img src="https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b576894fd22717696b9ead_Dark%20Mode.webp" loading="eager" alt="" sizes="232px" srcset="https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b576894fd22717696b9ead_Dark%20Mode-p-500.webp 500w, https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b576894fd22717696b9ead_Dark%20Mode-p-800.webp 800w, https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b576894fd22717696b9ead_Dark%20Mode.webp 1035w" class="dark-mode-app"></div>
          </div>
        </div>
      </div>
      <div class="white-mode-holder" style="will-change: width, height; height: 0vh;">
        <div class="center-iphone">
          <div class="dark-mode-hand-holder"><img src="https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand.webp" loading="eager" sizes="(max-width: 479px) 300px, 400px" srcset="https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand-p-500.webp 500w, https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand-p-800.webp 800w, https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand.webp 848w" alt="" class="dark-mode-hand">
            <div class="dark-mode-app-holder"><img src="https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b57689b7dd5c5baa9e0780_Light%20Mode.webp" loading="eager" alt="" sizes="232px" srcset="https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b57689b7dd5c5baa9e0780_Light%20Mode-p-500.webp 500w, https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b57689b7dd5c5baa9e0780_Light%20Mode-p-800.webp 800w, https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b57689b7dd5c5baa9e0780_Light%20Mode.webp 1036w" class="dark-mode-app"></div>
          </div>
        </div>
      </div>
    </div>
  </div>


</div>

latest jsfiddle 11th Sep – using clip paths

https://jsfiddle.net/jdqgewfh/

2

Answers


  1. Chosen as BEST ANSWER

    This is the latest version I have for this - but the codesandbox seems broken

    https://codesandbox.io/p/sandbox/plfszv

    "use client";
    import React, { useEffect } from "react";
    //import "./style.css";
    import { isMobile } from "react-device-detect";
    import MobileViewCom from "./mobileView.js";
    
    const Download = () => {
      useEffect(() => {
        let scrollableDiv = document.getElementById("mainDivPositionget");
        let mainPositionSet = document.getElementById("stickyImagePositin");
        let shadowInTexts = document.getElementById("shadowInTexts");
    
        let topPosition = 0;
        let element = scrollableDiv;
    
        // Accumulate the offsetTop up the chain
        while (element) {
          topPosition += element.offsetTop;
          element = element.offsetParent;
        }
        console.log("Position from top:", topPosition);
    
        window.addEventListener("scroll", function () {
          let makeScrollInImage;
          let offset = window.pageYOffset;
          console.log(offset);
    
          if (isMobile) {
            if (offset > topPosition) {
              mainPositionSet?.classList.add("stickyForMObile");
              makeScrollInImage = offset - (topPosition + 50);
            }
          } else {
            if (offset > topPosition + 250) {
              mainPositionSet?.classList.add("sticky");
              makeScrollInImage = offset - (topPosition + 50);
              // bgColor = window.getComputedStyle(div).backgroundColor;
            }
          }
        });
      }, []);
    
      return (
        <section id="download" className="section">
          <div className="container">
            <div className="effortlessly-integration-container">
              <div className="center-title">
                <div className="title-holder">
                  <div className="fade-in-move-on-scroll">
                    <h1 className="title">Start using Black now</h1>
                  </div>
                  <div className="fade-in-move-on-scroll">
                    <div className="experience-paragraph-holder">
                      <p>
                        Start your free trial now and see how easy it is to track,
                        manage, and optimize your time.
                      </p>
                    </div>
                  </div>
                </div>
              </div>
              <div className="download-badge-holder">
                <div className="download-badge-container">
                  <a
                    href="http://applestore.com"
                    target="_blank"
                    className="download-badge-button w-inline-block"
                  >
                    <img
                      src="https://assets.website-files.com/63aee5793ca698452efe7f60/63b55c334fd227405569f74b_App%20Store%20badge.svg"
                      loading="lazy"
                      alt=""
                      className="download-badge-image"
                    />
                  </a>
                  <a
                    href="http://googleplay.com"
                    target="_blank"
                    className="download-badge-button w-inline-block"
                  >
                    <img
                      src="https://assets.website-files.com/63aee5793ca698452efe7f60/63b55c347e2c7082fffff214_Mobile%20App%20Store%20Badge.svg"
                      loading="lazy"
                      alt=""
                      className="download-badge-image"
                    />
                  </a>
                </div>
              </div>
            </div>
          </div>
          <div className="h-[230vh] relative">
            <div className="w-full h-full absolute top-0 left-0 bottom-0 right-0">
              <div className="w-full h-[42%] bg-black"></div>
              <div className="w-full h-[60%] bg-white"></div>
            </div>
            <div
              className="absolute top-0 left-0 right-0 grid justify-center"
              id="stickyImagePositin"
            >
              <div className=" translate-y-[-5px] relative">
                <div className="" id="getImageBackgroundCOlorName">
                  <img
                    src="https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand.webp"
                    loading="eager"
                    sizes="(max-width: 479px) 300px, 400px"
                    srcSet="https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand-p-500.webp 500w, https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand-p-800.webp 800w, https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand.webp 848w"
                    alt=""
                    className="dark-mode-hand"
                  />
                  <div className="dark-mode-app-holder overflow-hidden">
                    <MobileViewCom />
                  </div>
                </div>
              </div>
            </div>
          </div>
        </section>
      );
    };
    
    export default Download;
    

  2. You can do this by setting up your cards positions to stack on top of each other. The top card would be initially set to absolute so the scroll can control it. Set the hands position to sticky so it sticks to its top position. The bottom card set to fixed with a display of none. Then set up a scroll event on the window and track the parent element, top card and hand image elements positions relative to each other and the window, you can then calculate the percentage of the cards scroll and change a clipPath when the parent containers bottom is with in the cards top and bottom bounds.

    el.style.clipPath: inset('${percentage}, 0, 0, 0') => Calculate the
    difference between the top cards top position and the cards
    parent containers bottom positions during scroll, then divide
    this by the top cards height and multiply by 100. This will
    give you the percentage of the inset for the clip-path
    to use on the top card.

    You will need to track the scrolling direction as well and affect the clipPath accordingly.

    NOTE ON EDIT: I edited the snippet to reflect the images you used in your snippet.

    See further comments in the snippet and let me know if anything is not clear.

    // get the container element using ID
    const container = document.querySelector('#container');
    // get the top layer card with higher z-index
    // this would be the element with the contrasting background
    // of the scrolling container it is a child of
    const topCard = document.querySelector('#top-card');
    // get the hand element which will sit on top of both card elements
    const hand = document.querySelector('#hand');
    
    // get the bottom element
    const bottomCard = document.querySelector('#bottom-card');
    
    // set a variable to track scroll direction
    let lastScrollTop = 0;
    // callback method used to track scroll on window
    const observeElementsScroll = e => {
      // variable to track windows scroll point for direction of scroll
      let dirOffset = window.pageYOffset || document.documentElement.scrollTop;
      // added an element for the hand image
      // getBoundingClientRect for card and hand elements
      const handRect = hand.getBoundingClientRect();
      const contRect = container.getBoundingClientRect();
      const topCardRect = topCard.getBoundingClientRect();
      
      // if your topcardRect.top <= 0, then we set bottomCards display to block
      // we set the topCards and hand images position to fixed 
      if (topCardRect.top <= 0) {
        bottomCard.style.display = 'block';
        topCard.style.position = 'fixed';
        hand.style.position = 'fixed';
      }
      // if containers top is greater than 0 set the bottomCard back 
      // to display of none and topCards position back to absolute so 
      // it can scroll and set the hand card to sticky
      if (contRect.top >= 0) {
        bottomCard.style.display = 'none';
        topCard.style.position = 'absolute';
        hand.style.position = 'sticky';
      }
    
      // get the top cards top and bottom bounds within the Document
      // if containers bottom is less than or equal to topCards bottom
      // AND containers bottom is greater than or equal to topCards top
      if (
        contRect.bottom <= topCardRect.bottom &&
        contRect.bottom >= topCardRect.top
      ) {
        // define our percentage -> take the difference of topCards.top
        // and subtract it with containers.bottom and convert to postive using Math.abs()
        // divide that by the height of the topCard and then mulitple by 100 = percentage
    
        // detect scroll direction and alter clipPaths first inset property
        // using percentage. Math.abs returns a whole number if number is negative.
        // Calculate the difference between topCards top and containers bottom positions 
        // during scroll, then divide this by the topCards height and multiply by 100
        if (dirOffset - lastScrollTop > 0) { // scrolling up
          topCard.style.clipPath = `inset(${Math.abs(topCardRect.top - contRect.bottom) / topCardRect.height * 100}% 0 100% 0)`;
        } else if (dirOffset - lastScrollTop < 0) { // scrolling down
          topCard.style.clipPath = `inset(${Math.abs(topCardRect.top - contRect.bottom) / topCardRect.height * 100}% 0 100% 0)`;
        }
        // added else if conditionals for when the parent containers bottom is out
        // of the bounds of the cards, this sets the clip-paths inset to static values 
        // and removes glitchy behavior if the scroll is moving fast with mouse wheel
      }else if(contRect.bottom < topCardRect.top) {
        topCard.style.clipPath = `inset(0% 0% 100% 0%)`;  
      }else if(contRect.bottom > topCardRect.bottom){
        topCard.style.clipPath = `inset(100% 0% 100% 0%)`;
      }
    
      // redefine the scrollTop for directional reference
      lastScrollTop = dirOffset;
    }
    
    // event listener for window scroll
    addEventListener('scroll', observeElementsScroll)
    body {
      height: 500vh;
      padding: 0;
      margin: 0;
      font-family: sans-serif;
      font-size: 1.2rem;
    }
    
    #parent {
      height: 200vh;
      width: 100%;
      background-color: rgba(0, 0, 0, 1);
      position: relative;
    }
    
    #heading {
      text-align: center;
      margin-top: 10rem;
    }
    
    .cards {
      top: 14px;
      width: 290px;
    }
    
    .cards img {
      width: 100%;
      height: 100%;
      border-radius: 40px;
    }
    
    #container {
      width: fit-content;
      height: 200vh;
      position: relative;
      left: 50%;
      margin-left: -250px;
    }
    
    #hand {
      position: sticky;
      top: 1px;
      z-index: 3;
    }
    
    #top-card {
      position: absolute;
      z-index: 2;
      margin-left: 80px;
      clip-path: inset(100% 0% 100% 0%);
    }
    
    #bottom-card {
      position: fixed;
      margin-left: 80px;
      display: none;
    }
    <h2 id="heading">Scroll Down</h2>
    <div id="parent">
      <div id="container">
        <div id="hand">
          <img src="https://assets.website-files.com/63aee5793ca698452efe7f60/63b5761b8fb633b42f3ad6c4_Iphone%20In%20Hand-p-500.webp">
        </div>
        <div id="top-card" class="cards"><img src="https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b57689b7dd5c5baa9e0780_Light%20Mode.webp">
        </div>
        <div id="bottom-card" class="cards"><img src="https://cdn.prod.website-files.com/63aee5793ca698452efe7f60/63b576894fd22717696b9ead_Dark%20Mode.webp">
        </div>
      </div>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search