用户同意机制设计与前端实现最佳实践
先扔个最简单的弹窗,跑通再说
用户同意这事,说白了就是个“确认框”,但合规要求一多,就变得又臭又长。我一开始图省事,直接用 alert() 弹个“是否同意隐私政策?”,结果被法务同事追着骂——这玩意儿连拒绝按钮都没有,更别说记录用户选择时间了。
后来改用原生模态框,核心逻辑其实就三步:1)页面加载时不自动收集数据;2)弹窗让用户明确勾选;3)点“同意”才发请求。下面这个方案是我折腾几次后目前线上在用的,亲测有效。
<div id="consent-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white p-6 rounded shadow-lg max-w-md w-full">
<h3 class="text-lg font-bold mb-3">隐私与 Cookie 同意</h3>
<p class="mb-4 text-sm">
我们使用 Cookie 来改善您的体验。点击“同意”即表示您接受我们的
<a href="/privacy" target="_blank" class="text-blue-600">隐私政策</a>。
</p>
<div class="flex items-center mb-4">
<input type="checkbox" id="consent-checkbox" class="mr-2">
<label for="consent-checkbox">我已阅读并同意上述条款</label>
</div>
<div class="flex justify-end gap-2">
<button id="reject-btn" class="px-4 py-2 border border-gray-300 rounded">拒绝</button>
<button id="accept-btn" class="px-4 py-2 bg-blue-600 text-white rounded" disabled>同意</button>
</div>
</div>
</div>
// 控制同意按钮状态
document.getElementById('consent-checkbox').addEventListener('change', (e) => {
document.getElementById('accept-btn').disabled = !e.target.checked;
});
// 拒绝处理
document.getElementById('reject-btn').addEventListener('click', () => {
// 这里可以跳转到仅基础功能的页面,或者直接关掉所有非必要脚本
localStorage.setItem('user_consent', 'rejected');
document.getElementById('consent-modal').classList.add('hidden');
// 注意:拒绝后仍需保留必要 Cookie(比如登录态),别一刀切
});
// 同意处理
document.getElementById('accept-btn').addEventListener('click', () => {
const consentData = {
granted: true,
timestamp: new Date().toISOString(),
version: 'v1.2' // 记录当前政策版本,方便以后审计
};
localStorage.setItem('user_consent', JSON.stringify(consentData));
document.getElementById('consent-modal').classList.add('hidden');
// 关键:这里才初始化分析工具、广告脚本等
initAnalytics();
loadThirdPartyScripts();
});
这个场景最好用:动态加载第三方脚本
很多人以为用户同意就是存个 localStorage 就完事了,其实大错特错。真正的坑在于:你得确保在用户没同意前,任何非必要的外部脚本都不能加载。比如 Google Analytics、Facebook Pixel、甚至某些字体 CDN。
我之前犯过一个低级错误:把 GA 的初始化代码放在页面底部,以为加个判断就行。结果发现,即使没执行 gtag(),gtag.js 文件本身已经被浏览器预加载了——这已经算数据收集了!
正确做法是延迟注入 script 标签。看下面这个封装函数:
function loadScript(src, callback) {
if (document.querySelector(script[src="${src}"])) return; // 防重复加载
const script = document.createElement('script');
script.src = src;
script.async = true;
if (callback) {
script.onload = callback;
}
document.head.appendChild(script);
}
function initAnalytics() {
// 只有用户同意后才加载
loadScript('https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID', () => {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
});
}
踩坑提醒:这三点一定注意
- localStorage 不是万能的:用户可能清空缓存,或者用无痕模式。所以每次页面加载都要检查同意状态,不能只依赖本地存储。建议在服务端也存一份(通过 API 上报),但注意别在未同意时偷偷发请求!
- “拒绝”不等于“删除”:GDPR 要求用户拒绝后,你得停用相关功能,但已收集的数据不一定能删(比如订单记录)。所以前端拒绝后,要确保后续不再发送新数据,而不是去调删除接口——那可能是另一个流程。
- 别忘了 iframe 场景:如果你嵌入了 YouTube 视频或地图,这些 iframe 本身会带 Cookie。最佳实践是先放一张静态封面图,用户点“播放”时再动态替换为真实 iframe,并在此之前弹同意框。我见过太多网站在这栽跟头。
高级技巧:细粒度同意管理
有些项目要求用户能分别同意“分析型 Cookie”和“广告型 Cookie”。这时候就得上组件化方案了。我的做法是搞个 ConsentManager 类,把不同类别的脚本分组:
class ConsentManager {
constructor() {
this.categories = {
necessary: { required: true, scripts: [] }, // 必要项,无需同意
analytics: { required: false, scripts: ['https://jztheme.com/analytics.js'] },
marketing: { required: false, scripts: ['https://ads.example.com/script.js'] }
};
this.loadFromStorage();
}
grant(category) {
if (!this.categories[category]) return;
this.consent[category] = true;
this.saveToStorage();
this.loadScripts(category);
}
isGranted(category) {
return this.categories[category]?.required || this.consent[category];
}
loadScripts(category) {
const { scripts } = this.categories[category];
scripts.forEach(src => loadScript(src));
}
// ... 其他方法
}
这样,当用户只勾选“分析”时,营销脚本就不会加载。不过要注意:UI 得设计清楚,别让用户晕头转向。我们产品当初给了五个开关,结果用户投诉“像考驾照科目一”。
最后唠叨几句
用户同意这事,技术上其实不难,难的是平衡体验和合规。我见过为了合规把首页塞满弹窗的,也见过假装有同意机制实际照常收集的。咱们开发者至少做到:1)没同意前绝不加载非必要资源;2)拒绝后真停用;3)记录可审计。
上面给的代码不是最优解,比如没处理 SSR 场景(Nuxt/Next 里得用 onMounted 包裹),也没做国际化。但作为 MVP 足够跑起来,后续再迭代。
以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如和 CMP 平台对接、自动检测 Cookie 类型),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流——特别是怎么优雅处理 iframe 的,我还在找好方案。

暂无评论