免费工具 Cookie 弹窗生成器:同意横幅代码 + 实时预览
做外贸独立站、面向欧盟和英国访客的网站,绕不开 Cookie 同意弹窗。选好位置、颜色、文案和你真正用到的 Cookie 分类,得到一段不依赖任何库的 HTML + CSS + JS,旁边就是能点的预览;需要的话再附上一段已经和弹窗接好的 Google Consent Mode v2 代码。
- 实时预览
- 无依赖原生代码
- 本地生成
先调好弹窗,再复制一整段代码
预览里跑的就是下面这段代码。试试「全部拒绝」「自定义」和右下角的「Cookie 设置」按钮,假网页下面的日志会显示弹窗发给 Google Consent Mode 的内容。
底部横条最不打扰;居中对话框会挡住页面,必须让「拒绝」和「接受」一样显眼。
文字颜色固定,保证对比度;只有按钮颜色跟随你的品牌色。
「全部接受」和「全部拒绝」共用这个颜色;按钮上的文字自动选对比度更高的黑或白。
影响按钮文字、分类名称和默认说明。面向海外访客一般选英文。
纯文本。写清可选 Cookie 用来做什么,政策链接会自动加在后面。
路径如 /cookies,或完整的 https:// 网址。还没有?用 Cookie 政策生成器 写一份。
两种都是第一方存储,只有你的网站能读到。
1–730 天。改下面的版本号也会让所有人重新选择。
新增用途或第三方服务时改一下。
实时预览
预览放在隔离的 iframe 里,拿不到 Cookie 和 localStorage,选择只记在内存中,改任何设置都会重置。不会在 tapcub.com 写任何东西。
这段弹窗做了什么
- 已做到按钮文字对比度 5.17:1,高于 WCAG 正文 4.5:1 的下限。
- 已做到「全部拒绝」和「全部接受」样式相同,在第一层并排显示。
- 已做到可选分类默认不勾选;重新打开时按上次保存的选择显示。
- 已做到使用 role="dialog",标题和说明都关联到对话框;所有控件都是真正的按钮或复选框,键盘可操作、有焦点框;按 Esc 关闭设置面板。
- 已做到做出选择后角落会出现「Cookie 设置」按钮;任何带 data-cc-open 的元素也能重新打开设置。
- 说明选择保存在 localStorage 的「cookie_consent」里,180 天后或版本号变化时重新询问。
- 说明浏览器发送 GPC 信号的访客,营销类默认关闭并显示说明。加州法规(§7025)要求出售或共享个人信息的企业把这个信号当作退出请求处理。
- 说明Consent Mode:把上面的 head 代码放在 Google 代码之前。默认全部拒绝,访客每次选择以及之后每次打开页面,弹窗都会发送 update。
- 说明想让某段脚本等到同意后再执行:把它的 type 改成 text/plain,并加上 data-cc-category="analytics"(或 marketing / preferences)。
- 说明预览里的链接不会跳转,选择只保存在内存中。
<!-- Google Consent Mode v2:把这段放在 <head> 的最前面,在 Google 代码或 Tag Manager 容器代码之前。 -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// 默认值:访客做出选择之前,所有可选用途都是 denied。
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'functionality_storage': 'denied',
'personalization_storage': 'denied',
'security_storage': 'granted',
'wait_for_update': 500
});
// 回访的访客:在任何 Google 代码运行之前,先恢复上次保存的选择。
(function () {
var raw = null, r = null;
try { raw = localStorage.getItem('cookie_consent'); } catch (e) {}
if (!raw) try { var m = document.cookie.match(/(?:^|; )cookie_consent=([^;]*)/); if (m) raw = decodeURIComponent(m[1]); } catch (e) {}
try { r = JSON.parse(raw); } catch (e) {}
if (!r || !r.c || !(r.e > Date.now()) || r.v !== '1') return;
var c = r.c;
gtag('consent', 'update', {
'ad_storage': c.marketing ? 'granted' : 'denied',
'ad_user_data': c.marketing ? 'granted' : 'denied',
'ad_personalization': c.marketing ? 'granted' : 'denied',
'analytics_storage': c.analytics ? 'granted' : 'denied',
'functionality_storage': c.preferences ? 'granted' : 'denied',
'personalization_storage': c.preferences ? 'granted' : 'denied'
});
})();
</script>放在 Google 代码或 Tag Manager 容器代码之前。要按地区设默认值、用 GTM 或 url_passthrough,请用 Consent Mode 代码生成器,它读取的是同一份已保存的选择。
<!-- Cookie 横幅 · 由 tapcub.com/tools/cookie-banner-generator 生成 · 无依赖。这是模板,不是法律意见,上线前请审阅。 -->
<!-- 把这一整段放在每个页面的 </body> 之前。 -->
<!-- 可选脚本写成 <script type="text/plain" data-cc-category="analytics" src="…"></script>,访客同意后才会执行。 -->
<!-- 接口:CookieConsent.get() · CookieConsent.open() · CookieConsent.reset() · CookieConsent.on(fn);任何带 data-cc-open 的元素都能重新打开设置。 -->
<style>
#cc-banner{--cc-bg:#ffffff;--cc-fg:#111827;--cc-muted:#4b5563;--cc-border:#d1d5db;--cc-panel:#f9fafb;--cc-accent:#2563eb;--cc-on-accent:#ffffff;--cc-link:#2563eb;
position:fixed;z-index:2147483000;left:0;right:0;bottom:0;border-width:1px 0 0;box-sizing:border-box;padding:20px;background:var(--cc-bg);color:var(--cc-fg);border:1px solid var(--cc-border);
box-shadow:0 8px 32px rgba(0,0,0,.18);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif}
#cc-banner[hidden],#cc-reopen[hidden],#cc-backdrop[hidden],#cc-banner [hidden]{display:none!important}
#cc-banner *{box-sizing:border-box}
#cc-banner:focus{outline:none}
#cc-banner .cc-inner{max-width:1080px;margin:0 auto;display:grid;gap:14px}
#cc-banner .cc-title{margin:0;font-size:17px;font-weight:700;line-height:1.3;color:var(--cc-fg)}
#cc-banner .cc-text{margin:0;color:var(--cc-muted)}
#cc-banner a{color:var(--cc-link);text-decoration:underline;text-underline-offset:2px}
#cc-banner .cc-cats{display:grid;gap:8px;margin:0;padding:0;border:0}
#cc-banner .cc-cat{display:flex;gap:10px;align-items:flex-start;padding:10px 12px;border:1px solid var(--cc-border);border-radius:8px;background:var(--cc-panel)}
#cc-banner .cc-cat input{width:20px;height:20px;margin:2px 0 0;flex:none;accent-color:var(--cc-accent)}
#cc-banner .cc-cat b{display:block;color:var(--cc-fg)}
#cc-banner .cc-cat span{display:block;font-size:13px;color:var(--cc-muted)}
#cc-banner .cc-note{margin:0;font-size:13px;color:var(--cc-muted)}
#cc-banner .cc-actions{display:flex;flex-wrap:wrap;gap:8px}
#cc-banner .cc-btn{flex:1 1 auto;min-height:44px;padding:10px 18px;border:2px solid var(--cc-accent);border-radius:8px;background:var(--cc-accent);color:var(--cc-on-accent);font:inherit;font-weight:600;cursor:pointer}
#cc-banner .cc-btn-alt{background:transparent;color:var(--cc-fg);border-color:var(--cc-border)}
#cc-banner .cc-btn:focus-visible,#cc-reopen:focus-visible,#cc-banner a:focus-visible,#cc-banner input:focus-visible{outline:3px solid var(--cc-link);outline-offset:2px}
#cc-backdrop{position:fixed;inset:0;z-index:2147482999;background:rgba(17,24,39,.55)}
#cc-reopen{position:fixed;z-index:2147482998;left:16px;bottom:16px;min-height:40px;padding:8px 14px;border:1px solid #d1d5db;border-radius:999px;background:#ffffff;color:#111827;font:600 13px/1.2 system-ui,sans-serif;cursor:pointer;box-shadow:0 2px 10px rgba(0,0,0,.15)}
#cc-reopen:focus-visible{outline:3px solid #2563eb;outline-offset:2px}
@media (min-width:720px){#cc-banner.cc-bar .cc-inner{grid-template-columns:1fr auto;align-items:center}#cc-banner.cc-bar .cc-prefs,#cc-banner.cc-bar .cc-actions-wide{grid-column:1/-1}#cc-banner .cc-btn{flex:0 1 auto}}
</style>
<div id="cc-banner" class="cc-bar" role="dialog" aria-modal="false" aria-labelledby="cc-title" aria-describedby="cc-desc" tabindex="-1" lang="en" hidden>
<div class="cc-inner">
<div>
<h2 class="cc-title" id="cc-title">We use cookies</h2>
<p class="cc-text" id="cc-desc">We use strictly necessary cookies to run this site. With your consent we also use cookies for analytics and marketing. You can change your choice at any time in “Cookie settings”. <a href="/cookies">Cookie policy</a></p>
</div>
<div class="cc-prefs" id="cc-prefs" hidden>
<fieldset class="cc-cats">
<legend class="cc-note">Cookie categories</legend>
<div class="cc-cat"><input type="checkbox" id="cc-cat-necessary" checked disabled><label for="cc-cat-necessary"><b>Strictly necessary</b><span>Always on. Needed for the site to work, for example security and remembering this choice.</span></label></div>
<div class="cc-cat"><input type="checkbox" id="cc-cat-analytics" data-cc-cat="analytics"><label for="cc-cat-analytics"><b>Analytics</b><span>Helps us understand how the site is used, for example which pages are visited.</span></label></div>
<div class="cc-cat"><input type="checkbox" id="cc-cat-marketing" data-cc-cat="marketing"><label for="cc-cat-marketing"><b>Marketing</b><span>Used to measure ad campaigns and show more relevant ads.</span></label></div>
</fieldset>
<p class="cc-note" data-cc-gpc hidden>Opt-out preference signal honored: your browser sent Global Privacy Control, so marketing stays off unless you switch it on here.</p>
</div>
<div class="cc-actions">
<button type="button" class="cc-btn" data-cc="reject">Reject all</button>
<button type="button" class="cc-btn" data-cc="accept">Accept all</button>
<button type="button" class="cc-btn cc-btn-alt" data-cc="customize" aria-expanded="false" aria-controls="cc-prefs">Customize</button>
<button type="button" class="cc-btn cc-btn-alt" data-cc="save" hidden>Save choices</button>
</div>
</div>
</div>
<button type="button" id="cc-reopen" data-cc-open hidden>Cookie settings</button>
<script>
(function () {
'use strict';
// 设置:在这里改,不用改下面的代码。
var KEY = "cookie_consent"; // localStorage 键名 / Cookie 名
var VERSION = "1"; // 用途变了就改它:所有访客会被重新询问
var DAYS = 180; // 多少天后重新询问
var STORAGE = "local"; // 'local' = localStorage,'cookie' = 第一方 Cookie
var CATEGORIES = ["analytics","marketing"];
var CONSENT_MODE = true; // 发送 gtag('consent', 'update', …)
var TAPCUB = false; // 同步给 TapCub 统计脚本(只在识别模式下有用)
var HONOR_GPC = true; // 收到全球隐私控制(GPC)信号时营销类保持关闭
var MODAL = false;
var root = document.getElementById('cc-banner');
if (!root) return;
var backdrop = document.getElementById('cc-backdrop');
var reopenBtn = document.getElementById('cc-reopen');
var prefs = document.getElementById('cc-prefs');
var memory = null, lastFocus = null, firstRun = false;
var gpc = HONOR_GPC && navigator.globalPrivacyControl === true && CATEGORIES.indexOf('marketing') > -1;
// 把横幅放到 Tab 顺序的最前面
document.body.insertBefore(root, document.body.firstChild);
if (backdrop) document.body.insertBefore(backdrop, root);
// ---- 存储:浏览器禁止存储时退回内存
function read() {
var raw = null;
try {
if (STORAGE === 'local') raw = localStorage.getItem(KEY);
else { var m = document.cookie.match(new RegExp('(?:^|; )' + KEY + '=([^;]*)')); if (m) raw = decodeURIComponent(m[1]); }
} catch (e) {}
if (!raw) return memory;
try { return JSON.parse(raw); } catch (e) { return null; }
}
function write(rec) {
memory = rec;
var s = rec ? JSON.stringify(rec) : null;
try {
if (STORAGE === 'local') { if (s) localStorage.setItem(KEY, s); else localStorage.removeItem(KEY); }
else document.cookie = KEY + '=' + (s ? encodeURIComponent(s) : '') + '; Max-Age=' + (s ? DAYS * 86400 : 0) + '; Path=/; SameSite=Lax' + (location.protocol === 'https:' ? '; Secure' : '');
} catch (e) {}
}
function valid(r) { return !!(r && r.c && r.v === VERSION && r.e > Date.now()); }
// ---- 做出选择之后
function apply(c, userAction) {
if (CONSENT_MODE) {
window.dataLayer = window.dataLayer || [];
if (typeof window.gtag !== 'function') window.gtag = function () { window.dataLayer.push(arguments); };
window.gtag('consent', 'update', {
'ad_storage': c.marketing ? 'granted' : 'denied',
'ad_user_data': c.marketing ? 'granted' : 'denied',
'ad_personalization': c.marketing ? 'granted' : 'denied',
'analytics_storage': c.analytics ? 'granted' : 'denied',
'functionality_storage': c.preferences ? 'granted' : 'denied',
'personalization_storage': c.preferences ? 'granted' : 'denied'
});
window.dataLayer.push({ event: 'cookie_consent_update' });
}
// 执行已获同意的 <script type="text/plain" data-cc-category="…">
var blocked = document.querySelectorAll('script[type="text/plain"][data-cc-category]');
for (var i = 0; i < blocked.length; i++) {
var old = blocked[i];
if (!c[old.getAttribute('data-cc-category')]) continue;
var s = document.createElement('script');
for (var k = 0; k < old.attributes.length; k++) {
var a = old.attributes[k];
if (a.name !== 'type' && a.name !== 'data-cc-category') s.setAttribute(a.name, a.value);
}
s.text = old.text;
old.parentNode.replaceChild(s, old);
}
var ev;
try { ev = new CustomEvent('cookieconsent', { detail: c }); } catch (e) { ev = document.createEvent('CustomEvent'); ev.initCustomEvent('cookieconsent', false, false, c); }
document.dispatchEvent(ev);
}
function choice(all) {
var c = {};
for (var i = 0; i < CATEGORIES.length; i++) {
var k = CATEGORIES[i];
if (all === null) { var box = document.getElementById('cc-cat-' + k); c[k] = !!(box && box.checked); }
else c[k] = all && !(gpc && k === 'marketing');
}
return c;
}
function save(c) {
var now = Date.now();
write({ v: VERSION, t: now, e: now + DAYS * 864e5, c: c });
apply(c, true);
close();
}
// ---- 打开 / 关闭和焦点
function focusables() { return root.querySelectorAll('button:not([hidden]):not([disabled]), a[href], input:not([disabled])'); }
function showPrefs(on) {
if (!prefs) return;
prefs.hidden = !on;
var cus = root.querySelector('[data-cc="customize"]');
var sav = root.querySelector('[data-cc="save"]');
if (cus) { cus.hidden = on; cus.setAttribute('aria-expanded', String(on)); }
if (sav) sav.hidden = !on;
if (on) { var first = prefs.querySelector('input:not([disabled])'); if (first) first.focus(); }
}
function open(withPrefs, byUser) {
var r = read(), saved = valid(r) ? r.c : {};
for (var i = 0; i < CATEGORIES.length; i++) {
var box = document.getElementById('cc-cat-' + CATEGORIES[i]);
if (box) box.checked = saved[CATEGORIES[i]] === true; // 不预先勾选
}
var g = root.querySelector('[data-cc-gpc]'); if (g) g.hidden = !gpc;
lastFocus = byUser ? document.activeElement : null;
root.hidden = false;
if (backdrop) backdrop.hidden = false;
if (reopenBtn) reopenBtn.hidden = true;
showPrefs(!!withPrefs);
if (!MODAL) document.documentElement.style.scrollPaddingBottom = root.offsetHeight + 'px';
if ((MODAL || byUser) && !withPrefs) root.focus();
}
function close() {
root.hidden = true;
firstRun = false;
if (backdrop) backdrop.hidden = true;
document.documentElement.style.scrollPaddingBottom = '';
if (reopenBtn) reopenBtn.hidden = false;
var to = lastFocus && document.contains(lastFocus) ? lastFocus : reopenBtn;
if (to && to.focus) to.focus();
}
root.addEventListener('click', function (e) {
var b = e.target.closest ? e.target.closest('[data-cc]') : null;
if (!b) return;
var act = b.getAttribute('data-cc');
if (act === 'accept') save(choice(true));
else if (act === 'reject') save(choice(false));
else if (act === 'save') save(choice(null));
else if (act === 'customize') showPrefs(true);
});
root.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
if (prefs && !prefs.hidden) { showPrefs(false); var cus = root.querySelector('[data-cc="customize"]'); if (cus) cus.focus(); }
else if (!firstRun) close();
return;
}
if (e.key !== 'Tab' || !MODAL) return;
var f = focusables(); if (!f.length) return;
var first = f[0], last = f[f.length - 1];
if (e.shiftKey && (document.activeElement === first || document.activeElement === root)) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
});
document.addEventListener('click', function (e) {
var t = e.target.closest ? e.target.closest('[data-cc-open]') : null;
if (t) { e.preventDefault(); open(true, true); }
});
window.CookieConsent = {
get: function () { var r = read(); return valid(r) ? r.c : null; },
open: function () { open(true, true); },
reset: function () { write(null); firstRun = true; open(false, false); },
on: function (fn) { document.addEventListener('cookieconsent', function (e) { fn(e.detail); }); }
};
var rec = read();
if (valid(rec)) { apply(rec.c, false); if (reopenBtn) reopenBtn.hidden = false; }
else { if (rec) write(null); firstRun = true; open(false, false); }
})();
</script>每个页面放一整段。设置项都在脚本开头。
分享这组输入:数值写在链接的 # 后面,不会发到服务器。
https://tapcub.com/zh/tools/cookie-banner-generator
版本 v1.0更新于 更新记录
生成的代码由哪几部分组成
一段弹窗代码,加一段可选的 Google 代码片段,不从任何第三方服务器加载东西。
弹窗代码
一个只作用于 #cc-banner 的 <style>、弹窗的 HTML 和一段脚本。存储键、版本号、天数、分类这些设置都写在脚本开头,改设置不用动逻辑。
第一层
标题、说明、政策链接,然后是「全部拒绝」「全部接受」「自定义」。拒绝和接受颜色、大小、位置完全对等;「自定义」展开分类列表,「必要」一项锁定开启。
Consent Mode 代码
放在 <head> 最前面:先把所有可选的 consent type 设为 denied,并带 wait_for_update: 500,再恢复访客上次保存的选择,让 Google 代码运行前就拿到结果。
检查清单
按钮文字对比度、缺少政策链接、重新询问间隔过长,以及每个选项的作用。它只说明这段代码本身;网站要不要弹窗、要哪些分类,取决于网站实际加载了什么。
弹窗只负责记录选择,管不了它控制不到的脚本:直接写死在页面里的统计、广告代码照样会执行,除非用 type="text/plain" 拦住,或者通过 Google Consent Mode / Tag Manager 加载。
四步
大约十分钟,大部分时间花在第一步。
-
1
先盘点网站存了什么
用无痕窗口打开网站,按 F12 打开开发者工具的「应用」(Application)面板,记下每个 Cookie 和 localStorage 项是哪个工具写的。只勾选你真的找到的分类。
-
2
调样式、写文案
选位置和颜色,看一眼对比度提示;说明里写清每类可选 Cookie 的用途,并链到 Cookie 政策。面向欧美访客的独立站,弹窗语言选英文。
-
3
贴代码
弹窗代码放在每个页面的
</body>之前;Consent Mode 代码放在<head>最前面。Shopify、WordPress 等建站系统一般在主题模板里加。可选脚本改成type="text/plain"并加data-cc-category。 -
4
三条路径都测一遍
无痕窗口里:点「全部拒绝」,确认存储面板仍然是干净的;再点「全部接受」;再重新打开设置撤回。开了 Consent Mode 的,用 Google Tag Assistant 看默认值和 update 是否生效。
弹窗为什么这样设计
每个默认做法都对应一条公开的规则或监管指引:
- 在用户设备上存储或读取信息,除非是提供用户所要求服务所必需的,都要先征得同意:欧盟 ePrivacy 指令第 5(3) 条,localStorage 同样适用
- 同意必须是明确的主动行为,预先勾选的选项不算:欧盟法院 C-673/17 Planet49 案(2019-10-01)、GDPR 序言第 32 条
- 撤回同意要和给出同意一样容易:GDPR 第 7(3) 条,所以要有重新打开的按钮和 data-cc-open
- 拒绝要和接受一样容易:法国 CNIL 2020-091 号指南和 2020-092 号建议(2020 年 9 月);EDPB Cookie 横幅工作组报告(2023 年 1 月)把第一层没有拒绝按钮、预先勾选、误导性的按钮颜色和对比度列为重点审查的做法
- 国内:《个人信息保护法》第 14 条要求同意在充分知情的前提下自愿、明确作出,第 15 条要求提供便捷的撤回方式;2019 年《App 违法违规收集使用个人信息行为认定方法》把默认勾选同意列为「未经用户同意」的情形
| 要求 | 生成代码里的做法 | 出处 |
|---|---|---|
| 拒绝和接受一样容易 | 同样式、同一层、并排 | CNIL 2020-092;EDPB 工作组报告 |
| 不预先勾选 | 可选分类默认不勾 | C-673/17 Planet49;工信部等 2019 认定方法 |
| 同意前不运行可选内容 | 被拦的脚本保持 text/plain;Consent Mode 默认 denied | ePrivacy 第 5(3) 条 |
| 方便撤回 | 悬浮按钮、data-cc-open、CookieConsent.open() | GDPR 第 7(3) 条;个保法第 15 条 |
| 看得清、键盘能用 | 按钮文字 ≥ 4.5:1、焦点可见、对话框有标题 | WCAG 2.2 SC 1.4.3、2.1.1、2.4.7 |
| 退出偏好信号 | GPC 时营销类保持关闭并说明 | 加州法规 tit. 11 §7025 |
三个网站,三种弹窗
勾什么取决于网站加载了什么。三个例子用的都是同一个生成器。
| 网站 | 加载了什么 | 设置 | 结果 |
|---|---|---|---|
| 外贸 B2B 独立站 | GA4 | 勾统计分析;开 Consent Mode | 拒绝 / 接受 / 自定义;GA4 在访客接受前按 denied 运行 |
| 跨境电商独立站 | GA4、Google Ads、Meta Pixel | 统计分析 + 营销;开 Consent Mode;像素代码加 data-cc-category="marketing" | 像素只在同意后加载;Google 代码收到 update |
| 只用 TapCub 的官网 | TapCub 默认模式 + 在线客服 | 不勾可选分类 | 一条只有「知道了」的提示,或经法务确认后不弹窗:两段脚本在访客操作之前都不在设备上存东西 |
第三行是 TapCub 自己的情况:统计脚本默认不写 Cookie、不写浏览器存储;客服插件只在访客开始对话后写一个会话令牌(见 TapCub 的 Cookie 说明页)。
Cookie 弹窗容易出问题的地方
多数问题不在弹窗本身,而在它周围跑的那些代码。
选择之前代码就跑了
页面里的 GA、像素代码已经执行,再弹窗征求同意就晚了。把这些脚本拦住,或用 Consent Mode 的默认拒绝。
拒绝藏在第二层
大大的「接受」旁边只有一个小小的「设置」链接,正是监管最先查的做法。拒绝要放在第一层、同样显眼。
选完就再也找不到
弹窗不再出现,访客就没法撤回同意。保留悬浮按钮,或在页脚放一个带 data-cc-open 的链接。
分类和实际不符
没有广告代码却列「营销」,或把统计 Cookie 说成「必要」,都会让弹窗失实。先盘点,再勾选。
统计不用在弹窗里占一格
TapCub 的统计脚本默认不在访客设备上写任何东西,统计访问不依赖弹窗;需要同意的地方,合规中心按地区处理。
网站一定要做 Cookie 弹窗吗?
面向欧盟、英国访客时,在设备上存储或读取非必要信息之前要先征得同意(ePrivacy 指令第 5(3) 条及各国法律)。只用登录会话、购物车这类必要存储的,这些项不需要弹窗;统计和广告 Cookie 通常需要。国内法律没有专门的 Cookie 条款,但用 Cookie 等收集个人信息时要遵守《个人信息保护法》的告知和同意规定。本页是模板,不是法律意见,具体请咨询律师。
做了 Cookie 弹窗就符合 GDPR 了吗?
不是。弹窗只记录选择,还需要:脚本真的按选择执行、政策里写清每个用途、依赖同意时保留同意记录、提供撤回方式。生成的代码负责弹窗这一块,并能把选择接到 Google Consent Mode 和被拦截的脚本上。
「拒绝」按钮必须和「接受」一样吗?
法国 CNIL 2020 年的建议给出的做法是「全部拒绝」和「全部接受」外观相同;EDPB 的 Cookie 横幅工作组也把第一层没有拒绝按钮、误导性的颜色和对比度列为审查重点。稳妥的做法就是本工具用的:两个按钮同样式、都在第一层。
Cookie 同意的选择应该保存多久?
GDPR 没有给出固定天数。选一个能在 Cookie 政策里说明理由的期限,到期再问一次,并在政策里写清选择保存多久。填好天数;每次新增用途时改一下版本号,所有人都会重新选择。
怎么在访客同意前不加载 Google Analytics?
两种做法。打开 Consent Mode,把 head 代码放在 Google 代码前面:GA4 以 analytics_storage = denied 启动,访客接受后再更新。或者整段拦住:把 <script> 改成 <script type="text/plain" data-cc-category="analytics">,弹窗只在同意后执行它。
访客以后能改主意吗?
能。做出选择后,角落里会留一个「Cookie 设置」按钮;任何带 data-cc-open 的元素或调用 CookieConsent.open() 都会重新打开分类设置,并显示上次保存的选择。
国内的 App 隐私弹窗、小程序隐私授权也能用这个吗?
不能直接用。App 首次启动的隐私政策弹窗、微信小程序的隐私授权弹窗是各自平台的机制,有专门的接口和审核要求。本工具生成的是网页上的 Cookie 同意弹窗,主要用于面向欧盟、英国、美国加州访客的网站。
预览会在 tapcub.com 写 Cookie 吗?
不会。预览放在隔离的 iframe 里,拿不到 Cookie 和 localStorage,生成的代码会退回内存保存;改设置或离开页面后选择就消失了。