Moshe Klein
Moshe Klein

Reputation: 1

window.postMessage({ trigger: "startCounting" }, "*"); does not trigger

i have a rolodex counter that i need at times to trigger the animation via posting a message

it does not work, instead of the two digits animating counting, only a single digits flickers without ending.

even on the console window.postMessage({ trigger: "startCounting" }, "*"); generates the same unwanted effect

i tried manipulating the animation, simplifying various actions all yielded the same result the code snippet

console.log("Version 1.77 - Explicit Viewport Trigger Integration");

class RolodexCounterElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  // Specify the observed attributes
  static get observedAttributes() {
    return ["start-value", "end-value", "duration", "font-size", "font-family", "font-weight", "color"];
  }

  // Handle attribute changes
  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`Attribute changed: ${name} from ${oldValue} to ${newValue}`);
    if (oldValue !== newValue) {
      this.updateComponent(); // Re-render component with new attributes
    }
  }

  connectedCallback() {
    console.log("Rolodex Counter connected to DOM");

    // Initial render
    this.updateComponent();

    // Elements
    const numbersContainer = this.shadowRoot.querySelector(".frm-elmnt-numbers");

    // Intersection Observer for viewport trigger
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          console.log("Element entered viewport: triggering animation");
          numbersContainer.classList.add("animation");
        } else {
          numbersContainer.classList.remove("animation");
        }
      });
    });

    // Start observing the custom element
    observer.observe(this);

    // Debugging trigger for manual testing
    window.addEventListener("message", (event) => {
      if (event.data.trigger === "startCounting") {
        console.log("Manual trigger received");
        numbersContainer.classList.add("animation");
      }
    });
  }

  updateComponent() {
    const startValue = parseInt(this.getAttribute("start-value")) || 0;
    const endValue = parseInt(this.getAttribute("end-value")) || 99;
    const duration = parseInt(this.getAttribute("duration")) || 4000;
    const fontSize = this.getAttribute("font-size") || "7.5rem";
    const fontFamily = this.getAttribute("font-family") || "'Graphik', sans-serif";
    const fontWeight = this.getAttribute("font-weight") || "300";
    const color = this.getAttribute("color") || "#2e2e38";

    console.log("Attributes:", { startValue, endValue, duration, fontSize, fontFamily, fontWeight, color });

    // Template
    const template = `
            <div class="frm-elmnt-counter">
                <div class="frm-elmnt-numbers-wrapper">
                    <div class="frm-elmnt-numbers"></div>
                </div>
                <div class="frm-elmnt-percent">%</div>
            </div>
            <style>
                :host {
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    width: auto;
                    max-width: 100%;
                    height: auto;
                    max-height: 100%;
                    background: transparent;
                    overflow: hidden;
                    box-sizing: border-box;
                }
                .frm-elmnt-counter {
                    display: flex;
                    align-items: baseline;
                    font-size: ${fontSize};
                    font-weight: ${fontWeight};
                    font-family: ${fontFamily};
                    color: ${color};
                    line-height: 1;
                    overflow: hidden;
                }
                .frm-elmnt-numbers-wrapper {
                    height: ${fontSize};
                    overflow: hidden;
                    position: relative;
                }
                .frm-elmnt-numbers {
                    display: flex;
                    flex-direction: column;
                    transform: translateY(0);
                    will-change: transform;
                }
                .frm-elmnt-numbers.animation {
                    animation: frm-elmnt-roll-animation ${duration}ms ease-out forwards;
                }
                .frm-elmnt-percent {
                    margin-left: 0.1em;
                    font-weight: ${fontWeight};
                    display: inline-block;
                    line-height: 1;
                    position: relative;
                    color: ${color};
                    font-size: ${fontSize};
                    font-family: ${fontFamily};
                    margin-left: 0.05em; /* Adjust negative margin */
                    padding-left: 0.05em;
                    letter-spacing: -0.005em;
                }
                @keyframes frm-elmnt-roll-animation {
                    0% {
                        transform: translateY(0);
                    }
                    85% {
                        transform: translateY(var(--final-position));
                    }
                    90% {
                        transform: translateY(var(--overshoot-position));
                    }
                    95% {
                        transform: translateY(var(--final-position));
                    }
                    100% {
                        transform: translateY(var(--final-position));
                    }
                }
            </style>
        `;

    this.shadowRoot.innerHTML = template;

    // Elements
    const numbersContainer = this.shadowRoot.querySelector(".frm-elmnt-numbers");
    const percentElement = this.shadowRoot.querySelector(".frm-elmnt-percent");

    // Generate Numbers
    const steps = endValue - startValue + 1;
    const numbers = Array.from({ length: steps }, (_, i) => startValue + i);
    numbersContainer.innerHTML = numbers.map((num) => `<span>${num}</span>`).join("");

    console.log("Generated numbers:", numbers);

    // Calculate Positions
    const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
    const lineHeight = parseFloat(getComputedStyle(numbersContainer).fontSize);
    const remConversion = lineHeight / rootFontSize;
    const finalPosition = `-${(steps - 1) * remConversion}rem`;
    const overshootPosition = `-${(steps - 1) * remConversion + 0.3}rem`;

    numbersContainer.style.setProperty("--final-position", finalPosition);
    numbersContainer.style.setProperty("--overshoot-position", overshootPosition);

    // Align Percent Sign
    const enforceAlignment = () => {
      const numbersHeight = numbersContainer.offsetHeight;
      const percentHeight = percentElement.offsetHeight;
      const topAdjustment = Math.min((numbersHeight - percentHeight) / 2, 0);
      percentElement.style.top = `${topAdjustment}px`;
    };

    enforceAlignment();
    window.addEventListener("resize", enforceAlignment);
  }
}

