Search code examples
javascriptscrollstickysticky-footerintersection-observer

Intersection Observer: Remove/ Add classes based on <header> and <footer> viewport position


I have a question relating to the Intersection Observer.

As you can see in my code, when you scroll down to the <footer> the .product-photo (T-Shirt) stays fixed and I'm not sure how to properly add the .absolute class to make it positioned bottom to it's container.

Note: I'm aware of css's position: sticky but it won't work for this particular case


My Goal: Make the T-Shirt image...

static when the header is in view
fixed when the header is not in view
absolute when the footer is in view

I have everything working except the absolute class

enter image description here

const photo = document.querySelector(".product-photo");
const header = document.querySelector("header");

const sectionOptions = {
	root: null
};

const sectionObserver = new IntersectionObserver(function(entries, sectionObserver) {
  entries.forEach(entry => {
    if (!entry.isIntersecting) {
      photo.classList.add("fixed");
    } 
		else {
      photo.classList.remove("fixed");
    }
  });
}, sectionOptions);


sectionObserver.observe(header);
* { margin: 0; padding: 0; box-sizing: border-box; font-family: roboto; }

header, footer { background: black; color: white; padding: 2rem; }
footer { height: 100vh; }
main { background: white; padding: 1rem; }
section { margin: 2rem 0; }
h1, h2 { margin: 0 0 1rem; font-size: 2rem; }
p { margin: 0 0 2rem; font-size: 1.2rem; line-height: 1.4; }
img { max-width: 100%; }

/* styles
****************************************/
.announcement { background: #f2f2f2; font-size: 2rem; padding: 2rem; text-align: center; border: 3px dashed rgba(0,0,0,.2); margin-top: 0; }
.product { height: 100%; display: flex; justify-items: space-between; position: relative; }
.product-photo { width: 50%; }
.product-description { width: 50%; }
.recommended-products { display: flex; }

/* Sticky Classes */
.product-photo.fixed img { position: fixed; top: 0; left: 0; width: 50%; }
.product-photo.absolute img { position: absolute; left: 0; bottom: 0; width: 50%; }

/* .fixed { position: fixed; top: 0; left: 0; right: 0; } */




/* Mobile */
@media (min-width: 0) and (max-width: 768px) { 
	.product { display: flex; flex-direction: column; }
	.product-photo { width: 100%; }
	.product-description { width: 100%; }
}
  
/* Tablet */
@media (min-width: 769px) and (max-width: 1024px) { 
	
    
}
  
/* Desktop */
@media (min-width: 1025px) { 
	
    
}
<header>Header</header>
<main>
	
	<!-- Product -->
	<section class="product">
		<figure class="product-photo">
			<img src="https://cdn.shopify.com/s/files/1/0078/6825/2273/products/SSCrew_TrueBlack_Front_1950x.png" alt="Black T-Shirt">
		</figure>
		<article class="product-description">
			<h1>Black T-Shirt</h1>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
		</article>
	</section>
</main>
<footer>Footer</footer>


Solution

  • Add the footer to the observer and add/remove fixed and absolute classes by testing isIntersecting of the header/footer which you can get in your case by using entry.target.tagName:

    const header = document.querySelector("header");
    const footer = document.querySelector("footer");
    
    const sectionOptions = {
        root: null
    };
    
    const sectionObserver = new IntersectionObserver(function(entries, sectionObserver) {
      const headerEntry = entries.find(entry => entry.target.tagName === 'HEADER');
      const footerEntry = entries.find(entry => entry.target.tagName === 'FOOTER');
      if (headerEntry && headerEntry.isIntersecting) {
        photo.classList.remove("fixed");
      } else if(footerEntry && footerEntry.isIntersecting) {
        photo.classList.remove("fixed");
        photo.classList.add("absolute");
      } else {
        photo.classList.add("fixed");
        photo.classList.remove("absolute");
      }
    }, sectionOptions);
    
    
    sectionObserver.observe(header);
    sectionObserver.observe(footer);
    

    Live example:

    const photo = document.querySelector(".product-photo");
    const header = document.querySelector("header");
    const footer = document.querySelector("footer");
    
    const sectionOptions = {
    	root: null
    };
    
    const sectionObserver = new IntersectionObserver(function(entries, sectionObserver) {
      const headerEntry = entries.find(entry => entry.target.tagName === 'HEADER');
      const footerEntry = entries.find(entry => entry.target.tagName === 'FOOTER');
      if (headerEntry && headerEntry.isIntersecting) {
        photo.classList.remove("fixed");
      } else if(footerEntry && footerEntry.isIntersecting) {
        photo.classList.remove("fixed");
        photo.classList.add("absolute");
      } else {
        photo.classList.add("fixed");
        photo.classList.remove("absolute");
      }
    }, sectionOptions);
    
    
    sectionObserver.observe(header);
    sectionObserver.observe(footer);
    * { margin: 0; padding: 0; box-sizing: border-box; font-family: roboto; }
    
    header, footer { background: black; color: white; padding: 2rem; }
    footer { height: 100vh; }
    main { background: white; padding: 1rem; }
    section { margin: 2rem 0; }
    h1, h2 { margin: 0 0 1rem; font-size: 2rem; }
    p { margin: 0 0 2rem; font-size: 1.2rem; line-height: 1.4; }
    img { max-width: 100%; }
    
    /* styles
    ****************************************/
    .announcement { background: #f2f2f2; font-size: 2rem; padding: 2rem; text-align: center; border: 3px dashed rgba(0,0,0,.2); margin-top: 0; }
    .product { height: 100%; display: flex; justify-items: space-between; position: relative; }
    .product-photo { width: 50%; }
    .product-description { width: 50%; }
    .recommended-products { display: flex; }
    
    /* Sticky Classes */
    .product-photo.fixed img { position: fixed; top: 0; left: 0; width: 50%; }
    .product-photo.absolute img { position: absolute; left: 0; bottom: 0; width: 50%; }
    
    /* .fixed { position: fixed; top: 0; left: 0; right: 0; } */
    
    
    
    
    /* Mobile */
    @media (min-width: 0) and (max-width: 768px) { 
    	.product { display: flex; flex-direction: column; }
    	.product-photo { width: 100%; }
    	.product-description { width: 100%; }
    }
      
    /* Tablet */
    @media (min-width: 769px) and (max-width: 1024px) { 
    	
        
    }
      
    /* Desktop */
    @media (min-width: 1025px) { 
    	
        
    }
    <header>Header</header>
    <main>
    	
    	<!-- Product -->
    	<section class="product">
    		<figure class="product-photo">
    			<img src="https://cdn.shopify.com/s/files/1/0078/6825/2273/products/SSCrew_TrueBlack_Front_1950x.png" alt="Black T-Shirt">
    		</figure>
    		<article class="product-description">
    			<h1>Black T-Shirt</h1>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    			<p>This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. </p>
    		</article>
    	</section>
    </main>
    <footer>Footer</footer>