primefaces / primevue

Next Generation Vue UI Component Library
https://primevue.org
MIT License
10.64k stars 1.24k forks source link

AnimateOnScroll: Opacity remains zero if the element is positioned higher on load position #6731

Open rozsazoltan opened 2 weeks ago

rozsazoltan commented 2 weeks ago

Describe the bug

I placed several animated divs on a page. I noticed that if I reload the page in the middle or at the bottom and start scrolling up, the animations don't play. After further inspection, I realized that the animations correctly didn't play since these elements were already scrolled past at load time, but their opacity remained at 0.

DOM result after loading

This component should have already loaded because it's positioned above the reload point, yet it still kept the opacity: 0 setting. That’s why it's not visible.

<div
  class="animate-duration-1000 animate-ease-in-out flex flex-col gap-4 px-8"
  data-pd-animateonscroll="true"
  style="opacity: 0;"
>...</div>

Reproducer

https://stackblitz.com/edit/primevue-4-vite-issue-template-wl17wf?file=src%2FApp.vue

PrimeVue version

4.2.1

Vue version

3.x

Language

TypeScript

Build / Runtime

Vite

Browser(s)

Chrome 130, Edge 130, Firefox 131, Opera 107, Safari 18

Steps to reproduce the behavior

  1. Load a demo with an animated div at the top of the page (this div will never be visible at the very top when scroll to top after refresh). 1-2. Note: There may be animated divs in different places on the page, but when you scroll back to the top (like starting over) and scroll down again, they become visible.
  2. Scroll down.
  3. Refresh the page (the browser will try to load the page where you left off due to caching).
  4. Where you refreshed, animations play, but if you scroll up, you see nothing (these elements are still there, but their opacity is set to 0, so they're invisible).
  5. Scroll down again (some elements will now switch their opacity to 1, but the div at the top of the page will still be invisible).

Check the DOM, the invisible elements are actually only hidden because of a leftover style="opacity: 0;".

https://github.com/user-attachments/assets/c9836578-06be-471b-9a5d-57db981ac954

(I don't know why the enterClass animations aren’t working on the StackBlitz repro, but I'm not concerned with that right now, just that my issue is reproducible.)

Expected behavior

After reloading, all animations above the current position should already be "completed", so I should see every component with style="opacity: 1", not style="opacity: 0".

rozsazoltan commented 2 weeks ago

Due to the fade and other animations, it’s not a solution to override the inline opacity: 0 value with CSS, as this would prevent the transition from 0 to 1.

It's a pretty basic solution, but it works temporarily... On page load, I check which animated elements are above the current scroll position and manually set their opacity value.

import { onMounted } from 'vue'

onMounted(() => {
  // Function to check the visibility of elements
  const checkVisibility = () => {
    // Get all elements with the specified attribute
    const elements = document.querySelectorAll('[data-pd-animateonscroll="true"]')
    const scrollY = window.scrollY || window.pageYOffset // Current scroll position

    elements.forEach(element => {
      const rect = element.getBoundingClientRect() // Get the element's bounding rect
      const elementTop = rect.top + scrollY // Calculate the top position relative to the document

      // Check if the element is in the viewport
      if (elementTop + rect.height < scrollY + window.innerHeight) {
        // Set opacity to 1 if the element is in view
        element.style.opacity = 1
      }
    })
  }

  // value of window.scrollY only updates after the DOM refreshes
  requestAnimationFrame(() => {
    checkVisibility()
  })
})