In ReactJS, how to request/exit FullScreen while also handling the FullScreen events to update the UI?

357 views Asked by At

I have a ReactJS app, and I want to be able to:

  1. Have a button that makes the app go into and out of FullScreen mode, at a specific element.
  2. Detect when the user uses other methods to enter/exit FullScreen mode (like the back button on a mobile device, or the escape key on a desktop).
  3. (Related to point 2) Update the UI to reflect that the user is in fullscreen mode or not.

Is it possible?


I've learned while reading the documentation that there is no support for "fullscreen" event in ReactJS, so one would have to configure such behavior manually.

To make things a bit more complicated, I learned by reading the Mozilla "Guide to the Fullscreen API" documentation that not all browsers use the same functions and events to interact with the fullscreen functionality. There is the "standard" API, the Webkit API, Mozilla API and Microsoft API.

I've looked around at the following Stack Overflow posts and Documentation, and found that there are several questions that answer pieces of this puzzle, but there isn't yet something that puts it all together and fulfills all my requirements at once.

  • The MDN Fullscreen API is a good starting point. It seems that the document element does provide methods to enter and exist the full screen: Element.requestFullscreen() and Document.exitFullscreen(). IT also mentions the existence of the fullscreenchange event. This knowledge can be used for point #1. And this SO answer "Enter and exit full screen in browser using JavaScript" seems to get close to what I want, but it is not a React solution, and lacks support for all browsers.
  • The Microsoft Full Screen API has also a very nice example of how to accomplish the behavior with plain HTML + plain Javascript (but again, it is not the ReactJS solution I'm looking for):
<!DOCTYPE html>

<html>
  <head>
    <title>msFullscreenElement test</title>
    <style>
      /* Add a white border*/
      div {
        border: solid 2px white;
      }
    </style>
  </head>
  <body>
    <div class="fullScreen" id="div-1" style="width:600px; height:350px; background-color:yellow">
      This is Div 1
    </div><p></p>
    <div class="noFullScreen" id="div-2" style="width:600px; height:350px; background-color:red">
       This is Div 2 
    </div>

    <script>
        var inFullScreen = false;  // flag for in or out of full-screen mode. 
        // set up div that doesn't go into full-screen mode on the click event
        var nfsClass = document.getElementsByClassName("noFullScreen");
        for (var i = 0; i < nfsClass.length; i++) {
            nfsClass[i].addEventListener("click", function (evt) {
                evt.target.innerHTML = getFSWindow();
            }, false);
        }

        var fsClass = document.getElementsByClassName("fullScreen");
        for (var i = 0; i < fsClass.length; i++) {
            fsClass[i].addEventListener("click", function (evt) {
            if (inFullScreen == false) {
                makeFullScreen(evt.target); // open to full screen
                evt.target.innerHTML = getFSWindow().id;
              } else {
                reset();
              }
            }, false);
        }
 
        //  request full screen across several browsers
        function makeFullScreen(divObj) {
            if (divObj.requestFullscreen) {
              divObj.requestFullscreen();
            }
            else if (divObj.msRequestFullscreen) {
              divObj.msRequestFullscreen();
            }
            else if (divObj.mozRequestFullScreen) {
              divObj.mozRequestFullScreen();
            }
            else if (divObj.webkitRequestFullscreen) {
              divObj.webkitRequestFullscreen();
            }
            inFullScreen = true;
            return;
        }

        //  reset full screen across several browsers
        function reset() {
            if (document.exitFullscreen) {
                document.exitFullscreen();
            }
            else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            }
            else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            }
            else if (document.webkitCancelFullScreen) {
                document.webkitCancelFullScreen();
            }
            inFullScreen = false;
            return;
        }

        //  get full screen element across several browsers
        function getFSWindow() {
            if (document.fullscreenElement) {
                return document.fullscreenElement;
            }
            else if (document.msFullscreenElement) {
                return document.msFullscreenElement;
            }
            else if (document.mozFullScreenElement) {
                return document.mozFullScreenElement;
            }
            else if (document.webkitFullscreenElement) {
                return document.webkitFullscreenElement;
            }
        }
    </script>
  </body>
