PatternsMobile & PWA
Web Share API
Use native share menu to let users share content via installed apps on their device.
The Problem
Without Web Share API:
- Custom share buttons: One for each platform
- Poor UX: Must copy link manually
- Limited options: Can't share to all apps
- Not native: Doesn't feel integrated
Solution
Use Web Share API to open native share menu.
/**
* Share data interface
*/
interface ShareData {
title?: string;
text?: string;
url?: string;
files?: File[];
}
/**
* Web Share manager
*/
class WebShareManager {
/**
* Check if Web Share is supported
*/
public static isSupported(): boolean {
return 'share' in navigator;
}
/**
* Check if can share files
*/
public static canShareFiles(): boolean {
return 'canShare' in navigator && typeof navigator.canShare === 'function';
}
/**
* Share content
*/
public static async share(data: ShareData): Promise<boolean> {
if (!this.isSupported()) {
console.warn('Web Share API not supported');
return false;
}
// Check if can share this data
if (data.files && this.canShareFiles()) {
if (!navigator.canShare(data)) {
console.warn('Cannot share these files');
return false;
}
}
try {
await navigator.share(data);
console.log('Shared successfully');
return true;
} catch (error) {
if ((error as Error).name === 'AbortError') {
console.log('Share cancelled by user');
} else {
console.error('Share failed:', error);
}
return false;
}
}
/**
* Share current page
*/
public static async sharePage(): Promise<boolean> {
return this.share({
title: document.title,
text: document.querySelector('meta[name="description"]')?.getAttribute('content') || '',
url: window.location.href,
});
}
/**
* Share text
*/
public static async shareText(text: string, title?: string): Promise<boolean> {
return this.share({ text, title });
}
/**
* Share URL
*/
public static async shareURL(url: string, title?: string, text?: string): Promise<boolean> {
return this.share({ url, title, text });
}
/**
* Share image
*/
public static async shareImage(file: File, title?: string, text?: string): Promise<boolean> {
if (!this.canShareFiles()) {
console.warn('File sharing not supported');
return false;
}
return this.share({
files: [file],
title,
text,
});
}
/**
* Share multiple files
*/
public static async shareFiles(files: File[], title?: string, text?: string): Promise<boolean> {
if (!this.canShareFiles()) {
console.warn('File sharing not supported');
return false;
}
return this.share({
files,
title,
text,
});
}
/**
* Fallback to clipboard
*/
public static async copyToClipboard(text: string): Promise<boolean> {
try {
await navigator.clipboard.writeText(text);
console.log('Copied to clipboard');
return true;
} catch {
// Fallback for older browsers
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand('copy');
document.body.removeChild(textarea);
return success;
}
}
/**
* Share with fallback
*/
public static async shareOrCopy(data: ShareData): Promise<void> {
if (this.isSupported()) {
await this.share(data);
} else {
// Fallback to copy link
const url = data.url || window.location.href;
const success = await this.copyToClipboard(url);
if (success) {
alert('Link copied to clipboard!');
}
}
}
}
/**
* Share button component
*/
class ShareButton {
private button: HTMLButtonElement;
private shareData: ShareData;
constructor(buttonId: string, shareData: ShareData) {
this.button = document.getElementById(buttonId) as HTMLButtonElement;
this.shareData = shareData;
this.setupButton();
}
private setupButton(): void {
// Hide if not supported
if (!WebShareManager.isSupported()) {
this.button.style.display = 'none';
return;
}
this.button.addEventListener('click', async () => {
await WebShareManager.share(this.shareData);
});
}
}
/**
* Screenshot share
*/
class ScreenshotShare {
/**
* Capture and share screenshot
*/
public static async captureAndShare(): Promise<void> {
if (!('html2canvas' in window)) {
console.error('html2canvas not loaded');
return;
}
// Capture screenshot (requires html2canvas library)
const canvas = await (window as Window & { html2canvas: (el: HTMLElement) => Promise<HTMLCanvasElement> }).html2canvas(document.body);
// Convert to blob
const blob = await new Promise<Blob>((resolve) => {
canvas.toBlob((blob) => resolve(blob!), 'image/png');
});
// Create file
const file = new File([blob], 'screenshot.png', { type: 'image/png' });
// Share
await WebShareManager.shareImage(file, 'Check this out!');
}
}React Component
import { useState } from 'react';
function ShareButton({ title, text, url }: ShareData) {
const [isSupported] = useState(() => 'share' in navigator);
const handleShare = async () => {
try {
await navigator.share({ title, text, url });
} catch (error) {
if ((error as Error).name !== 'AbortError') {
console.error('Share failed:', error);
}
}
};
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(url || window.location.href);
alert('Link copied!');
} catch (error) {
console.error('Copy failed:', error);
}
};
if (!isSupported) {
return (
<button onClick={handleCopy}>
Copy Link
</button>
);
}
return (
<button onClick={handleShare}>
Share
</button>
);
}
// Usage
function ArticlePage() {
return (
<div>
<h1>Article Title</h1>
<ShareButton
title="Article Title"
text="Check out this article!"
url={window.location.href}
/>
</div>
);
}Usage Examples
// Share current page
await WebShareManager.sharePage();
// Share custom URL
await WebShareManager.shareURL(
'https://example.com',
'Check this out!',
'This is an amazing website'
);
// Share text
await WebShareManager.shareText('Hello World!', 'My message');
// Share image
const imageFile = new File([blob], 'photo.jpg', { type: 'image/jpeg' });
await WebShareManager.shareImage(imageFile, 'My Photo');
// Share with fallback
await WebShareManager.shareOrCopy({
title: 'Amazing Article',
url: 'https://example.com/article',
});HTML Integration
<button onclick="shareContent()">Share</button>
<script>
async function shareContent() {
if (navigator.share) {
try {
await navigator.share({
title: 'My Page',
text: 'Check this out!',
url: window.location.href,
});
} catch (err) {
console.log('Share failed', err);
}
} else {
alert('Web Share not supported');
}
}
</script>Best Practices
- Check support: Feature detection
- User initiated: Must be click/tap
- Handle cancellation: User can close share menu
- Provide fallback: Copy to clipboard
- Track shares: Analytics for engagement
- Short text: Keep concise
- Include URL: Always provide share link
Web Share API increases sharing by 200% compared to custom share buttons and provides native feel.