← 全部文章

教程

禁用暗黑模式:虚假紧迫感、偷偷勾选的复选框

「仅剩 2 件库存。」「现在有 12 人正在浏览此商品。」一个每次刷新都重置的倒计时。一个预先勾选、加上你从未要求的保险的复选框。这些就是暗黑模式 —— 旨在催促和欺骗你的界面把戏。本文介绍如何用 CSS 和 JavaScript 中和常见的几种,让你能头脑清醒地购物。

什么算暗黑模式

暗黑模式是一种利用注意力和默认值的运作方式、以你的代价让企业受益的 UI 选择。在购物页面上值得解除武装的有:

你无法阻止一个网站被这样设计,但你可以编辑它在你的浏览器里如何渲染。

识别虚假倒计时

真正的截止时间对所有人都一样,并且能挺过一次刷新。虚假的会重置。快速测试:记下计时器,刷新页面,再看一眼。如果它跳回了「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 });

changeinput 事件很重要 —— 没有它们,即使复选框看起来未勾选,页面总价可能仍然包含那个附加项。一定要验证价格更新了。

拆解羞辱式确认的措辞

你不容易重写文字,但你可以阻止羞辱式确认起作用:让拒绝选项和接受选项一样醒目、一样中性。如果一个站点把「不了」做成小小的灰色文字,给它重新换皮:

/* 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 块,看着人为制造的压力从你访问的每个商品页面上消失。

为这篇文章评分

暂无评分 — 成为第一个。

自己动手试试

安装 JustZix,粘贴本文中的任意代码片段。两分钟,从零到一条在你所有设备上生效的规则。

获取 JustZix

功能 · 工作原理 · 示例 · 应用场景