</html>
  • The "Document: fullscreenchange event", I've learned that one can listen to the fullscreenchange event. So it seems quite a good starting point for requirement #2.
  • HTML5 Fullscreen Event Listener: This seems like an interesting answer, because it reveals that there are different ways browsers might trigger the event ("fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange" and "msfullscreenchange")).
  • Is fullscreenchange event supported in React?: This is the closes answer I found that would accomplish what I need. It shows how to use React's useLayoutEffect to override(?) the document.onfullscreenchange event, but I want to add an event listener, not to modify things in a hacky way. So, I want a better solution.
  • And again, the Microsoft Full Screen API has a very nice example of how to "Detect full-screen mode changes" with plain HTML + plain Javascript (yet again, this is not a full ReactJS solution, and doesn't cover all browsers):
if (document.requestFullscreen) {
  document.addEventListener("fullscreenchange", function () {
    if (document.fullscreenElement != null) {
      console.info("Went full screen");
    } else {
      console.info("Exited full screen");
    }
  });
}
else if (document.msRequestFullscreen) {
  document.addEventListener("MSFullscreenChange", function () {
    if (document.msFullscreenElement != null) {
      console.info("Went full screen");
    } else {
      console.info("Exited full screen");
    }
  });
}

So, is there a way to combine all the pieces of information into a simple solution that fulfils my objectives with ReactJS?

1

There are 1 answers

0
cavpollo On

The solution I found is far from perfect, and definitely not simple, but it was the best I was able to come up with my limited ReactJS experience.

So, having said that, let's dive into it.

The UI

First, let's build the UI. Leaving all the boilerplate HTML, we will need:

  • An element onto which we can to "fullscreen" to (id="app").
  • An element that we can click to trigger the full screen events (the <button>).
  • And optionally, an element where we can indicate to the user what is the full screen status. I've decided to add the feedback to the button's text for simplicity.
<div id="app">
  <button onClick={toggleFullScreen}>
    Click me to { isFulllScreen ? 'exit' : 'enter' } FullScreen
  </button>
    ...
</div>
...

So the goal is that when the <button> is clicked, the browser fullscreens into the app <div> element, or out of it.

Then whenever the user enters or exits FullScreen, the buttons text should be updated regardless of how it happened.

Helper functions for the FullScreen API

Now let's make use of the FullScreen API. We'll need helper functions to interact with the API on different browsers (you can read more about it on the Guide to the Fullscreen API):

  • A function to check which element has been "fullscreened".
  • A function to retrieve the right FullScreen Change event.
  • A function to retrieve the right FullScreen Request method.
  • And a function to retrieve the right FullScreen Exit method.

This is how the helper functions will look like:

const getFullScreenElement = (() => {
  if (document.fullscreenEnabled) {
    return document.fullscreenElement;
  } else if (document.webkitFullscreenEnabled) {
    return document.webkitFullscreenElement;
  } else if (document.mozFullScreenEnabled) {
    return document.mozFullScreenElement;
  } else if (document.msFullscreenEnabled) {
    return document.msFullscreenElement;
  } else {
    return;
  }
});

const hasEvent = ((contentElement, eventName) => {
  for (const key in contentElement) {
    if (eventName === key) {
      return true;
    }
  }
  return false;
});

const getFullScreenChangeEvent = ((contentElement) => {
  if (document.fullscreenEnabled && hasEvent(contentElement, 'onfullscreenchange')) {
    return 'fullscreenchange';
  } else if (document.webkitFullscreenEnabled && hasEvent(contentElement, 'onwebkitfullscreenchange')) {
    return 'webkitfullscreenchange';
  } else if (document.mozFullScreenEnabled && hasEvent(contentElement, 'onmozfullscreenchange')) {
    return 'mozfullscreenchange';
  } else if (document.msFullscreenEnabled && hasEvent(contentElement, 'onmsfullscreenchange')) {
    return 'msfullscreenchange';
  } else {
    return;
  }
});

const getFullScreenCancelMethod = (() => {
  if (document.fullscreenEnabled && document.exitFullscreen) {
    return document.exitFullscreen;
  } else if (document.webkitFullscreenEnabled && document.webkitExitFullscreen) {
    return document.webkitExitFullscreen;
  } else if (document.mozFullScreenEnabled && document.mozCancelFullScreen) {
    return document.mozCancelFullScreen;
  } else if (document.msFullscreenEnabled && document.msExitFullscreen) {
    return document.msExitFullscreen;
  } else {
    return;
  }
});

const getFullScreenRequestMethod = ((contentElement) => {
  if (document.fullscreenEnabled && contentElement.requestFullscreen) {
    return contentElement.requestFullscreen;
  } else if (document.webkitFullscreenEnabled && contentElement.webkitRequestFullscreen) {
    return contentElement.webkitRequestFullscreen;
  } else if (document.mozFullScreenEnabled && contentElement.mozRequestFullScreen) {
    return contentElement.mozRequestFullScreen;
  } else if (document.msFullscreenEnabled && contentElement.msRequestFullscreen) {
    return contentElement.msRequestFullscreen;
  } else {
    return;
  }
});

Notice how we check for all variants of the FullScreen events (normal, webkit, moz and ms), not all of them capitalize the word Screen, and the function to exist the FullScreen may have different names.

React JS: Toggle FullScreen Button

Now with the above out of the way, we can finally begin playing with the ReactJS logic. Let's start with the button's click function. We need:

  • A React State object (isFullScreen) that tracks if the page is in FullScreen mode.
  • A toggleFullScreen function that is compatible with React's States. We'll use React useCallback. It must: ** Enter FullScreen mode on the selected app element if isFullScreen is not true. ** Exit FullScreen mode if isFullScreen is true.

The code would look like this:

const [isFullScreen, _] = useState(false);

const toggleFullScreen = useCallback(() => {
  if (isFullScreen) {
    const requestMethod = getFullScreenCancelMethod();
    if (requestMethod) {
      requestMethod.call(document);
    }
  } else {
    const contentElement = document.getElementById('app');
    const requestMethod = getFullScreenRequestMethod(contentElement);
    if (requestMethod) {
        requestMethod.call(contentElement);
    }
  }
}, [isFullScreen]);

React JS: Listen for FullScreen changes

Finally, it is time to track the FullScreen status. For that we'll need:

  • Something that registers a listener to the FullScreen change event, but only once. For this we'll use React's useEffect.
  • A React Ref to make sure we don't register a listener to the FullScreen change event multiple times.
  • And the FullScreen change event listener, which updates a React State (see isFulllScreen in the previous step).

The code would look like this:

const isEventListenerConnected = useRef(false);
const [isFullScreen, setFullScreen] = useState(false);

const fullScreenChangeListener = ((setFullScreen) => {
  const isFullScreenActive = getFullScreenElement() != null;
  setFullScreen(isFullScreenActive);
});

useEffect(() => {
  if (!isEventListenerConnected.current) {
    let contentElement = document.getElementById('app');
    if (contentElement) {

      let eventName = getFullScreenChangeEvent(contentElement);
      if (eventName) {
        contentElement.addEventListener(eventName, () => fullScreenChangeListener(setFullScreen));
      }

      isEventListenerConnected.current = true;
    }
  }
}, [isEventListenerConnected, setFullScreen]);

Final solution

When it is all put together, it looks like this:

import { useState, useCallback, useEffect, useRef } from 'react';

function App() {
  const isEventListenerConnected = useRef(false);
  const [isFullScreen, setFullScreen] = useState(false);


  const getFullScreenElement = (() => {
    if (document.fullscreenEnabled) {
      return document.fullscreenElement;
    } else if (document.webkitFullscreenEnabled) {
      return document.webkitFullscreenElement;
    } else if (document.mozFullScreenEnabled) {
      return document.mozFullScreenElement;
    } else if (document.msFullscreenEnabled) {
      return document.msFullscreenElement;
    } else {
      return;
    }
  });

  const hasEvent = ((contentElement, eventName) => {
    for (const key in contentElement) {
      if (eventName === key) {
        return true;
      }
    }
    return false;
  });

  const getFullScreenChangeEvent = ((contentElement) => {
    if (document.fullscreenEnabled && hasEvent(contentElement, 'onfullscreenchange')) {
      return 'fullscreenchange';
    } else if (document.webkitFullscreenEnabled && hasEvent(contentElement, 'onwebkitfullscreenchange')) {
      return 'webkitfullscreenchange';
    } else if (document.mozFullScreenEnabled && hasEvent(contentElement, 'onmozfullscreenchange')) {
      return 'mozfullscreenchange';
    } else if (document.msFullscreenEnabled && hasEvent(contentElement, 'onmsfullscreenchange')) {
      return 'msfullscreenchange';
    } else {
      return;
    }
  });

  const getFullScreenCancelMethod = (() => {
    if (document.fullscreenEnabled && document.exitFullscreen) {
      return document.exitFullscreen;
    } else if (document.webkitFullscreenEnabled && document.webkitExitFullscreen) {
      return document.webkitExitFullscreen;
    } else if (document.mozFullScreenEnabled && document.mozCancelFullScreen) {
      return document.mozCancelFullScreen;
    } else if (document.msFullscreenEnabled && document.msExitFullscreen) {
      return document.msExitFullscreen;
    } else {
      return;
    }
  });

  const getFullScreenRequestMethod = ((contentElement) => {
    if (document.fullscreenEnabled && contentElement.requestFullscreen) {
      return contentElement.requestFullscreen;
    } else if (document.webkitFullscreenEnabled && contentElement.webkitRequestFullscreen) {
      return contentElement.webkitRequestFullscreen;
    } else if (document.mozFullScreenEnabled && contentElement.mozRequestFullScreen) {
      return contentElement.mozRequestFullScreen;
    } else if (document.msFullscreenEnabled && contentElement.msRequestFullscreen) {
      return contentElement.msRequestFullscreen;
    } else {
      return;
    }
  });

  const fullScreenChangeListener = ((setFullScreen) => {
    const isFullScreenActive = getFullScreenElement() != null;
    setFullScreen(isFullScreenActive);
  });

  useEffect(() => {
    if (!isEventListenerConnected.current) {
      let contentElement = document.getElementById('app');
      if (contentElement) {

        let eventName = getFullScreenChangeEvent(contentElement);
        if (eventName) {
          contentElement.addEventListener(eventName, () => fullScreenChangeListener(setFullScreen));
        }

        isEventListenerConnected.current = true;
      }
    }
  }, [isEventListenerConnected, setFullScreen]);


  const toggleFullScreen = useCallback(() => {
    if (isFullScreen) {
      const requestMethod = getFullScreenCancelMethod();
      if (requestMethod) {
        requestMethod.call(document);
      }
    } else {
      const contentElement = document.getElementById('app');
      const requestMethod = getFullScreenRequestMethod(contentElement);
      if (requestMethod) {
          requestMethod.call(contentElement);
      }
    }
  }, [isFullScreen]);

  return (
    <div id="app">
      <button onClick={toggleFullScreen}>Click me to { isFulllScreen ? 'exit' : 'enter' } FullScreen</button>
    </div>
  );
}

This sample does not take care of handling unhappy scenarios (i.e. FullScreen being disabled), so be sure to check documentation and implement those yourself as you see fit. :)