customElements.define("rolodex-counter-element", RolodexCounterElement);

Upvotes: 0

Views: 27

Answers (1)

KLASANGUI
KLASANGUI

Reputation: 1144

I took a look at your code and managed to achieve the behavior you want.

I made a slight change to a default value and used a click event to trigger the animation instead of a message...

console.log("Version 1.77 - Explicit Viewport Trigger Integration");

class RolodexCounterElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  // Specify the observed attributes
  static get observedAttributes() {
    return ["start-value", "end-value", "duration", "font-size", "font-family", "font-weight", "color"];
  }

  // Handle attribute changes
  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`Attribute changed: ${name} from ${oldValue} to ${newValue}`);
    if (oldValue !== newValue) {
      this.updateComponent(); // Re-render component with new attributes
    }
  }

  connectedCallback() {
    console.log("Rolodex Counter connected to DOM");

    // Initial render
    this.updateComponent();

    // Elements
    const numbersContainer = this.shadowRoot.querySelector(".frm-elmnt-numbers");

    // Intersection Observer for viewport trigger
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          console.log("Element entered viewport: triggering animation");
          numbersContainer.classList.add("animation");
        } else {
          numbersContainer.classList.remove("animation");
        }
      });
    });

    // Start observing the custom element
    observer.observe(this);

    // Debugging trigger for manual testing
    window.addEventListener("message", (event) => {
      if (event.data.trigger === "startCounting") {
        console.log("Manual trigger received");
        numbersContainer.classList.add("animation");
      }
    });
  }

  updateComponent() {
    const startValue = parseInt(this.getAttribute("start-value")) || 0;
    const endValue = parseInt(this.getAttribute("end-value")) || 0; // Changed from 99 to 0
    const duration = parseInt(this.getAttribute("duration")) || 4000;
    const fontSize = this.getAttribute("font-size") || "7.5rem";
    const fontFamily = this.getAttribute("font-family") || "'Graphik', sans-serif";
    const fontWeight = this.getAttribute("font-weight") || "300";
    const color = this.getAttribute("color") || "#2e2e38";

    console.log("Attributes:", { startValue, endValue, duration, fontSize, fontFamily, fontWeight, color });

    // Template
    const template = `
            <div class="frm-elmnt-counter">
                <div class="frm-elmnt-numbers-wrapper">
                    <div class="frm-elmnt-numbers"></div>
                </div>
                <div class="frm-elmnt-percent">%</div>
            </div>
            <style>
                :host {
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    width: auto;
                    max-width: 100%;
                    height: auto;
                    max-height: 100%;
                    background: transparent;
                    overflow: hidden;
                    box-sizing: border-box;
                }
                .frm-elmnt-counter {
                    display: flex;
                    align-items: baseline;
                    font-size: ${fontSize};
                    font-weight: ${fontWeight};
                    font-family: ${fontFamily};
                    color: ${color};
                    line-height: 1;
                    overflow: hidden;
                }
                .frm-elmnt-numbers-wrapper {
                    height: ${fontSize};
                    overflow: hidden;
                    position: relative;
                }
                .frm-elmnt-numbers {
                    display: flex;
                    flex-direction: column;
                    transform: translateY(0);
                    will-change: transform;
                }
                .frm-elmnt-numbers.animation {
                    animation: frm-elmnt-roll-animation ${duration}ms ease-out forwards;
                }
                .frm-elmnt-percent {
                    margin-left: 0.1em;
                    font-weight: ${fontWeight};
                    display: inline-block;
                    line-height: 1;
                    position: relative;
                    color: ${color};
                    font-size: ${fontSize};
                    font-family: ${fontFamily};
                    margin-left: 0.05em; /* Adjust negative margin */
                    padding-left: 0.05em;
                    letter-spacing: -0.005em;
                }
                @keyframes frm-elmnt-roll-animation {
                    0% {
                        transform: translateY(0);
                    }
                    85% {
                        transform: translateY(var(--final-position));
                    }
                    90% {
                        transform: translateY(var(--overshoot-position));
                    }
                    95% {
                        transform: translateY(var(--final-position));
                    }
                    100% {
                        transform: translateY(var(--final-position));
                    }
                }
            </style>
        `;

    this.shadowRoot.innerHTML = template;

    // Elements
    const numbersContainer = this.shadowRoot.querySelector(".frm-elmnt-numbers");
    const percentElement = this.shadowRoot.querySelector(".frm-elmnt-percent");

    // Generate Numbers
    const steps = endValue - startValue + 1;
    const numbers = Array.from({ length: steps }, (_, i) => startValue + i);
    numbersContainer.innerHTML = numbers.map((num) => `<span>${num}</span>`).join("");

    console.log("Generated numbers:", numbers);

    // Calculate Positions
    const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
    const lineHeight = parseFloat(getComputedStyle(numbersContainer).fontSize);
    const remConversion = lineHeight / rootFontSize;
    const finalPosition = `-${(steps - 1) * remConversion}rem`;
    const overshootPosition = `-${(steps - 1) * remConversion + 0.3}rem`;

    numbersContainer.style.setProperty("--final-position", finalPosition);
    numbersContainer.style.setProperty("--overshoot-position", overshootPosition);

    // Align Percent Sign
    const enforceAlignment = () => {
      const numbersHeight = numbersContainer.offsetHeight;
      const percentHeight = percentElement.offsetHeight;
      const topAdjustment = Math.min((numbersHeight - percentHeight) / 2, 0);
      percentElement.style.top = `${topAdjustment}px`;
    };

    enforceAlignment();
    window.addEventListener("resize", enforceAlignment);
  }
}

// Added this event listener that can be attached to 'message' or anything else.
actionButton.addEventListener
(
  // Triggering the stuff on click.
  'click', clickEvent => (rolodexCounter.setAttribute('end-value', 100), rolodexCounter.connectedCallback())
);

customElements.define("rolodex-counter-element", RolodexCounterElement);
<rolodex-counter-element id="rolodexCounter" start-value="0" end-value="0"></rolodex-counter-element>
<button id="actionButton">Trigger 100%</button>

You can actually trigger the animation multiple times, even if the animation is still unfinished.

You just need to change to a 'message' event listener instead, change the element properties according to the event data and then trigger the animation like I did in the example above.

Upvotes: 0

Related Questions