背景与核心价值原生系统分享可显著提升传播链路与用户转化,Share Target 让 PWA 成为系统级“接收端”。统一实现方案覆盖文本/链接/文件的分享与接收,兼容非支持环境的回退逻辑。在保证隐私与权限最小化的前提下,提供可度量的成功率与交互效率指标。兼容性与前置条件`Web Share API`:Chrome/Edge/Android 上广泛支持;桌面端支持分享文本/URL,移动端支持文件分享。`Share Target`:需安装的 PWA(添加到桌面/主屏)才可作为系统分享目标;iOS Safari 暂不支持 Share Target(提供回退方案)。需具备 `manifest.json` 与 `Service Worker`,并通过 HTTPS 部署。发送分享:feature detection 与渐进增强function canNativeShare(payload: ShareData): boolean { // 桌面端不支持文件分享,移动端支持;统一做能力检测 // @ts-expect-error: navigator.canShare在部分环境是实验性 const canShareFiles = typeof navigator.canShare === 'function' && payload.files && payload.files.length > 0 ? navigator.canShare({ files: payload.files }) : true; return !!navigator.share && canShareFiles; } export async function share(payload: ShareData) { if (canNativeShare(payload)) { try { await navigator.share(payload); return { ok: true }; } catch (err) { // 用户取消或系统拒绝 return { ok: false, error: String(err) }; } } // 回退:复制链接或文本到剪贴板 if (payload.url) { await navigator.clipboard.writeText(`${payload.title ? payload.title + "\n" : ''}${payload.text ? payload.text + "\n" : ''}${payload.url}`); return { ok: true, fallback: 'clipboard' }; } if (payload.text) { await navigator.clipboard.writeText(payload.text); return { ok: true, fallback: 'clipboard' }; } return { ok: false, error: 'no-capability' }; } // 示例:分享文件(移动端) async function shareImage(blob: Blob) { const file = new File([blob], 'image.jpg', { type: 'image/jpeg' }); const payload: ShareData = { title: '示例图片', text: '来自应用的图片', files: [file] }; return share(payload); } 注意事项:必须由用户手势触发(如点击按钮);否则浏览器会拒绝。文件类型与大小:建议图片/视频≤20MB,单次分享文件数≤10(依据移动端稳定性经验)。Share Target:在 PWA 中接收系统分享manifest.json 配置示例:{ "name": "示例应用", "start_url": "/", "display": "standalone", "share_target": { "action": "/share-target", "method": "POST", "enctype": "multipart/form-data", "params": { "title": "title", "text": "text", "url": "url", "files": [{ "name": "files", "accept": ["image/*", "video/*", "text/plain"] }] } } } Service Worker 拦截 share target 的 POST:self.addEventListener('fetch', (event: FetchEvent) => { const url = new URL(event.request.url); if (event.request.method === 'POST' && url.pathname === '/share-target') { event.respondWith(handleShareTarget(event.request)); } }); async function handleShareTarget(req: Request): Promise<Response> { const form = await req.formData(); const title = form.get('title')?.toString() || ''; const text = form.get('text')?.toString() || ''; const url = form.get('url')?.toString() || ''; const files = form.getAll('files') as File[]; const payload = { title, text, url, filesMeta: files.map(f => ({ name: f.name, size: f.size, type: f.type })) }; const clientsList = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); if (clientsList.length) { clientsList[0].postMessage({ type: 'share-target', payload }); return Response.redirect('/share', 303); } await self.clients.openWindow('/share'); return Response.redirect('/share', 303); } 页面接收消息并渲染:navigator.serviceWorker.addEventListener('message', (e: MessageEvent) => { if (e.data?.type === 'share-target') { const p = e.data.payload as { title: string; text: string; url: string; filesMeta: Array<{name:string;size:number;type:string}> }; renderSharePayload(p); } }); function renderSharePayload(p: any) { // 将内容展示在 /share 页面,文件可发起进一步处理/上传 } 隐私与安全治理最小化参数:仅在必要时收集 `title/text/url/files`;不在 SW 中持久化原始文件,优先页面态处理并征得用户同意。类型白名单:`accept` 限制文件类型;上传前进行 MIME 校验与大小限制。权限提示:分享触发不需要持久权限,但剪贴板回退需获得用户授权;避免频繁自动复制导致体验问题。日志与监控:仅记录成功/失败的计数与类型,不记录分享内容本身(保护隐私)。验证指标(Chrome 128/Android,PWA 已安装)分享成功率:文本/链接 ≥ 99.5%,图片(≤10MB)≥ 98.5%。分享交互耗时(P95):打开原生分享面板 ≤ 250ms;完成分享 ≤ 2.5s。Share Target 处理耗时(P95):解析 `multipart/form-data` ≤ 120ms;页面打开并呈现 ≤ 600ms。失败占比:用户主动取消 ≥ 95% 的失败原因;权限/类型不匹配 ≤ 0.3%。回退策略与兼容建议iOS/Safari:不支持 Share Target → 使用页面上传入口与剪贴板分享;URL Scheme(如 `mailto:`)作为辅助。桌面端:优先文本/URL分享;文件分享回退至上传组件。无 Service Worker:仅提供 Web Share API;提示用户安装 PWA 以启用接收分享。测试清单功能:文本/URL/文件分享在支持与不支持环境的路径均成功或合理回退。稳定性:连续分享 20 次无崩溃与异常;超大文件正确受限并提示。指标:采集成功率、耗时分布与失败原因分类;隐私检查不含敏感内容。应用场景社交传播、客服转发、媒体素材接力、跨应用内容拾取(系统图库 → PWA 编辑)。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部