Parallax effect - force text block to behave like background-attachment: fixed


I'm attempting to create a simple parallax effect where each 100vh section scrolls up to reveal the next section (new background color, background image, and text block) while keeping the text block fixed relative to its parent container.

I've put together a static example of what I'm trying to achieve using screenshots of each section: static example. Of course I'd like the content to be dynamic rather than flat images.

Here's a simple version of my code thus far:

body {
  margin: 0;
  padding: 0;
}
h2 {
  font-size: 48px;
}
p {
  font-size: 18px;
}
section {
  min-height: 100vh;
  width: 100%;
  text-align: center;
  position: relative;
  background-attachment: fixed !important;
  background-size: cover !important;
  background-repeat: no-repeat !important;
}
section.first {
  background: url(https://picsum.photos/1920/500/?image=1057);
}
section.first .content {
  background-color: rgba(74, 180, 220, .85);
}
section.second {
  background: url(https://picsum.photos/1920/500/?image=1067);
}
section.second .content {
  background-color: rgba(103, 198, 180, .85)
}
section.third {
  background: url(https://picsum.photos/1920/500/?image=1033);
}
section.third .content {
  background-color: rgba(5, 123, 188, .85);
}
section.fourth {
  background: url(https://picsum.photos/1920/500?image=1063);
}
section.fourth .content {
  background-color: rgba(187, 216, 100, .85)
}
.content {
  position: relative;
  height: 100vh;
  width: 100%;
  padding: 50px 0;  
}
.copy {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #fff;
  font-family: 'Noto Serif', serif;
  font-weight: 300;
}
.button {
  border: 2px solid #fff;
  border-radius: 3px;
  padding: 15px 25px;
  display: inline-block;
  width: auto;
  font-family: 'Assistant', sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  transition: .2s ease all;
}
.button:hover {
  background: #fff;
  color: #333;
  cursor: pointer;
}
<body>
	<section class="first">
		<div class="content">
			<div class="copy">
				<h2>Header 1  </h2>
				<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
			</div>
		</div>
	</section>
	<section class="second">
		<div class="content">
			<div class="copy">
				<h2>Header 2</h2>
				<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
			</div>
		</div>
	</section>
	<section class="third">
		<div class="content">
			<div class="copy">
				<h2>Header 3</h2>
				<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
			</div>
		</div>
	</section>
	<section class="fourth">
		<div class="content">
			<div class="copy">
				<h2>Call to action</h2>
				<a class="button">Button</a>
			</div>
		</div>
	</section>
</body>

The parallax effect is achieved using CSS background-attachment: fixed and works just fine; the trouble is with the text blocks. I'd like to keep them "pinned" in place and centered within their section. If they are set to position: fixed they of course overlap each other and all show up in the first section. If they are set to any other position attribute, they will simply scroll like any other element.

Now, I realize that setting an element's position to fixed means it can no longer be relative to its parent element; it escapes the flow so to speak, but I'm trying to determine if there's a way to achieve the effect with some advanced CSS or even a JS alternative.

I've tried numerous HTML/CSS combinations (wrappers within wrappers, etc.) and I've also attempted various javascript solutions such as rellax, jarallax, and ScrollMagic, but everything I've come across is far too robust for my needs. I've searched around for the better part of the day hoping to find an example of what I'm attempting, but no luck.

In a previous question I did a similar effect with image and using some JS so am going to use the same technique to reproduce this using content as I don't think there is a pure CSS solution. So the idea is to simulate the fixed position by using absolute position and adjusting the top property dynamically on scroll.

Here is an example where I also adjusted some of the CSS to make it easier. I will also rely on CSS variables to make the JS code very light so we can manage everything with CSS.

window.onscroll = function() {
  var scroll = window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop;
  document.documentElement.style.setProperty('--scroll-var', scroll + "px");
}
:root {
  --scroll-var: 0px
}

body {
  margin: 0;
  padding: 0;
}

h2 {
  font-size: 48px;
}

p {
  font-size: 18px;
}

section {
  min-height: 100vh;
  width: 100%;
  text-align: center;
  overflow: hidden;
  background-attachment: fixed !important;
  background-size: cover !important;
  background-repeat: no-repeat !important;
  position: relative; /*Mandatory for the overflow effect*/
  height: 100vh;
}

section.first {
  background: linear-gradient(rgba(74, 180, 220, .85), rgba(74, 180, 220, .85)), url(https://picsum.photos/1920/500/?image=1057);
}

section.first .content {
  /* the first section so top start from 0*/
  top: calc((0 * 100vh) + var(--scroll-var));
}

section.second {
  background: linear-gradient(rgba(103, 198, 180, .85), rgba(103, 198, 180, .85)), url(https://picsum.photos/1920/500/?image=1067);
}

section.second .content {
  /* the second section so we need to remove the height of top section
     to have the same position so -100vh and we do the same for the other sections  
  */
  top: calc((-1 * 100vh) + var(--scroll-var));
}

section.third {
  background: linear-gradient(rgba(5, 123, 188, .85), rgba(5, 123, 188, .85)), url(https://picsum.photos/1920/500/?image=1033);
}

section.third .content {
  top: calc((-2 * 100vh) + var(--scroll-var));
}

section.fourth {
  background: linear-gradient(rgba(187, 216, 100, .85), rgba(187, 216, 100, .85)), url(https://picsum.photos/1920/500?image=1063);
}

section.fourth .content {
  top: calc((-3 * 100vh) + var(--scroll-var));
}

.content {
  position: absolute;
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.copy {
  color: #fff;
  font-family: 'Noto Serif', serif;
  font-weight: 300;
  max-width: 300px;
}

.button {
  border: 2px solid #fff;
  border-radius: 3px;
  padding: 15px 25px;
  display: inline-block;
  width: auto;
  font-family: 'Assistant', sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  transition: .2s ease all;
}

.button:hover {
  background: #fff;
  color: #333;
  cursor: pointer;
}
<body>
  <section class="first">
    <div class="content">
      <div class="copy">
        <h2>Header 1 </h2>
        <p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
      </div>
    </div>
  </section>
  <section class="second">
    <div class="content">
      <div class="copy">
        <h2>Header 2</h2>
        <p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
      </div>
    </div>
  </section>
  <section class="third">
    <div class="content">
      <div class="copy">
        <h2>Header 3</h2>
        <p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
      </div>
    </div>
  </section>
  <section class="fourth">
    <div class="content">
      <div class="copy">
        <h2>Call to action</h2>
        <a class="button">Button</a>
      </div>
    </div>
  </section>
</body>


I have put up a little snippet, that works. But you need to figure out the exact math behind positioning yourself. And of course take care of the details

$( document ).ready(function() {
    $(document).scroll(function() {
      // get the position of my first slide, so I know where did I move
      var rect = $(".first")[0].getBoundingClientRect();
      
      // get height of viewport
      var screenHeight = $( window ).height();
      
      // setting offset for every .copy element on page, so they share
      // the same offset from top (are on top of each other)
      // Now you just need to figure out exact math here
      $(".copy").offset({ top: screenHeight*1.5-rect.bottom});
    });
});
body {
  margin: 0;
  padding: 0;
}

h2 {
  font-size: 48px;
}

p {
  font-size: 18px;
}

section {
  min-height: 100vh;
  width: 100%;
  text-align: center;
  position: relative;
  background-attachment: fixed !important;
  background-size: cover !important;
  background-repeat: no-repeat !important;
  
  /* added overflow hidden, so that my boxes don't flow out of the slide */
  overflow: hidden;
}

section.first {
  background: url(https://picsum.photos/1920/500/?image=1057);
}

section.first .content {
  background-color: rgba(74, 180, 220, .85);
}

section.second {
  background: url(https://picsum.photos/1920/500/?image=1067);
}

section.second .content {
  background-color: rgba(103, 198, 180, .85)
}

section.third {
  background: url(https://picsum.photos/1920/500/?image=1033);
}

section.third .content {
  background-color: rgba(5, 123, 188, .85);
}

section.fourth {
  background: url(https://picsum.photos/1920/500?image=1063);
}

section.fourth .content {
  background-color: rgba(187, 216, 100, .85)
}

.content {
  position: relative;
  height: 100vh;
  width: 100%;
  padding: 50px 0;
}

.copy {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #fff;
  font-family: 'Noto Serif', serif;
  font-weight: 300;
}

.button {
  border: 2px solid #fff;
  border-radius: 3px;
  padding: 15px 25px;
  display: inline-block;
  width: auto;
  font-family: 'Assistant', sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  transition: .2s ease all;
}

.button:hover {
  background: #fff;
  color: #333;
  cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
  <section class="first">
    <div class="content">
      <div class="copy">
        <h2>Header 1 </h2>
        <p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
      </div>
    </div>
  </section>
  <section class="second">
    <div class="content">
      <div class="copy">
        <h2>Header 2</h2>
        <p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
      </div>
    </div>
  </section>
  <section class="third">
    <div class="content">
      <div class="copy">
        <h2>Header 3</h2>
        <p>Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
      </div>
    </div>
  </section>
  <section class="fourth">
    <div class="content">
      <div class="copy">
        <h2>Call to action</h2>
        <a class="button">Button</a>
      </div>
    </div>
  </section>
</body>