Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scroll reveal text animation

I'm working on a webpage where I want text to behave in a specific way when it overflows its container: instead of breaking in the middle of a word, the entire word should move to the next line.

Here's what I've done so far:

I added the following CSS properties to control word breaking:

word-break: keep-all;
overflow-wrap: normal;
word-wrap: break-word;
-webkit-hyphens: auto;
hyphens: auto;

Here's the full snippet of my code, including JavaScript for animation and the HTML structure:

class Lenis {
  constructor(options) {
    this.options = options;
    this.currentScroll = 0;
  }

  raf(time) {
    // Dummy implementation for smooth scroll effect
    this.currentScroll += (window.scrollY - this.currentScroll) * this.options.easing(0.1);
  }

  destroy() {
    // Clean-up if necessary
  }
}

function splitText(element) {
  const text = element.textContent;
  element.textContent = "";
  const wrapper = document.createElement("div");
  wrapper.style.display = "inline-block";
  wrapper.style.width = "100%";

  const spans = [...text].map((char) => {
    const span = document.createElement("span");
    span.textContent = char === " " ? "\u00A0" : char;
    span.style.display = "inline-block";
    wrapper.appendChild(span);
    return span;
  });

  element.appendChild(wrapper);
  return spans;
}

document.addEventListener("DOMContentLoaded", () => {
  // Initialize Lenis for smooth scrolling
  const lenis = new Lenis({
    duration: 1.2,
    easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
    direction: "vertical",
    gestureDirection: "vertical",
    smooth: true,
    smoothTouch: false,
    touchMultiplier: 2,
  });

  function raf(time) {
    lenis.raf(time);
    requestAnimationFrame(raf);
  }

  requestAnimationFrame(raf);

  window.addEventListener("unload", () => lenis.destroy());

  // Reveal text animation
  const revealElements = document.querySelectorAll(".reveal-type");

  revealElements.forEach((element) => {
    const chars = splitText(element);
    const bgColor = element.dataset.bgColor;
    const fgColor = element.dataset.fgColor;

    chars.forEach((char) => {
      char.style.color = bgColor;
    });

    const animateOnScroll = () => {
      const rect = element.getBoundingClientRect();
      const windowHeight = window.innerHeight;
      const elementHeight = rect.height;

      const visibleHeight = windowHeight - rect.top;
      const progressLength = elementHeight * 1.2;

      if (visibleHeight <= 0) return;

      let progress = visibleHeight / progressLength;
      progress = Math.max(0, Math.min(1, progress));

      const charsToColor = Math.floor(progress * chars.length);

      chars.forEach((char, index) => {
        if (index < charsToColor) {
          char.style.color = fgColor;
        } else {
          char.style.color = bgColor;
        }
      });
    };

    animateOnScroll();
    window.addEventListener("scroll", animateOnScroll);

    window.addEventListener("unload", () => {
      window.removeEventListener("scroll", animateOnScroll);
    });
  });
});
body {
  margin: 0;
  font-family: "Inter", sans-serif;
  background: rgb(5, 4, 4);
}

section {
  min-height: auto;
  margin: 2rem auto;
  padding: 2rem clamp(1rem, 5vw, 4rem);
  display: grid;
  place-content: center;
  box-sizing: border-box;
}
section p {
  font-size: clamp(1.2rem, 5vw, 9rem);
  word-break: keep-all;
  overflow-wrap: normal;
  word-wrap: break-word;
  -webkit-hyphens: auto;
          hyphens: auto;
}
section:nth-of-type(1) {
  border: 1px solid black;
  height: auto;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: flex-start;
}
section:nth-of-type(1) img {
  max-width: 100%;
  height: auto;
  -o-object-fit: cover;
     object-fit: cover;
}
section:nth-of-type(2) {
  max-width: 100%;
  margin: 0 auto;
  transform: translateX(0);
}/*# sourceMappingURL=main.css.map */
  <section>
    <img src="example" alt="Thumbnail">
  </section>
  <section>
    <p 
      class="reveal-type" 
      data-bg-color="#232323" 
      data-fg-color="white"
    >
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </p>
  </section>
  <section></section>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lenis.min.js"></script>

As you can see, i already add

      word-break: keep-all;
  overflow-wrap: normal;
  word-wrap: break-word;
  -webkit-hyphens: auto;
          hyphens: auto;

The Problem

Despite adding these CSS properties, the words are still breaking in the middle and wrapping to the next line. I expected the whole word to move to the next line if it doesn't fit

What I Tried

1. Using word-break: keep-all, which, as I understand, should prevent breaking words in the middle.

2. Combining it with overflow-wrap: normal and word-wrap: break-word, thinking it would work for all browsers.

3. Adding hyphens: auto to handle hyphenation gracefully. However, the behavior is still not what I expect. Words keep breaking mid-word.

My Environment

Browser: Arc or edge

Tools: React 19.0.0

like image 744
Rafi rizqullah ramadhan Avatar asked Sep 29 '25 10:09

Rafi rizqullah ramadhan


1 Answers

A different idea using only CSS (for the future as the support is still not good)

body {
  margin: 0;
  font-family: "Inter", sans-serif;
  background: rgb(5, 4, 4);
}

section {
  margin-top: 100vh;
  padding: 2rem clamp(1rem, 5vw, 4rem);
}

section p {
  font-size: clamp(1.2rem, 5vw, 9rem);
}

.reveal-type span {
  background: conic-gradient(#fff 0 0) left no-repeat #232323;
  background-size: 0% 100%;
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: show linear forwards;
  animation-timeline: view(block);
  animation-range:  entry 10% entry-crossing 110%;
}

@keyframes show {
  to {
    background-size: 100% 100%;
  }
}
<section>
  <p class="reveal-type">
   <span>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</span>
  </p>
</section>
like image 132
Temani Afif Avatar answered Oct 02 '25 06:10

Temani Afif



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!