slidevjs / slidev

Presentation Slides for Developers
https://sli.dev
MIT License
33.49k stars 1.37k forks source link

onSlideEnter triggered repeatedly on the same slide for a component, and in a continuing loop when using requestAnimationFrame(scroll) #1727

Closed gureckis closed 4 months ago

gureckis commented 4 months ago

I wrote a Vue component that scrolls a div automatically onSlideEnter(). The scrolling makes use of requestAnimationFrame. I've noticed that this component calls onSlideEnter() repeatedly rather than only once when the slide is entered/loaded. When I comment out the part of the onSlideEnter()function which uses the requestAnimationFrame stuff it doesn't seem to trigger onSlideEnter repeatedly. But even then ,you can see the function it is being called approximately 4 times on slide load:

image

I created a stacklitz example reproducing the problem here: https://stackblitz.com/edit/github-vyvxjm?file=components%2FCreditScroll.vue

Environment

KermanX commented 4 months ago

The onSlideEnter hook is called multiple times bacause each instance of this component registers its own hook. And there are at least two instances of the component: one in the main page, and one in the QuickOverview panel.

You can use ['slide','presenter'].includes($renderContext.value) to checked whether the instance of the component is rendered in the main page.

However, I am not sure how requestAnimationFrame affects this hook.

gureckis commented 4 months ago

i fixed some of the code in this component and this looping behavior went away.... it remains a mystery but i don't think it needs fixing as is. it must be something more low level than Slidev. 👻

Another piece of strange behavior I noticed with onSlideEnter that might be more straightforward -- onSlideEnter is called sometimes before the onMounted() lifecycle of components inside the slide (possibly the layout too, but i'm not sure). This means these components can't use this new onSlideEnter event reliably. This comes up mostly when reloading the slidedeck on a slide number later in the deck (like reloading on slide 5 when components in slide 5 are expecting to process onSlideEnter). the components do not load yet and so they fail to respond to the onSlideEnter event. If the deck loads from slide 1 then there's time for everything to load before navigating to the new slide.

KermanX commented 4 months ago

onSlideEnter is called sometimes before the onMounted() lifecycle of components inside the slide

The implement of onSlideEnter is:

export function useIsSlideActive() {
  const { $page } = useSlideContext()
  const { currentSlideNo } = useNav()
  return computed(() => $page.value === currentSlideNo.value)
}

export function onSlideEnter(cb: () => void) {
  const isActive = useIsSlideActive()
  watchEffect(() => isActive.value && cb())
}

So it is a watchEffect and the callback can be called before mount. You can use it inside the onMounted hook to make sure this component has been mounted.

onMounted(() => {
  onSlideEnter(() => { ... })
})

If you are using it in a slide layer, a possible workaround is:

import { useIsSlideActive, useSlideContext } from '@slidev/client'
const isActive = useIsSlideActive()
const { $clicksContext } = useSlideContext()
watchEffect(() => {
  if (isActive.value && $clicksContext.isMounted) {
    ....
  }
})
gureckis commented 4 months ago

amazing!! this is so helpful. i hope to pay my debt for all these questions by sharing a fun theme soon!

KermanX commented 4 months ago

I'm curious why would the slide layer need to be treated differently than the onMounted(() =>{ onSlideEnter(cb) } thing?

Because onMounted hook is called when this component is mounted. The slide content may be mounted later that the slide layer. In fact, the second way works both for components used in the slide content and slide layer.