禁用暗黑模式:虚假紧迫感、偷偷勾选的复选框
「仅剩 2 件库存。」「现在有 12 人正在浏览此商品。」一个每次刷新都重置的倒计时。一个预先勾选、加上你从未要求的保险的复选框。这些就是暗黑模式 —— 旨在催促和欺骗你的界面把戏。本文介绍如何用 CSS 和 JavaScript 中和常见的几种,让你能头脑清醒地购物。
什么算暗黑模式
暗黑模式是一种利用注意力和默认值的运作方式、以你的代价让企业受益的 UI 选择。在购物页面上值得解除武装的有:
- 虚假紧迫感 —— 倒计时,「促销将在 04:59 后结束」,刷新就重新开始。
- 虚假稀缺 —— 「仅剩 2 件」、「热销中」,这些数字与真实库存毫无关联。
- 虚假社会认同 —— 「12 人正在浏览此商品」,随机且无法验证。
- 偷偷塞进购物车 —— 预先勾选的附加项:保险、捐款、加急配送。
- 羞辱式确认 —— 把拒绝链接的措辞写成羞辱你:「不了,我不喜欢省钱」。
你无法阻止一个网站被这样设计,但你可以编辑它在你的浏览器里如何渲染。
识别虚假倒计时
真正的截止时间对所有人都一样,并且能挺过一次刷新。虚假的会重置。快速测试:记下计时器,刷新页面,再看一眼。如果它跳回了「05:00」,那就是演戏。打开 DevTools 的 Elements 面板,找到那个文字在跳动的元素 —— 记下它的类。
用 CSS 隐藏虚假紧迫感小部件
最快的修法是让这些施压小部件彻底消失。它们几乎总是干净分离、按其本来面目命名的元素:
/* Fake urgency, scarcity and social-proof widgets */
.countdown-timer,
.sale-timer,
.urgency-banner,
.stock-warning,
.low-stock,
.viewing-now,
.recently-viewed-count,
[class*="countdown" i],
[class*="urgency" i],
[class*="scarcity" i],
[class*="viewers" i],
[class*="people-viewing" i] {
display: none !important;
}
一如既往,i 标志让属性匹配大小写不敏感。从这个块起手;它能瞬间清掉大多数零售页面上可见的噪声。
冻结倒计时而不是隐藏它
有时你想看到促销确实存在,但不想被一个嘀嗒走动的钟催促。冻结它:让更新它的脚本停下来。粗暴但可靠的办法是在页面脚本启动之前、在 document_start 把计时器函数解除武装:
// Freeze countdowns: defang the timers the page uses to tick
const realSetInterval = window.setInterval;
window.setInterval = function (fn, delay, ...args) {
// Block fast-ticking timers (countdowns update ~every second)
if (delay && delay <= 1000) {
console.log('[dark-patterns] blocked a 1s interval');
return -1; // a harmless fake id
}
return realSetInterval.call(this, fn, delay, ...args);
};
这是刻意写宽的 —— 它会停掉每一个亚秒级的 interval,在商品页面上那几乎总是一个倒计时。如果它冻结了你需要的东西,在拦截之前检查 fn.toString() 里有没有 countdown 之类的关键词来收窄它。
一个更温和的替代:让计时器运行,但把它显示的文字固定一次,然后停止对那个节点的更新:
// Pin the countdown text and stop it changing
const timer = document.querySelector('[class*="countdown" i]');
if (timer) {
const frozen = timer.textContent;
new MutationObserver(() => {
if (timer.textContent !== frozen) timer.textContent = frozen;
}).observe(timer, { childList: true, subtree: true, characterData: true });
}
取消偷偷勾选的附加项复选框
最昂贵的暗黑模式是预先勾选的复选框:配送保险、一个「为慈善凑整」、一个周期性会员。它默认被勾选,措辞被写得让你一扫而过。在页面加载后用 JavaScript 取消勾选它们:
// Un-tick pre-checked add-on checkboxes
function unchargeMe() {
document.querySelectorAll('input[type="checkbox"]:checked')
.forEach(box => {
const label = (box.closest('label')?.textContent
|| box.parentElement?.textContent || '').toLowerCase();
// Only touch boxes that smell like a paid add-on
if (/insurance|protection|warranty|donation|round up|express|priority|membership|subscribe/.test(label)) {
box.checked = false;
// Fire events so the page recalculates the total
box.dispatchEvent(new Event('change', { bubbles: true }));
box.dispatchEvent(new Event('input', { bubbles: true }));
console.log('[dark-patterns] un-checked:', label.trim().slice(0, 60));
}
});
}
unchargeMe();
// Re-run when the cart re-renders
new MutationObserver(unchargeMe)
.observe(document.body, { childList: true, subtree: true });
change 和 input 事件很重要 —— 没有它们,即使复选框看起来未勾选,页面总价可能仍然包含那个附加项。一定要验证价格更新了。
拆解羞辱式确认的措辞
你不容易重写文字,但你可以阻止羞辱式确认起作用:让拒绝选项和接受选项一样醒目、一样中性。如果一个站点把「不了」做成小小的灰色文字,给它重新换皮:
/* Make the decline link a normal, readable button */
.modal a[class*="decline" i],
.modal button[class*="dismiss" i],
[class*="confirm-shame" i] {
font-size: 1rem !important;
color: inherit !important;
text-decoration: underline !important;
opacity: 1 !important;
}
用 JustZix 限定作用域
这些规则里有些很激进 —— 拦截亚秒级 interval 可能影响一个你其实想要的实时更新小部件。所以给它们限定作用域。创建一个叫「反暗黑模式」的文件夹,为你使用的每个购物站点加一条规则,每条都带紧凑的 URL 模式。不购物时把整个文件夹关掉。你能得到一个更平静的结账,而别处没有副作用。
另见
- 移除软付费墙 —— 同样的移除遮罩层技能。
- 构建一个价格与库存监视器 —— 跟踪真实价格而不是虚假紧迫感。
- 示例区里的可直接粘贴的代码片段。
购物应该是一个决定,而不是一个反应。安装 JustZix,粘上面那个 CSS 块,看着人为制造的压力从你访问的每个商品页面上消失。
为这篇文章评分
暂无评分 — 成为第一个。