FormidableLabs / spectacle

A React-based library for creating sleek presentations using JSX syntax that gives you the ability to live demo your code.
https://commerce.nearform.com/open-source/spectacle/
MIT License
9.74k stars 691 forks source link

unmount slides when not displayed #1022

Open sneakers-the-rat opened 3 years ago

sneakers-the-rat commented 3 years ago

Description

Currently all slides remain in the DOM with display:none when not displayed & the components remain mounted. this means any slides that have components with side effects like animations will continue running, and everything starts going very sloooow.

Proposal

Add a prop to Deck like bufferRange where the content of slides slides +/- that range are mounted in the Dom, but otherwise are unmounted.

sneakers-the-rat commented 3 years ago

since the progress bar is now also rendered for every slide, once you have appreciable slides you end up rendering an n^2 number of elements if you include it -- eg for a slideshow of 40 slides that's 1600 additional elements in the DOM. I haven't profiled but my slideshows are significantly laggier when I have any variant of a progress bar object (the base progress bar included)

sneakers-the-rat commented 3 years ago

it would be nice to be able to render an object that can access the DeckContext once rather than needing to include something on every slide. when I try and use DeckContext at the level of the deck though it has reference errors from unmounted slides. this seems like a pretty common use case (eg. progress bars, contact info, content overlays that persist between slides) that would make me elevate this issue from a feature request to a bug since the performance hit seems to be so massive

sneakers-the-rat commented 3 years ago

This works except for that now all the intra-slide steps are broken, that must be computed when the deck is initially mounted?

https://github.com/sneakers-the-rat/infrastructure-presentation/blob/e78c43ef482aa104cad05b63371fa473d6613696/src/components/hideslide.js#L1-L40

sneakers-the-rat commented 3 years ago

rendering them on first mount of Deck (by adding a || !initialized check) seems to fix the number of steps, and all my custom stepper components work, but now Appear components are broken because they seem to be tied to a specific stepID rather than a step #

edit: confirmed that providing an explicit 'id' to Appear fully fixes and everything is way way way way faster

mhink commented 3 years ago

First off: you're absolutely right about Progress, and I'm kind of annoyed at myself for not catching it. 😅

Secondly: your suggestion absolutely holds water. The problems you're running into most likely have to do with the fact that Slides need to actually render their contents in order to detect "step participants". Moreover, as you've noticed: slides aren't exactly aware of their location in the presentation- they only know that the Deck addresses them by their slide ID. So the solution is more or less to have the Deck keep track of which slides should be rendered and which slides shouldn't, and signal them accordingly.

You could technically implement a fix with some creative usage of DeckContext and your HideSlide component, but it's much easier to implement in-core. So I'm cutting that PR now.

shipplix commented 8 months ago

Here is a simple way to only mount a slide's content if it is active.

First create a wrapper around Slide like this:

import { useContext, useId } from "react";
import { DeckContext, Slide as SpectacleSlide } from "spectacle";

export const Slide = (props: React.ComponentProps<typeof SpectacleSlide>) => {
  const randomId = useId();
  const id = props.id ?? randomId;
  const { activeView } = useContext(DeckContext);
  return (
    <SpectacleSlide id={id}>
      {id === activeView.slideId && props.children}
    </SpectacleSlide>
  );
};

and then use it in your deck like this:

import { Deck, Heading, DefaultTemplate } from "spectacle";
import { Slide } from "./shared";

export const App = () => {
  return (
    <Deck template={<DefaultTemplate />}>
      <Slide>
        <Heading>My Slide</Heading>
        <MyComponent />
      </Slide>
     {/* and so on...*/}
    </Deck>
  );
};

Suggestion: this logic could be added to the Slide component based on isActive flag in https://github.com/FormidableLabs/spectacle/blob/main/packages/spectacle/src/components/slide/slide.tsx#L438 and then add it as optional props to Slide <Slide unmountInactive=true /> or <Deck unmountInactive=true />