Web APIs
JavaScript Fullscreen API: Complete Immersive Experience Guide
Master the Fullscreen API in JavaScript for creating immersive web experiences. Learn fullscreen controls, events, and cross-browser implementation.
By JavaScriptDoc Team•
fullscreenimmersivevideogamesjavascript
JavaScript Fullscreen API: Complete Immersive Experience Guide
The Fullscreen API enables web applications to display content using the entire screen, creating immersive experiences for videos, games, presentations, and interactive applications.
Understanding the Fullscreen API
The Fullscreen API provides methods to enter and exit fullscreen mode, along with events to track fullscreen state changes.
// Check Fullscreen API support
const checkFullscreenSupport = () => {
const prefixes = ['', 'webkit', 'moz', 'ms'];
for (const prefix of prefixes) {
const property = prefix
? `${prefix}FullscreenEnabled`
: 'fullscreenEnabled';
if (property in document) {
return true;
}
}
return false;
};
console.log('Fullscreen supported:', checkFullscreenSupport());
// Get vendor-prefixed methods
const getFullscreenAPI = () => {
const prefixes = ['', 'webkit', 'moz', 'ms'];
const api = {};
for (const prefix of prefixes) {
if (prefix) {
if (`${prefix}RequestFullscreen` in Element.prototype) {
api.requestFullscreen = `${prefix}RequestFullscreen`;
api.exitFullscreen = `${prefix}ExitFullscreen`;
api.fullscreenElement = `${prefix}FullscreenElement`;
api.fullscreenEnabled = `${prefix}FullscreenEnabled`;
api.fullscreenchange = `${prefix}fullscreenchange`;
api.fullscreenerror = `${prefix}fullscreenerror`;
break;
}
} else {
if ('requestFullscreen' in Element.prototype) {
api.requestFullscreen = 'requestFullscreen';
api.exitFullscreen = 'exitFullscreen';
api.fullscreenElement = 'fullscreenElement';
api.fullscreenEnabled = 'fullscreenEnabled';
api.fullscreenchange = 'fullscreenchange';
api.fullscreenerror = 'fullscreenerror';
break;
}
}
}
return api;
};
const fullscreenAPI = getFullscreenAPI();
console.log('Fullscreen API:', fullscreenAPI);
Basic Fullscreen Operations
Entering and Exiting Fullscreen
class FullscreenManager {
constructor() {
this.api = this.getAPI();
this.isSupported = this.checkSupport();
this.callbacks = new Map();
this.initializeListeners();
}
// Get cross-browser API
getAPI() {
const prefixes = ['', 'webkit', 'moz', 'ms'];
for (const prefix of prefixes) {
const testElement = document.createElement('div');
const methodName = prefix
? `${prefix}RequestFullscreen`
: 'requestFullscreen';
if (methodName in testElement) {
return {
request: methodName,
exit: prefix ? `${prefix}ExitFullscreen` : 'exitFullscreen',
element: prefix ? `${prefix}FullscreenElement` : 'fullscreenElement',
enabled: prefix ? `${prefix}FullscreenEnabled` : 'fullscreenEnabled',
change: prefix ? `${prefix}fullscreenchange` : 'fullscreenchange',
error: prefix ? `${prefix}fullscreenerror` : 'fullscreenerror',
};
}
}
return null;
}
// Check support
checkSupport() {
return this.api && document[this.api.enabled] !== undefined;
}
// Request fullscreen
async requestFullscreen(element, options = {}) {
if (!this.isSupported) {
throw new Error('Fullscreen API is not supported');
}
try {
// Set up pre-fullscreen state
this.beforeFullscreen(element);
// Request fullscreen with options
if (element[this.api.request]) {
await element[this.api.request](options);
} else {
throw new Error('Element does not support fullscreen');
}
// Handle post-fullscreen setup
this.afterFullscreen(element);
return true;
} catch (error) {
console.error('Fullscreen request failed:', error);
this.handleError(error);
throw error;
}
}
// Exit fullscreen
async exitFullscreen() {
if (!this.isFullscreen()) {
return false;
}
try {
await document[this.api.exit]();
return true;
} catch (error) {
console.error('Exit fullscreen failed:', error);
throw error;
}
}
// Toggle fullscreen
async toggleFullscreen(element) {
if (this.isFullscreen()) {
await this.exitFullscreen();
} else {
await this.requestFullscreen(element);
}
}
// Check if in fullscreen
isFullscreen() {
return document[this.api.element] != null;
}
// Get fullscreen element
getFullscreenElement() {
return document[this.api.element];
}
// Before fullscreen setup
beforeFullscreen(element) {
// Store original styles
element._originalStyles = {
position: element.style.position,
width: element.style.width,
height: element.style.height,
zIndex: element.style.zIndex,
};
// Add fullscreen class
element.classList.add('fullscreen-active');
}
// After fullscreen setup
afterFullscreen(element) {
// Focus element for keyboard events
if (element.tabIndex < 0) {
element.tabIndex = 0;
}
element.focus();
}
// Initialize event listeners
initializeListeners() {
if (!this.api) return;
// Fullscreen change event
document.addEventListener(this.api.change, (event) => {
this.handleFullscreenChange(event);
});
// Fullscreen error event
document.addEventListener(this.api.error, (event) => {
this.handleFullscreenError(event);
});
// Keyboard shortcuts
document.addEventListener('keydown', (event) => {
this.handleKeyboard(event);
});
}
// Handle fullscreen change
handleFullscreenChange(event) {
const element = this.getFullscreenElement();
const isFullscreen = this.isFullscreen();
// Notify callbacks
this.notifyCallbacks('change', {
isFullscreen,
element,
event,
});
// Clean up on exit
if (!isFullscreen && event.target._originalStyles) {
Object.assign(event.target.style, event.target._originalStyles);
event.target.classList.remove('fullscreen-active');
delete event.target._originalStyles;
}
}
// Handle fullscreen error
handleFullscreenError(event) {
console.error('Fullscreen error:', event);
this.notifyCallbacks('error', {
error: event,
element: event.target,
});
}
// Handle keyboard events
handleKeyboard(event) {
if (!this.isFullscreen()) return;
// ESC key handling (some browsers handle this automatically)
if (event.key === 'Escape' && this.isFullscreen()) {
this.exitFullscreen();
}
// F11 key handling
if (event.key === 'F11') {
event.preventDefault();
this.exitFullscreen();
}
}
// Subscribe to events
on(event, callback) {
if (!this.callbacks.has(event)) {
this.callbacks.set(event, []);
}
this.callbacks.get(event).push(callback);
return () => {
const callbacks = this.callbacks.get(event);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
};
}
// Notify callbacks
notifyCallbacks(event, data) {
const callbacks = this.callbacks.get(event) || [];
callbacks.forEach((callback) => callback(data));
}
// Handle errors
handleError(error) {
const errorMessages = {
TypeError: 'Invalid element or options provided',
NotAllowedError: 'Fullscreen request must be initiated by user action',
NotSupportedError: 'Fullscreen is not supported on this element',
};
const message = errorMessages[error.name] || error.message;
this.notifyCallbacks('error', {
error: error,
message: message,
});
}
}
// Usage
const fullscreen = new FullscreenManager();
// Listen for fullscreen changes
fullscreen.on('change', ({ isFullscreen, element }) => {
console.log('Fullscreen:', isFullscreen);
console.log('Element:', element);
});
// Request fullscreen on button click
document.getElementById('fullscreenBtn').addEventListener('click', async () => {
const element = document.getElementById('videoPlayer');
try {
await fullscreen.requestFullscreen(element);
} catch (error) {
console.error('Failed to enter fullscreen:', error);
}
});
// Toggle fullscreen
document.addEventListener('dblclick', (event) => {
const element = event.target.closest('.fullscreen-capable');
if (element) {
fullscreen.toggleFullscreen(element);
}
});
Advanced Fullscreen Features
Fullscreen with Controls
class FullscreenController {
constructor(element, options = {}) {
this.element = element;
this.options = {
showControls: true,
autoHideControls: true,
controlsTimeout: 3000,
showOnHover: true,
customControls: null,
...options,
};
this.fullscreen = new FullscreenManager();
this.controls = null;
this.hideTimeout = null;
this.isControlsVisible = false;
this.init();
}
// Initialize controller
init() {
this.createControls();
this.attachEventListeners();
this.setupKeyboardShortcuts();
}
// Create control interface
createControls() {
if (this.options.customControls) {
this.controls = this.options.customControls;
} else {
this.controls = this.createDefaultControls();
}
// Add controls to element
this.element.style.position = 'relative';
this.element.appendChild(this.controls);
// Initially hide controls
this.hideControls(false);
}
// Create default controls
createDefaultControls() {
const controls = document.createElement('div');
controls.className = 'fullscreen-controls';
controls.innerHTML = `
<div class="controls-bar">
<button class="control-btn play-pause" title="Play/Pause">
<span class="icon-play">▶</span>
<span class="icon-pause" style="display:none">❚❚</span>
</button>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-filled"></div>
<div class="progress-handle"></div>
</div>
</div>
<div class="time-display">
<span class="current-time">0:00</span>
<span class="separator">/</span>
<span class="total-time">0:00</span>
</div>
<button class="control-btn volume" title="Volume">
<span class="icon-volume">🔊</span>
</button>
<input type="range" class="volume-slider" min="0" max="100" value="100">
<button class="control-btn fullscreen-toggle" title="Toggle Fullscreen">
<span class="icon-enter">⛶</span>
<span class="icon-exit" style="display:none">✕</span>
</button>
</div>
`;
// Apply styles
this.applyControlStyles(controls);
return controls;
}
// Apply control styles
applyControlStyles(controls) {
const style = document.createElement('style');
style.textContent = `
.fullscreen-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.7));
padding: 20px 10px 10px;
transition: opacity 0.3s, transform 0.3s;
z-index: 1000;
}
.fullscreen-controls.hidden {
opacity: 0;
transform: translateY(100%);
pointer-events: none;
}
.controls-bar {
display: flex;
align-items: center;
gap: 10px;
color: white;
}
.control-btn {
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 20px;
padding: 5px 10px;
transition: transform 0.2s;
}
.control-btn:hover {
transform: scale(1.1);
}
.progress-container {
flex: 1;
height: 5px;
background: rgba(255,255,255,0.3);
border-radius: 5px;
cursor: pointer;
position: relative;
}
.progress-filled {
height: 100%;
background: #fff;
border-radius: 5px;
width: 0%;
transition: width 0.1s;
}
.progress-handle {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 15px;
height: 15px;
background: white;
border-radius: 50%;
left: 0%;
cursor: grab;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.progress-handle:active {
cursor: grabbing;
}
.time-display {
font-size: 14px;
min-width: 100px;
}
.volume-slider {
width: 80px;
cursor: pointer;
}
/* Fullscreen-specific styles */
.fullscreen-active .fullscreen-controls {
padding: 30px 20px 20px;
}
.fullscreen-active .control-btn {
font-size: 24px;
}
.fullscreen-active .progress-container {
height: 8px;
}
`;
document.head.appendChild(style);
}
// Attach event listeners
attachEventListeners() {
// Fullscreen button
const fullscreenBtn = this.controls.querySelector('.fullscreen-toggle');
fullscreenBtn?.addEventListener('click', () => {
this.toggleFullscreen();
});
// Mouse movement for auto-hide
if (this.options.autoHideControls) {
this.element.addEventListener('mousemove', () => {
this.showControls();
this.scheduleHideControls();
});
this.element.addEventListener('mouseleave', () => {
this.hideControls();
});
}
// Show on hover
if (this.options.showOnHover) {
this.element.addEventListener('mouseenter', () => {
this.showControls();
});
}
// Fullscreen change
this.fullscreen.on('change', ({ isFullscreen }) => {
this.updateFullscreenButton(isFullscreen);
if (isFullscreen) {
this.element.classList.add('in-fullscreen');
} else {
this.element.classList.remove('in-fullscreen');
}
});
// Touch events for mobile
let touchTimeout;
this.element.addEventListener('touchstart', () => {
this.showControls();
clearTimeout(touchTimeout);
touchTimeout = setTimeout(() => {
this.hideControls();
}, this.options.controlsTimeout);
});
}
// Setup keyboard shortcuts
setupKeyboardShortcuts() {
this.element.addEventListener('keydown', (event) => {
if (!this.fullscreen.isFullscreen()) return;
switch (event.key) {
case ' ':
case 'k':
event.preventDefault();
this.togglePlayPause();
break;
case 'f':
event.preventDefault();
this.toggleFullscreen();
break;
case 'ArrowLeft':
event.preventDefault();
this.seek(-10);
break;
case 'ArrowRight':
event.preventDefault();
this.seek(10);
break;
case 'ArrowUp':
event.preventDefault();
this.adjustVolume(10);
break;
case 'ArrowDown':
event.preventDefault();
this.adjustVolume(-10);
break;
case 'm':
event.preventDefault();
this.toggleMute();
break;
}
});
}
// Toggle fullscreen
async toggleFullscreen() {
try {
await this.fullscreen.toggleFullscreen(this.element);
} catch (error) {
console.error('Fullscreen toggle failed:', error);
}
}
// Update fullscreen button
updateFullscreenButton(isFullscreen) {
const btn = this.controls.querySelector('.fullscreen-toggle');
if (!btn) return;
const enterIcon = btn.querySelector('.icon-enter');
const exitIcon = btn.querySelector('.icon-exit');
if (isFullscreen) {
enterIcon.style.display = 'none';
exitIcon.style.display = 'inline';
btn.title = 'Exit Fullscreen';
} else {
enterIcon.style.display = 'inline';
exitIcon.style.display = 'none';
btn.title = 'Enter Fullscreen';
}
}
// Show controls
showControls(immediate = false) {
clearTimeout(this.hideTimeout);
if (!this.isControlsVisible) {
this.isControlsVisible = true;
if (immediate) {
this.controls.style.transition = 'none';
}
this.controls.classList.remove('hidden');
if (immediate) {
// Force reflow
this.controls.offsetHeight;
this.controls.style.transition = '';
}
}
}
// Hide controls
hideControls(animated = true) {
clearTimeout(this.hideTimeout);
if (this.isControlsVisible && this.options.showControls) {
this.isControlsVisible = false;
if (!animated) {
this.controls.style.transition = 'none';
}
this.controls.classList.add('hidden');
if (!animated) {
// Force reflow
this.controls.offsetHeight;
this.controls.style.transition = '';
}
}
}
// Schedule controls hide
scheduleHideControls() {
clearTimeout(this.hideTimeout);
this.hideTimeout = setTimeout(() => {
if (this.fullscreen.isFullscreen()) {
this.hideControls();
}
}, this.options.controlsTimeout);
}
// Media control methods (implement based on element type)
togglePlayPause() {
if (this.element.tagName === 'VIDEO' || this.element.tagName === 'AUDIO') {
if (this.element.paused) {
this.element.play();
} else {
this.element.pause();
}
}
}
seek(seconds) {
if (this.element.tagName === 'VIDEO' || this.element.tagName === 'AUDIO') {
this.element.currentTime += seconds;
}
}
adjustVolume(change) {
if (this.element.tagName === 'VIDEO' || this.element.tagName === 'AUDIO') {
const newVolume = Math.max(
0,
Math.min(1, this.element.volume + change / 100)
);
this.element.volume = newVolume;
}
}
toggleMute() {
if (this.element.tagName === 'VIDEO' || this.element.tagName === 'AUDIO') {
this.element.muted = !this.element.muted;
}
}
}
// Usage
const videoElement = document.getElementById('myVideo');
const controller = new FullscreenController(videoElement, {
showControls: true,
autoHideControls: true,
controlsTimeout: 3000,
});
// Custom implementation for non-video elements
const presentationElement = document.getElementById('presentation');
const presentationController = new FullscreenController(presentationElement, {
customControls: createPresentationControls(),
showOnHover: false,
});
Fullscreen Styling
CSS for Fullscreen Mode
class FullscreenStyleManager {
constructor() {
this.styles = new Map();
this.styleElement = null;
this.init();
}
// Initialize style manager
init() {
this.createStyleElement();
this.addDefaultStyles();
this.setupPseudoClasses();
}
// Create style element
createStyleElement() {
this.styleElement = document.createElement('style');
this.styleElement.id = 'fullscreen-styles';
document.head.appendChild(this.styleElement);
}
// Add default fullscreen styles
addDefaultStyles() {
const defaultStyles = `
/* Fullscreen pseudo-classes */
:fullscreen {
background-color: black;
}
:-webkit-full-screen {
background-color: black;
}
:-moz-full-screen {
background-color: black;
}
:-ms-fullscreen {
background-color: black;
}
/* Fullscreen element styles */
.fullscreen-active {
width: 100% !important;
height: 100% !important;
object-fit: contain;
}
/* Video in fullscreen */
video:fullscreen {
width: 100%;
height: 100%;
object-fit: contain;
}
video:-webkit-full-screen {
width: 100%;
height: 100%;
object-fit: contain;
}
/* Hide scrollbars in fullscreen */
:fullscreen::-webkit-scrollbar {
display: none;
}
/* Fullscreen backdrop */
::backdrop {
background-color: black;
}
::-webkit-backdrop {
background-color: black;
}
/* Animations */
.fullscreen-enter {
animation: fullscreen-enter 0.3s ease-out;
}
.fullscreen-exit {
animation: fullscreen-exit 0.3s ease-out;
}
@keyframes fullscreen-enter {
from {
transform: scale(0.8);
opacity: 0.8;
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes fullscreen-exit {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0.8);
opacity: 0.8;
}
}
/* Loading indicator */
.fullscreen-loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 24px;
z-index: 10000;
}
/* Error message */
.fullscreen-error {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 0, 0, 0.8);
color: white;
padding: 10px 20px;
border-radius: 5px;
z-index: 10000;
}
`;
this.updateStyles('default', defaultStyles);
}
// Setup pseudo-class detection
setupPseudoClasses() {
// Monitor fullscreen changes
const prefixes = ['', 'webkit', 'moz', 'ms'];
prefixes.forEach((prefix) => {
const event = prefix ? `${prefix}fullscreenchange` : 'fullscreenchange';
document.addEventListener(event, () => {
this.handleFullscreenChange();
});
});
}
// Handle fullscreen change
handleFullscreenChange() {
const isFullscreen = this.isFullscreen();
if (isFullscreen) {
document.body.classList.add('has-fullscreen');
} else {
document.body.classList.remove('has-fullscreen');
}
}
// Check if in fullscreen
isFullscreen() {
return !!(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
// Add custom styles
addStyles(id, styles) {
this.styles.set(id, styles);
this.updateStyleElement();
}
// Remove styles
removeStyles(id) {
this.styles.delete(id);
this.updateStyleElement();
}
// Update styles
updateStyles(id, styles) {
this.styles.set(id, styles);
this.updateStyleElement();
}
// Update style element
updateStyleElement() {
const allStyles = Array.from(this.styles.values()).join('\n');
this.styleElement.textContent = allStyles;
}
// Create responsive fullscreen styles
createResponsiveStyles(breakpoints = {}) {
const defaultBreakpoints = {
mobile: 640,
tablet: 1024,
desktop: 1280,
...breakpoints,
};
const responsiveStyles = `
/* Mobile fullscreen adjustments */
@media (max-width: ${defaultBreakpoints.mobile}px) {
:fullscreen {
padding: 0;
}
.fullscreen-controls {
font-size: 14px;
}
.control-btn {
padding: 10px;
}
}
/* Tablet fullscreen adjustments */
@media (min-width: ${defaultBreakpoints.mobile + 1}px) and (max-width: ${defaultBreakpoints.tablet}px) {
.fullscreen-controls {
padding: 15px;
}
}
/* Desktop fullscreen adjustments */
@media (min-width: ${defaultBreakpoints.desktop}px) {
.fullscreen-controls {
max-width: 1200px;
margin: 0 auto;
}
}
/* Landscape orientation */
@media (orientation: landscape) and (max-height: 500px) {
.fullscreen-controls {
padding: 10px;
}
.controls-bar {
font-size: 12px;
}
}
`;
this.addStyles('responsive', responsiveStyles);
}
// Apply theme
applyTheme(theme = 'dark') {
const themes = {
dark: {
background: 'black',
controlsBg: 'rgba(0, 0, 0, 0.7)',
text: 'white',
accent: '#007bff',
},
light: {
background: 'white',
controlsBg: 'rgba(255, 255, 255, 0.9)',
text: 'black',
accent: '#0056b3',
},
cinema: {
background: '#000',
controlsBg: 'rgba(20, 20, 20, 0.9)',
text: '#ccc',
accent: '#e50914',
},
};
const selectedTheme = themes[theme] || themes.dark;
const themeStyles = `
:fullscreen {
background-color: ${selectedTheme.background};
}
.fullscreen-controls {
background: linear-gradient(transparent, ${selectedTheme.controlsBg});
color: ${selectedTheme.text};
}
.control-btn {
color: ${selectedTheme.text};
}
.control-btn:hover {
color: ${selectedTheme.accent};
}
.progress-filled {
background: ${selectedTheme.accent};
}
`;
this.addStyles('theme', themeStyles);
}
}
// Usage
const styleManager = new FullscreenStyleManager();
// Add custom styles
styleManager.addStyles(
'custom',
`
.my-fullscreen-element:fullscreen {
display: flex;
align-items: center;
justify-content: center;
}
`
);
// Apply theme
styleManager.applyTheme('cinema');
// Create responsive styles
styleManager.createResponsiveStyles({
mobile: 480,
tablet: 768,
desktop: 1920,
});
Fullscreen Gallery
Image Gallery with Fullscreen
class FullscreenGallery {
constructor(container, images = []) {
this.container = container;
this.images = images;
this.currentIndex = 0;
this.fullscreen = new FullscreenManager();
this.viewer = null;
this.init();
}
// Initialize gallery
init() {
this.createGallery();
this.createViewer();
this.attachEventListeners();
}
// Create gallery grid
createGallery() {
this.container.innerHTML = '';
this.container.className = 'fullscreen-gallery';
const grid = document.createElement('div');
grid.className = 'gallery-grid';
this.images.forEach((image, index) => {
const item = document.createElement('div');
item.className = 'gallery-item';
item.innerHTML = `
<img src="${image.thumbnail || image.src}" alt="${image.alt || ''}" />
<div class="gallery-overlay">
<button class="view-btn" data-index="${index}">
<span>⛶</span> View
</button>
</div>
`;
grid.appendChild(item);
});
this.container.appendChild(grid);
this.applyGalleryStyles();
}
// Create fullscreen viewer
createViewer() {
this.viewer = document.createElement('div');
this.viewer.className = 'fullscreen-viewer';
this.viewer.innerHTML = `
<div class="viewer-container">
<img class="viewer-image" src="" alt="" />
<div class="viewer-loading">Loading...</div>
</div>
<div class="viewer-controls">
<button class="viewer-btn prev" title="Previous">❮</button>
<button class="viewer-btn next" title="Next">❯</button>
<button class="viewer-btn close" title="Close">✕</button>
<div class="viewer-info">
<span class="image-counter">1 / ${this.images.length}</span>
<span class="image-title"></span>
</div>
<div class="viewer-thumbnails">
${this.images
.map(
(img, i) => `
<img class="thumbnail ${i === 0 ? 'active' : ''}"
src="${img.thumbnail || img.src}"
data-index="${i}" />
`
)
.join('')}
</div>
</div>
`;
document.body.appendChild(this.viewer);
this.applyViewerStyles();
}
// Apply gallery styles
applyGalleryStyles() {
const style = document.createElement('style');
style.textContent = `
.fullscreen-gallery {
padding: 20px;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
}
.gallery-item {
position: relative;
overflow: hidden;
border-radius: 8px;
cursor: pointer;
aspect-ratio: 1;
}
.gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.gallery-item:hover img {
transform: scale(1.05);
}
.gallery-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s;
}
.gallery-item:hover .gallery-overlay {
opacity: 1;
}
.view-btn {
background: rgba(255, 255, 255, 0.9);
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
gap: 5px;
}
`;
document.head.appendChild(style);
}
// Apply viewer styles
applyViewerStyles() {
const style = document.createElement('style');
style.textContent = `
.fullscreen-viewer {
display: none;
background: black;
}
.fullscreen-viewer:fullscreen {
display: flex;
flex-direction: column;
}
.viewer-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.viewer-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
transition: opacity 0.3s;
}
.viewer-image.loading {
opacity: 0;
}
.viewer-loading {
position: absolute;
color: white;
font-size: 20px;
display: none;
}
.viewer-loading.active {
display: block;
}
.viewer-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
padding: 20px;
color: white;
}
.viewer-btn {
position: absolute;
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
font-size: 30px;
padding: 10px 15px;
cursor: pointer;
transition: background 0.3s;
}
.viewer-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.viewer-btn.prev {
left: 20px;
top: 50%;
transform: translateY(-50%);
}
.viewer-btn.next {
right: 20px;
top: 50%;
transform: translateY(-50%);
}
.viewer-btn.close {
top: 20px;
right: 20px;
font-size: 20px;
}
.viewer-info {
text-align: center;
margin-bottom: 10px;
}
.viewer-thumbnails {
display: flex;
gap: 10px;
overflow-x: auto;
padding: 10px 0;
justify-content: center;
}
.viewer-thumbnails img {
width: 60px;
height: 60px;
object-fit: cover;
border: 2px solid transparent;
cursor: pointer;
opacity: 0.6;
transition: all 0.3s;
}
.viewer-thumbnails img.active {
border-color: white;
opacity: 1;
}
.viewer-thumbnails img:hover {
opacity: 1;
}
/* Touch gestures */
.viewer-container.dragging {
cursor: grabbing;
}
`;
document.head.appendChild(style);
}
// Attach event listeners
attachEventListeners() {
// Gallery item clicks
this.container.addEventListener('click', (e) => {
const viewBtn = e.target.closest('.view-btn');
if (viewBtn) {
const index = parseInt(viewBtn.dataset.index);
this.openViewer(index);
}
});
// Viewer controls
this.viewer.querySelector('.prev').addEventListener('click', () => {
this.showPrevious();
});
this.viewer.querySelector('.next').addEventListener('click', () => {
this.showNext();
});
this.viewer.querySelector('.close').addEventListener('click', () => {
this.closeViewer();
});
// Thumbnail clicks
this.viewer.addEventListener('click', (e) => {
const thumbnail = e.target.closest('.thumbnail');
if (thumbnail) {
const index = parseInt(thumbnail.dataset.index);
this.showImage(index);
}
});
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (!this.fullscreen.isFullscreen()) return;
switch (e.key) {
case 'ArrowLeft':
this.showPrevious();
break;
case 'ArrowRight':
this.showNext();
break;
case 'Escape':
this.closeViewer();
break;
}
});
// Touch gestures
this.setupTouchGestures();
}
// Setup touch gestures
setupTouchGestures() {
let startX = 0;
let startY = 0;
let distX = 0;
let distY = 0;
const container = this.viewer.querySelector('.viewer-container');
container.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
});
container.addEventListener('touchmove', (e) => {
if (!startX || !startY) return;
distX = e.touches[0].clientX - startX;
distY = e.touches[0].clientY - startY;
});
container.addEventListener('touchend', () => {
if (Math.abs(distX) > Math.abs(distY) && Math.abs(distX) > 50) {
if (distX > 0) {
this.showPrevious();
} else {
this.showNext();
}
}
startX = 0;
startY = 0;
distX = 0;
distY = 0;
});
}
// Open viewer
async openViewer(index) {
this.currentIndex = index;
try {
await this.fullscreen.requestFullscreen(this.viewer);
this.viewer.style.display = 'flex';
this.showImage(index);
} catch (error) {
console.error('Failed to open fullscreen viewer:', error);
}
}
// Close viewer
async closeViewer() {
try {
await this.fullscreen.exitFullscreen();
this.viewer.style.display = 'none';
} catch (error) {
console.error('Failed to close viewer:', error);
}
}
// Show image
showImage(index) {
if (index < 0 || index >= this.images.length) return;
this.currentIndex = index;
const image = this.images[index];
const viewerImage = this.viewer.querySelector('.viewer-image');
const loading = this.viewer.querySelector('.viewer-loading');
// Show loading
viewerImage.classList.add('loading');
loading.classList.add('active');
// Load image
const img = new Image();
img.onload = () => {
viewerImage.src = img.src;
viewerImage.alt = image.alt || '';
viewerImage.classList.remove('loading');
loading.classList.remove('active');
};
img.onerror = () => {
loading.textContent = 'Failed to load image';
};
img.src = image.src;
// Update UI
this.updateViewerUI();
}
// Update viewer UI
updateViewerUI() {
// Update counter
const counter = this.viewer.querySelector('.image-counter');
counter.textContent = `${this.currentIndex + 1} / ${this.images.length}`;
// Update title
const title = this.viewer.querySelector('.image-title');
const currentImage = this.images[this.currentIndex];
title.textContent = currentImage.title || '';
// Update thumbnails
const thumbnails = this.viewer.querySelectorAll('.thumbnail');
thumbnails.forEach((thumb, index) => {
thumb.classList.toggle('active', index === this.currentIndex);
});
// Update navigation buttons
const prevBtn = this.viewer.querySelector('.prev');
const nextBtn = this.viewer.querySelector('.next');
prevBtn.style.display = this.currentIndex > 0 ? 'block' : 'none';
nextBtn.style.display =
this.currentIndex < this.images.length - 1 ? 'block' : 'none';
}
// Show previous image
showPrevious() {
if (this.currentIndex > 0) {
this.showImage(this.currentIndex - 1);
}
}
// Show next image
showNext() {
if (this.currentIndex < this.images.length - 1) {
this.showImage(this.currentIndex + 1);
}
}
}
// Usage
const images = [
{
src: '/images/photo1-full.jpg',
thumbnail: '/images/photo1-thumb.jpg',
title: 'Mountain Landscape',
alt: 'Beautiful mountain landscape',
},
{
src: '/images/photo2-full.jpg',
thumbnail: '/images/photo2-thumb.jpg',
title: 'Ocean Sunset',
alt: 'Sunset over the ocean',
},
// More images...
];
const galleryContainer = document.getElementById('gallery');
const gallery = new FullscreenGallery(galleryContainer, images);
Best Practices
-
Always check for user interaction
// Fullscreen must be triggered by user action button.addEventListener('click', async () => { try { await element.requestFullscreen(); } catch (error) { console.error('Fullscreen denied:', error); } });
-
Handle all vendor prefixes
const requestFullscreen = element.requestFullscreen || element.webkitRequestFullscreen || element.mozRequestFullScreen || element.msRequestFullscreen;
-
Provide exit mechanisms
// Always show exit button in fullscreen const exitButton = createExitButton(); element.appendChild(exitButton);
-
Test across browsers and devices
// Different behavior on mobile vs desktop if (isMobile()) { // Adjust UI for mobile fullscreen }
Conclusion
The Fullscreen API enables powerful immersive experiences:
- Video players with cinema mode
- Image galleries with lightbox effects
- Games with immersive gameplay
- Presentations with distraction-free viewing
- Document viewers with focused reading
- Interactive apps with full-screen modes
Key takeaways:
- Always require user interaction
- Handle all browser prefixes
- Provide clear exit mechanisms
- Style appropriately for fullscreen
- Test across different devices
- Consider mobile differences
Master the Fullscreen API to create engaging, immersive web experiences!