PAYATHON 2026

如何替换 Ecwid checkout button 并避免自动恢复?

支付小周

结论

Ecwid.OnPageLoaded 只能说明 Ecwid 已切换到某个逻辑页面,并不代表购物车 DOM 已完成最后一次渲染。Ecwid 后续的异步更新可能会重新创建 Checkout button,因此直接调用 .hide() 或替换节点,效果往往无法持续。

建议采用以下方式:

  1. 用 CSS 持续隐藏原 Checkout button;
  2. 以幂等方式插入自定义 button,避免重复添加;
  3. 通过事件委托处理点击;
  4. 收集信息后,先通过 Ecwid API 写入订单备注,再进入正常的 checkout;
  5. 如果自定义 button 所在的容器也会被重建,再使用 MutationObserver,不要使用高频 timer。

为什么 button 会自动恢复

下面的代码只会修改执行时已经存在的 DOM 节点:

$button.hide();

.hide() 会给当前节点添加内联样式。当 Ecwid 因购物车校验、运费计算、库存更新或其他异步操作重新渲染购物车区域时,旧节点会被删除。新生成的 Checkout button 没有原来的内联样式,所以看起来像是被“恢复”了。

通常并不是其他脚本撤销了 .hide(),而是 Ecwid 替换了整个节点。Ecwid.OnPageLoaded 也不是 Checkout button 专用的“渲染完成”事件,不适合用来执行一次性的 DOM 替换。

推荐实现

先用 CSS 隐藏 Ecwid 原 button。即使节点被重新创建,这条 CSS 规则仍然有效:

.ecwid-productBrowser-CART
.ecwid-productBrowser-cart-checkoutButton {
  display: none !important;
}

然后以幂等方式插入自定义 button:

(function () {
  var customButtonId = 'delivery-options-button';

  function ensureCustomButton() {
    var checkoutButton = document.querySelector(
      '.ecwid-productBrowser-cart-checkoutButton'
    );

    if (!checkoutButton) {
      return;
    }

    var parent = checkoutButton.parentNode;

    if (!parent || document.getElementById(customButtonId)) {
      return;
    }

    var button = document.createElement('button');
    button.id = customButtonId;
    button.type = 'button';
    button.textContent = 'Delivery Options';

    parent.appendChild(button);
  }

  Ecwid.OnPageLoaded.add(function (page) {
    if (page.type === 'CART') {
      ensureCustomButton();
    }
  });

  document.addEventListener('click', function (event) {
    var button = event.target.closest('#' + customButtonId);

    if (!button) {
      return;
    }

    event.preventDefault();
    openDeliveryOptions();
  });

  function openDeliveryOptions() {
    // 显示表单或弹窗,在这里收集额外信息。
  }
})();

点击事件通过事件委托绑定在 document 上。即使自定义 button 随购物车区域一起被删除并重新插入,也不需要重新绑定 click handler。

处理 Ecwid 的后续重绘

如果实际测试发现,OnPageLoaded 触发后 Checkout button 的父容器仍会被重建,可以使用 MutationObserver 监听 DOM 变化:

(function () {
  var customButtonId = 'delivery-options-button';
  var scheduled = false;

  function ensureCustomButton() {
    scheduled = false;

    var checkoutButton = document.querySelector(
      '.ecwid-productBrowser-cart-checkoutButton'
    );

    if (!checkoutButton) {
      return;
    }

    var parent = checkoutButton.parentNode;

    if (!parent || document.getElementById(customButtonId)) {
      return;
    }

    var button = document.createElement('button');
    button.id = customButtonId;
    button.type = 'button';
    button.textContent = 'Delivery Options';

    parent.appendChild(button);
  }

  function scheduleUpdate() {
    if (scheduled) {
      return;
    }

    scheduled = true;
    window.requestAnimationFrame(ensureCustomButton);
  }

  var observer = new MutationObserver(scheduleUpdate);

  observer.observe(document.body, {
    childList: true,
    subtree: true
  });

  Ecwid.OnPageLoaded.add(function (page) {
    if (page.type === 'CART') {
      scheduleUpdate();
    }
  });

  document.addEventListener('click', function (event) {
    if (!event.target.closest('#' + customButtonId)) {
      return;
    }

    event.preventDefault();
    openDeliveryOptions();
  });

  function openDeliveryOptions() {
    // 收集额外信息。
  }
})();

MutationObserver 只在 DOM 发生变化时执行,比反复运行 setInterval 更稳定。回调仍需保持幂等,并通过 requestAnimationFrame 合并短时间内连续发生的变更。

写入 Notes field 后继续 checkout

用户确认输入后,可以先写入订单备注,再进入 Ecwid checkout。Ecwid JavaScript API 是否可用,取决于当前商店和加载的 API 版本,因此需要先检查 Ecwid.Cart.setOrderComments 是否存在:

function continueToCheckout(extraInformation) {
  var notes = [
    'Delivery options:',
    extraInformation
  ].join('\n');

  if (
    Ecwid.Cart &&
    typeof Ecwid.Cart.setOrderComments === 'function' &&
    typeof Ecwid.Cart.gotoCheckout === 'function'
  ) {
    Ecwid.Cart.setOrderComments(notes);
    Ecwid.Cart.gotoCheckout();
    return;
  }

  console.error(
    'Required Ecwid Cart API methods are not available on this storefront.'
  );
}

如果 setOrderComments 是异步方法,或者当前 Ecwid 版本提供 callback 或 Promise,应按照该版本的实际 API 定义,等写入完成后再调用 Ecwid.Cart.gotoCheckout(),以免页面在备注保存前跳转。

注意事项

  • 不要依赖 $button.hide(),它只对当前节点有效。
  • 不要反复克隆或替换 Ecwid 的原 button,Ecwid 内部可能保存了该节点的状态或事件。
  • 自定义元素必须使用固定 id 或其他唯一标记,防止重复插入。
  • CSS selector 属于 Ecwid 的页面实现细节。主题或 storefront 版本升级后,可能需要重新检查。
  • 如果额外信息属于订单的正式结构化数据,应优先考虑 Ecwid 支持的 checkout custom fields,不要把所有内容都拼入 Notes field。
  • 不要假设 Ecwid 提供了 Checkout button 专用的公开重绘事件。在没有官方 API 保证的情况下,CSS 配合 MutationObserver 是更稳妥的 DOM 层方案。

备注:内容仅供参考。