<div class="container">
  <p>We want to indicate if there's more to scroll by adding a decreased opacity on the top or bottom element.</p>
  <p>However, if we're at the top, we don't want to decrease the opacity of the first element. And if we're at the bottom, we don't want to decrease the opacity of the last element.</p>
  <p>We'll use <a href="https://caniuse.com/mdn-css_properties_animation-timeline">Scroll Driven Animations</a> for this usecase and <a href="https://caniuse.com/mdn-css_at-rules_property">@property</a>. Chrome only for now. Do not use in production.</p>

  <div class="scroll">
    <p class="message me">Hey!</p>
    <p class="message you">Hey!</p>
    <p class="message me">What's up?</p>
    <p class="message you">All good, you?</p>
    <p class="message me">All good.</p>
    <p class="message you">Cool.</p>
    <p class="message me">Cool.</p>
    <p class="message you">Cool.</p>
    <p class="message me">Cool.</p>
    <p class="message you">Cool.</p>
    <p class="message me">Cool.</p>
    <p class="message you">Cool.</p>
    <p class="message me">Cool.</p>
    <p class="message you">Cool.</p>
    <p class="message me">Cool.</p>
    <p class="message you">👋</p>
    <p class="message me">👋</p>
  </div>
</div>
@supports (animation-timeline: view()) {
  /* The opacity of the element depending on its position in the scroll (near a border : 0.3, far from a border: 1) */
  @property --opacity {
    syntax: "<number>";
    inherits: true;
    initial-value: 1;
  }

  /* Depends on the proximity with the top of the scroll container (nothing to scroll top: 0, more than 3rem to scroll top: 1) */
  @property --top-min-opacity {
    syntax: "<number>";
    inherits: true;
    initial-value: 1;
  }

  /* Depends on the proximity with the top of the scroll container (nothing to scroll bottom: 0, more than 3rem to scroll bottom: 1) */
  @property --bottom-min-opacity {
    syntax: "<number>";
    inherits: true;
    initial-value: 1;
  }

  /*
   * Indicates if the element is in the top half or bottom half of the view (top: 1, bottom: 0)
   * This allows to indicate if it's the top-proximity or the bottom-proximity that should be used
   * to compute the opacity
   */
  @property --is-top {
    syntax: "<number>";
    inherits: true;
    initial-value: 0;
  }

  /* y-distribution is at 0% when element above the scroll view, 100% when below the scroll view */
  @keyframes y-distribution {
    0% { --opacity: 0.3; --is-top: 1 }
    25% { --opacity: 1 }
    50% { --is-top: 1 }
    51% { --is-top: 0 }
    75% { --opacity: 1 }
    100% { --opacity: 0.3; --is-top: 0 }
  }

  /* Based on the first 150px of the scroll */
  @keyframes top-proximity {
    0% { --top-min-opacity: 0 }
    100% { --top-min-opacity: 1 }
  }


  /* Based on the last 150px of the scroll */
  @keyframes bottom-proximity {
    0% { --bottom-min-opacity: 1 }
    100% { --bottom-min-opacity: 0 }
  }

  .message {
	  --min-opacity: var(--is-top) * var(--top-min-opacity)
        + (1 - var(--is-top)) * var(--bottom-min-opacity);
    
    opacity: max(
      var(--opacity),
      var(--min-opacity)
    );
    animation: y-distribution ease-in-out reverse both,
      top-proximity ease-in-out reverse both,
      bottom-proximity ease-in-out reverse both;
    animation-timeline: view(),
      scroll(),
      scroll();
    animation-range: normal,
      0% 3rem,
      calc(100% - 3rem) 100%;
  }
}

/* Global styles */
body {
  min-height: 100vh;
  min-height: 100dvh;
  background: #f0f0f0;
  font-family: system-ui, sans-serif;
  padding-top: 1rem;
}

.container {
  max-width: 30rem;
  margin: 0 auto;
}

.scroll {
  max-height: 500px;
  overflow-y: auto;
  margin-top: 3rem;
}

.message {
  padding: 1rem;
  border-radius: 1rem;
}

.message + .message {
  margin-top: 1rem;
}

.message.you {
  margin-right: 2rem;
  background: white;
}

.message.me {
  margin-left: 2rem;
  background: rgb(207 58 0);
  color: white;
  text-align: right;
}


External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.