hakimel / reveal.js

The HTML Presentation Framework
https://revealjs.com
MIT License
67.9k stars 16.64k forks source link

Incorporating reveal.js into a react project? #2784

Closed princefishthrower closed 4 years ago

princefishthrower commented 4 years ago

Searched the open and closed issues, nobody has seemed to have done this or asked about this yet. Does anyone in the community have experience with using reveal.js in a react project or know a decent react wrapper for it? (I found some old react wrappers for reveal.js but most haven't seen any changes in the last 4+ years!)

princefishthrower commented 4 years ago

Actually, it works! (At an initial try. Currently, I'm having trouble with getting the slide transition animations to work, but hopefully, it is just a CSS conflict). Here's a most basic react component with hooks example (note that I moved the styles in locally as a quick fix since frameworks like create-react-app do not allow imports from outside the src/ directory!)

import * as React from "react";
import Reveal from 'reveal.js';

import '../../styles/reveal.js/reset.css';
import '../../styles/reveal.js/reveal.css';
import '../../styles/reveal.js/theme/white.css';

export function Presentation() {

    React.useEffect(() => {
        let deck = new Reveal({
            backgroundTransition: 'slide',
            transition: 'slide'
         })
         deck.initialize();
         console.log('initialized fool')
    },[])

    return (
        <div className="reveal">
            {/* Any section element inside of this container is displayed as a slide */}
            <div className="slides" data-transition="slide">
                <section data-transition="slide">
                    <a href="https://revealjs.com">
                        <img
                            src="https://static.slid.es/reveal/logo-v1/reveal-white-text.svg"
                            alt="reveal.js logo"
                            style={{
                                height: "180px",
                                margin: "0 auto 4rem auto",
                                background: "transparent",
                            }}
                            className="demo-logo"
                        />
                    </a>
                    <h3>The HTML Presentation Framework</h3>
                </section>

                <section data-transition="slide">
                    <h2>Hello There</h2>
                    <p>
                        reveal.js enables you to create beautiful interactive
                        slide decks using HTML. This presentation will show you
                        examples of what it can do.
                    </p>
                </section>

                <section data-transition="slide">
                    <h2>Marvelous List</h2>
                    <ul>
                        <li>No order here</li>
                        <li>Or here</li>
                        <li>Or here</li>
                        <li>Or here</li>
                    </ul>
                </section>

                <section data-transition="slide">
                    <h2>Fantastic Ordered List</h2>
                    <ol>
                        <li>One is smaller than...</li>
                        <li>Two is smaller than...</li>
                        <li>Three!</li>
                    </ol>
                </section>
            </div>
        </div>
    );
}
princefishthrower commented 4 years ago

Also helpful for anyone who comes here. We were also using Bootstrap for our styling, this solution https://github.com/hakimel/reveal.js/issues/1515#issuecomment-432471954 solved the transition issue I mentioned above!

All is well in reveal.js + react.js land!

simonbarker commented 4 years ago

i can't get this to work in a react project. it inits fine but the content just sits on the page as if Reveal can't access the dom

hyunwoona commented 3 years ago

@simonbarker I had the same problem but got this working. I had to set the width and height of .reveal.

DQinYuan commented 2 years ago

I have an error:

export 'default' (imported as 'Reveal') was not found in 'reveal.js'

anyone know why?

SumukhJadhav commented 2 years ago

Yes even I'm having similar issues @DQinYuan

pinkboid commented 1 year ago

Is there really no other documentation on how to do this? Pasted the code example shown above, not working...

kenneho commented 1 year ago

Maybe the walkthrough over at 3488 might be of help.

ImJimmi commented 12 months ago

I got this working by having

<div id="root" class="reveal">

in my index.html, and then

function App() {
    useEffect(() => {
        let deck = new Reveal({
            backgroundTransition: "slide",
            transition: "slide",
        });
        deck.initialize();
    });

    return (
        <div class="slides">
            <section>Slide 1</section>
            <section>Slide 2</section>
        </div>
    );
}

in App.js.

Having the class="reveal" in App() didn't seem to work for whatever reason.

t-fritsch commented 12 months ago

I haven't tested it but I think that adding an empty array as useEffect's dependencies might ensure the deck is initialized only once (ie. even if App component gets rerendered). Also having the deck in a ref may be useful to later call methods on it (like deck.current.next(), deck.current.getSlide(...), etc. see https://revealjs.com/api/) :

function App() {
    const deck = useRef(null); // keep deck instance in a ref
    useEffect(() => {
        deck.current = new Reveal({
            backgroundTransition: "slide",
            transition: "slide",
        });
        deck.current.initialize();
    }, []); // only launch useEffect at first render

    return (
        <div class="slides">
            <section>Slide 1</section>
            <section>Slide 2</section>
        </div>
    );
}

Edit: corrected the ref manipulation

bouzidanas commented 9 months ago

For anyone who is still interested in being able to put react components inside Reveal slides, here is some solutions

Manual

Say you have a very simple presentation:

<html>
  <head>
    <link rel="stylesheet" href="dist/reveal.css">
    <link rel="stylesheet" href="dist/theme/white.css">
  </head>
  <body>
    <div class="reveal">
      <div class="slides">
        <section>Slide 1</section>
        <section>Slide 2</section>
      </div>
    </div>
    <script src="dist/reveal.js"></script>
    <script>
      Reveal.initialize();
    </script>
  </body>
</html>

The content of the presentation is in the <div class="slides"> element.

React everywhere

If you want to create the contents entirely in React (which you can), then make this div (or preferably the parent div with class="reveal" https://github.com/hakimel/reveal.js/issues/2784#issuecomment-1818063790) the #root of your React App (using ReactDOM.createRoot). Dont forget to add the parts of your presentation's html to your React projects index.html that are needed to get the slides to work. For the example above, you would need to add:

<link rel="stylesheet" href="dist/reveal.css">
<link rel="stylesheet" href="dist/theme/white.css">

to index.html's head element and add the scripts

<script src="dist/reveal.js"></script>
<script>
  Reveal.initialize();
</script>

somewhere at the end of index.html's body element.

React Islands

However, if you want to only sprinkle in components in specific slides, then I would make the react root element a separate element that sits outside of the reveal container div and then use portals to place react component into specific sections.

For a simple Vite project you might end up with an index.html that looks like:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <link rel="stylesheet" href="dist/reveal.css">
    <link rel="stylesheet" href="dist/theme/white.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div class="reveal">
      <div class="slides">
        <section>Slide 1</section>
        <section>Slide 2</section>
      </div>
    </div>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
    <script src="dist/reveal.js"></script>
    <script>
      Reveal.initialize();
    </script>
  </body>
</html>

Paths

There is an issue with the examples provided so far and that is the paths for the reveal links and scripts. You need to make sure they are correct and they depend on how you added Reveal to your project. I would recommend using the npm installation method and then making sure reveal is included in the build (so it ends up in the dist directory).

Programmatic

Instead of editing/manipulating the index.html by hand, you can add and initialize reveal in a react app source file like main.tsx or app.tsx. You can do it globally outside of app/component functions or inside one of them. In the latter case, you have to be careful not to stack initializations. Only initialize a slide deck once. If you need to reconfigure, use the configure function or destroy the deck before initializing again.

To begin, install reveal using npm:

npm install reveal.js

and to get types for typescript

npm i --save-dev @types/reveal.js

imports

You will need the following imports:

import Reveal from "reveal.js";
import "reveal.js/dist/reveal.css";
import "reveal.js/dist/theme/black.css";   // "black" theme

Finally, just execute the code provided in the initialization section of the Reveal.js docs. Make sure to use what is relevant to your case.

@t-fritsch provides a good example just above https://github.com/hakimel/reveal.js/issues/2784#issuecomment-1818433506

Initialization issue

To contribute something to this issue thread that hasnt already been said, I will tackle the re-initialization issue in more detail.

I highly recommend using a ref to the div that has the reveal class. But even when you do, if react is in StrictMode, you will still get the re-initialization problem.

Its very weird issue where the reveal deck is initialized but no flag (like isReady) indicated it has except the changes made by Reveal to the DOM. The latter is what I check to make sure it hasnt been previously initialized. The alternative is to remove StrictMode but I like that less.

So starting from @t-fritsch's example, I would make the following changes:

function App() {
    const deckDivRef = useRef<HTMLDivElement>(null)
    const deckRef = useRef<Reveal.Api>(null); // keep deck instance in a ref

    useEffect(() => {
        const isInitializing = deckDivRef.current?.classList.contains("reveal");
        if (isInitializing) return; // escape useEffect if deckDiv already has "reveal" classes added
        deckDivRef.current.current!.classList.add("reveal"); // add "reveal" class
        deckRef.current = new Reveal(deckDivRef.current!, {
            backgroundTransition: "slide",
            transition: "slide",
        });
        deckRef.current.initialize().then(() => {
            // good place for event handlers and plugin setups
        };

        return () => {
            // code to run on component unmount goes here
            if (!deckRef.current) return;
            try {
                deckRef.current!.destroy();
            } catch (e) {
                console.warn("destroy call failed.");
            }
        };
    }, []); // only launch useEffect at first render

    return (
        <div ref={deckDivRef }>
            <div className="slides">
                <section>Slide 1</section>
                <section>Slide 2</section>
            </div>
        </div>
    );
}

Note that I use the reference to the div element in creating the Reveal constructor and this is important if you want to add multiple decks to the App.

react-reveal-slides

I have created a package called react-reveal-slides for creating reveal presentations entirely in react. It takes care of pretty much everything in the last section for you and allows you to easily create presentations using react components including all of the contents of the presentation. You can make every slide completely dynamic, control the presentation state and more. You can also create multiple presentations.

Example

import { RevealSlides } from "react-reveal-slides"

// Make sure reveal.js is installed with npm for the following imports to work
// Plugins
import RevealNotes from 'reveal.js/plugin/notes/notes';
import RevealZoom from 'reveal.js/plugin/zoom/zoom';

const timeDelta = 1000;

function App() {
  const [firstSlideText, setFirstSlideText] = useState("Create dynamic Reveal.js slides")
  const [presState, setPresState] = useState({"indexh": -1, "indexv": -1, "indexf": -1, "paused": false, "overview": false })
  const [useCustomTheme] = useState(false);
  const [controlsLayout] = useState<"edges" | "bottom-right" | undefined>("edges");

  // The following is just to show that you can reconfigure and control the RevealSlides component and its react content
  useEffect(() => {
    if (!showIntro) return;
    const timer = setTimeout(() => {
      setTheme("none")
    }, 3*timeDelta);

    const timer2 = setTimeout(() => {
      setFirstSlideText("Explore new possibilities thanks to the React framework and ecosystem")
    }, 6*timeDelta);

    const timer2a = setTimeout(() => {
      setPresState({"indexh": 0, "indexv": 1, "indexf": 0, "paused": false, "overview": false });
    }, 9*timeDelta);

    const timer2b = setTimeout(() => {
      setPresState({"indexh": 0, "indexv": 1, "indexf": 1, "paused": false, "overview": false });
    }, 12*timeDelta);

    const timer2c = setTimeout(() => {
      setPresState({"indexh": 0, "indexv": 1, "indexf": 2, "paused": false, "overview": false });
    }, 15*timeDelta);

    return () => {
      clearTimeout(timer);
      clearTimeout(timer2);
      clearTimeout(timer2a);
      clearTimeout(timer2b);
      clearTimeout(timer2c);
    }
  }, []);

return (
  <RevealSlides 
      controlsLayout={controlsLayout} 
      controls={false} 
      presState={presState}  
      theme={theme} 
      plugins={[RevealZoom, RevealNotes]}
      onStateChange={(state)=>console.log(state)} 
  >
      <section key="0" data-background-color="#0c1821">
          <section key="0-0">
              <h2 style={{color: "#E7AD52", marginTop: "-0.5rem"}}>react-reveal-slides</h2>
              <p key={firstSlideText} style={{animation: "fadeIn 500ms ease-in-out", height: "7rem"}}>{firstSlideText}</p>
          </section>
              <section key="0-1">
                  <ul>
                      <li className="fragment">Easily make presentation content dynamic</li>
                      <li className="fragment">Easily add presentations to React apps</li>
                      <li className="fragment">Embed React components inside presentations</li>
                  </ul>
              </section>
          </section>
          <section key="1" data-background-color='#bf4f41'>
              <section key="1-0">
                  <h2 style={{color: "#432534"}}> 
                  Free reign over your presentation
                  </h2>
                  <p>This package makes no efforts to impead or restrict what you can do.</p>
              </section>
              <section key="1-1">
                  <p>Since React creates HTML DOM elements out of JSX, there should be no reason we cant just put JSX inside of our RevealSlides component instead of the HTML markup Reveal.js normally expects.</p>
                  </section>
              <section key="1-2">  
                  <p>Simply put, React already takes care of converting JSX into something Reveal.js can work with.</p>
                  <aside className="notes">
                  Shhh, these are your private notes 📝
                  </aside>
              </section>
          </section>
      </section>
  </RevealSlides>
)

The package is still in dev phase (although everything I just mentioned is working) and has not yet been published to npm. This means you will have to install it using the repo.