useFullscreen
Hook to handle fullscreen mode
This hook requires the following packages:
screenfull
npm install screenfull
yarn add screenfull
pnpm add screenfull
Example
import { useRef, useState } from "react";
import useFullscreen from "@/hooks/useFullscreen";
export default function UseFullscreenExample() {
const ref = useRef(null);
const [enabled, setEnabled] = useState(false);
const isFullscreen = useFullscreen(ref, enabled);
return (
<div
className="flex flex-col items-center justify-center gap-4 p-4 text-ctp-text"
ref={ref}
>
<p>Fullscreen is {isFullscreen ? "enabled" : "disabled"}.</p>
<div className="flex flex-row items-center justify-center gap-4 p-4">
<button
className="rounded bg-ctp-blue px-4 py-2 text-ctp-base"
onClick={() => setEnabled(!enabled)}
>
Toggle
</button>
</div>
</div>
);
}
Code
import { useEffect, useLayoutEffect, useState } from "react";
import type { RefObject } from "react";
import screenfull from "screenfull";
export interface FullScreenOptions {
video?: RefObject<
HTMLVideoElement & {
webkitEnterFullscreen?: () => void;
webkitExitFullscreen?: () => void;
}
>;
onClose?: (error?: Error) => void;
}
const isBrowser = typeof window !== "undefined";
const isoMorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
/**
* Hook to handle fullscreen mode
* @requires screenfull
* @param {RefObject<Element>} ref - The ref of the element to handle fullscreen
* @param {boolean} enabled - Whether to enable fullscreen mode
* @param {FullScreenOptions} options - The options for the fullscreen mode
* @param {RefObject<HTMLVideoElement>} options.video - The ref of the video element to handle fullscreen
* @param {(error?: Error) => void} options.onClose - The callback to call when the fullscreen mode is closed
* @returns {boolean} - Whether the fullscreen mode is enabled
*/
const useFullscreen = (
ref: RefObject<Element>,
enabled: boolean,
options: FullScreenOptions = {},
): boolean => {
const { video, onClose = () => {} } = options;
const [isFullscreen, setIsFullscreen] = useState(enabled);
isoMorphicLayoutEffect(() => {
if (!enabled) {
return;
}
if (!ref.current) {
return;
}
const onWebkitEndFullscreen = () => {
if (video?.current) {
window.removeEventListener(
"webkitendfullscreen",
onWebkitEndFullscreen,
);
}
onClose();
};
const onChange = () => {
if (screenfull.isEnabled) {
const isScreenfullFullscreen = screenfull.isFullscreen;
setIsFullscreen(isScreenfullFullscreen);
if (!isScreenfullFullscreen) {
onClose();
}
}
};
if (screenfull.isEnabled) {
try {
screenfull.request(ref.current);
setIsFullscreen(true);
} catch (error) {
onClose(error as Error | undefined);
setIsFullscreen(false);
}
screenfull.on("change", onChange);
} else if (video && video.current && video.current.webkitEnterFullscreen) {
video.current.webkitEnterFullscreen();
window.addEventListener("webkitendfullscreen", onWebkitEndFullscreen);
setIsFullscreen(true);
} else {
onClose();
setIsFullscreen(false);
}
return () => {
setIsFullscreen(false);
if (screenfull.isEnabled) {
try {
screenfull.off("change", onChange);
screenfull.exit();
} catch {}
} else if (video && video.current && video.current.webkitExitFullscreen) {
window.removeEventListener(
"webkitendfullscreen",
onWebkitEndFullscreen,
);
video.current.webkitExitFullscreen();
}
};
}, [enabled, video, ref]);
return isFullscreen;
};
export default useFullscreen;