手机分享按钮别等上线才测Web Share API 做邀请卡原型用 cpolar 给同事真机验收手机端的“分享”看起来只是一个按钮真正容易出问题的部分却在按钮之外浏览器是否实现 Web Share API页面是不是 HTTPS调用是否紧跟用户点击用户取消后页面怎样提示以及桌面浏览器该走哪条回退路径。只在电脑上看截图验证不了系统分享面板更验证不了不同手机的取消行为。本文做一个虚构的“社区开放日”邀请卡。页面只有静态文案和原生 HTML、CSS、JavaScript不读取通讯录不收集联系人不上传文件也不连接账号或接口。先在本机跑通再通过 cpolar 生成短时 HTTPS 地址让同事用各自手机完成一次有边界的验收。Web Share API 分享的其实是一组文本数据navigator.share()接收的核心字段是title、text和url。浏览器把这些字段交给系统分享面板用户自己选择目标应用和接收对象。网页不会因此获得联系人列表也不会知道用户最终选了谁。这条边界要在产品说明里写清楚。我们的示例只传虚构活动标题、简短说明和当前页面的 HTTPS 地址不调用文件分享能力。验收者点“分享邀请”后系统面板出现完成分享、主动取消或浏览器拒绝调用页面都给出可读状态。Web Share API 通常要求安全上下文。localhost便于本机开发但同事从手机访问开发机地址时并不等同于本机访问。cpolar 提供的 HTTPS 入口正好用于短时真机检查同时本地静态服务仍只监听127.0.0.1。先做一张不依赖外部资源的邀请卡新建index.html把样式和脚本都放进同一个文件。这样验收包只有一个静态页面不会带出额外配置或内部资源。!doctype html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title微风社区开放日邀请/title style :root { color-scheme: light; font-family: system-ui, -apple-system, Segoe UI, sans-serif; background: #eef5ef; color: #18332a; } * { box-sizing: border-box; } body { min-height: 100vh; margin: 0; display: grid; place-items: center; padding: 24px; } .card { width: min(100%, 560px); padding: 32px; border: 1px solid #c9ddd0; border-radius: 24px; background: #fff; box-shadow: 0 18px 50px rgba(24, 51, 42, .12); } .eyebrow { color: #35705a; font-weight: 700; letter-spacing: .12em; } h1 { margin: 10px 0 12px; font-size: clamp(30px, 8vw, 48px); } .meta { padding: 16px; border-radius: 14px; background: #f2f8f4; line-height: 1.8; } .actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; } button { min-height: 46px; padding: 0 18px; border: 1px solid #246b51; border-radius: 999px; font: inherit; font-weight: 700; cursor: pointer; } #shareButton { background: #246b51; color: #fff; } #copyButton { background: #fff; color: #205b46; } button:focus-visible { outline: 3px solid #efb94c; outline-offset: 3px; } #status { min-height: 1.6em; margin: 16px 0 0; color: #315f50; } /style /head body main classcard p classeyebrow仅供原型验收/p h1微风社区开放日/h1 p一起看手作摊位、旧物交换角和小型音乐会。/p div classmeta strong日期/strong9 月 12 日 14:00—17:00br strong地点/strong示例社区共享花园br strong说明/strong本活动与地点均为虚构内容 /div div classactions button idshareButton typebutton分享邀请/button button idcopyButton typebutton复制邀请链接/button /div p idstatus rolestatus aria-livepolite/p /main script const shareButton document.querySelector(#shareButton); const copyButton document.querySelector(#copyButton); const status document.querySelector(#status); const setStatus (message) { status.textContent message; }; const shareData () ({ title: 微风社区开放日邀请, text: 来看看这个虚构的社区开放日原型。, url: window.location.href }); async function copyCurrentUrl() { const url window.location.href; try { await navigator.clipboard.writeText(url); setStatus(邀请链接已复制可以手动粘贴发送。); } catch (error) { const input document.createElement(textarea); input.value url; input.setAttribute(readonly, ); input.style.position fixed; input.style.opacity 0; document.body.appendChild(input); input.select(); const copied document.execCommand(copy); input.remove(); setStatus(copied ? 邀请链接已复制可以手动粘贴发送。 : 请手动复制${url}); } } shareButton.addEventListener(click, async () { if (typeof navigator.share ! function) { setStatus(当前浏览器不支持系统分享请使用复制链接。); copyButton.focus(); return; } try { await navigator.share(shareData()); setStatus(系统分享操作已完成。); } catch (error) { if (error.name AbortError) { setStatus(你已取消分享页面内容没有变化。); return; } setStatus(系统分享未完成请改用复制链接。); } }); copyButton.addEventListener(click, copyCurrentUrl); if (typeof navigator.share ! function) { shareButton.hidden true; setStatus(当前浏览器不支持系统分享请复制邀请链接。); } else { setStatus(此浏览器支持系统分享可以开始验收。); } /script /body /html这里没有把分享数据写成页面加载时的常量而是在点击时读取window.location.href。本机访问时分享本机链接经 cpolar 访问时分享临时 HTTPS 链接验收者不会误拿旧地址。用户手势不能被定时器和页面初始化替代系统分享属于受保护能力调用应直接发生在点击事件里。不要在页面打开后自动弹出也不要先启动一个延时任务再调用。示例在click监听器中立即执行navigator.share()中间没有网络请求用户手势链条清楚。能力检测也不能只看设备名称。手机浏览器不等于一定支持桌面浏览器也不等于一定不支持。用typeof navigator.share function判断更可靠。示例在不支持时隐藏主分享按钮保留“复制邀请链接”并用状态区域解释当前路径。rolestatus与aria-livepolite能让读屏软件在合适时机朗读结果。状态文案不只靠颜色区分键盘焦点也有清晰轮廓。回退发生时把焦点移到复制按钮键盘用户无需重新寻找操作入口。取消分享不是程序故障用户打开系统面板后点取消Promise 通常以名为AbortError的异常结束。这是正常选择不能显示成红色报错更不该反复弹出面板。示例单独识别取消并提示“页面内容没有变化”。其他异常统一引导到复制链接但页面不展示原始异常详情。这样既避免把实现信息暴露给验收者也能让流程继续。复制功能优先使用 Clipboard API若不可用再借助临时文本框尝试复制仍失败时直接把当前地址显示出来方便手动选择。要注意系统面板返回成功只表示浏览器完成了分享调用不代表网页能确认接收方已阅读。验收记录应写“面板已拉起并完成操作”不要写成“消息已送达”。网页也不应追踪分享对象。分享数据要以当前页面为准分享参数很短仍值得逐项检查。title用活动名称不要照搬网页里过长的标题text用一句完整说明避免把日期、地点和操作要求全塞进去url则取当前地址。不同接收应用对三项内容的组合方式不同有的会保留标题有的主要展示说明和链接因此关键信息不能只存在于某一个字段中。本例没有额外拼接查询参数。若原型以后加入页面状态也不要把内部标识、调试开关或会话信息带入分享地址。验收前可以在浏览器地址栏直接核对链接确保它只指向这张邀请卡。发送给同事的验收说明也应独立于页面链接例如写清“请在今晚六点前测试结束后不要继续转发”而不是把管理要求编码进网址。页面刷新后状态提示会恢复为能力检测结果这是有意设计。它不使用本地存储不保留分享次数也不建立访问者画像。若需要统计测试结果采用人工验收表即可记录设备型号、系统版本、浏览器版本和观察结果不写接收人身份。本机跑通后再开放短时 HTTPS 地址在index.html所在目录启动静态服务python3 -m http.server 8080 --bind 127.0.0.1先访问http://127.0.0.1:8080检查卡片排版、复制按钮、键盘焦点和状态提示。确认页面没有外链请求后再开另一个终端执行cpolar http 8080从 cpolar 输出中取 HTTPS 地址用它打开页面。不要把地址改写成固定域名也不要长期复用。把这条临时链接只发给本次验收同事并附上截止时间。静态服务仍只绑定回环地址对外入口由隧道在本次会话内转发。桌面回退也要当成主流程测试团队常犯的错误是把复制链接当作一句“浏览器不支持”的善后文案直到桌面验收时才发现按钮不可聚焦、剪贴板被拒绝后没有提示。这个示例让复制按钮始终保留即使系统分享可用用户仍可主动选择复制。这样更符合桌面沟通习惯也方便验收者直接粘贴到测试群。测试复制分支时可以先正常复制再在浏览器站点设置中拒绝剪贴板权限并重试。页面应进入备用复制逻辑若浏览器连备用操作也拒绝则显示完整当前地址供手动选择。整个过程中不应清空邀请卡、不应刷新页面也不应让焦点凭空丢失。开发者工具里的设备模拟只适合检查窄屏排版不能替代系统面板测试。模拟视口仍运行在桌面浏览器内核和桌面系统环境中分享目标、权限处理与手机不同。它是排版预检不是真机验收结论。真机验收不要只检查“能弹出来”建议至少准备一台 iOS 手机、一台 Android 手机和一个桌面浏览器按同一张表记录通过 HTTPS 地址打开页面没有安全上下文警告。点击“分享邀请”系统面板由这次点击触发没有自动弹出。面板中的标题、说明和链接均为虚构内容链接与地址栏当前地址一致。选择取消页面读出取消状态按钮仍可再次使用。完成一次分享页面显示完成状态但不宣称接收方已阅读。在不支持系统分享的浏览器中复制入口可见复制成功与失败都有文字反馈。使用键盘或读屏操作时焦点顺序、按钮名称和状态播报清楚。测试期间不要加入真实客户资料、订单、支付、库存、手机号、Token、私有链接或任何凭据。这个页面也不连接数据库、容器控制接口、本机目录及任何服务端业务能力。原型只回答一个问题分享与回退体验在真实手机上是否可用。临时开放后的收尾清单结束 cpolar 进程使临时 HTTPS 入口立即失效。停止本地静态服务确认 8080 端口不再监听。在验收群撤回临时链接并明确告知入口已经关闭。清理聊天中的链接预览、终端输出截图和临时验收记录。保留的结论只写设备、浏览器、分享结果与回退结果不记录联系人或接收对象。再次检查源码确保只有虚构活动与脱敏文案没有误带内部地址、凭据或私有资源。分享按钮越接近系统能力越不该等部署后再碰运气。把页面限制为单一静态原型把服务锁在本机回环地址再用短时 HTTPS 入口做跨手机验收既能提前发现兼容与交互问题也能在验收结束后干净收口。