shopify组件section开发:粘性购物车


预计阅读时间:12 分钟

1. 业务价值 (Business Value)

本组件旨在解决长页面产品详情导致的“购买中断”问题。通过在用户向下滑动时持续提供购买入口,并实时同步用户所选的规格信息,显著降低加购流失率并提升转化。

2. 核心逻辑与功能 (Core Functions)

2.1 智能滚动监听 (Scroll Intelligence)

  • 触发机制:利用 Intersection Observer API 监听。当指定的 Target Selector(如产品主图或描述块)完全移出视口顶部时,横幅自动滑入。
  • 自动收回:用户回滚至主购买区域时,横幅自动隐藏,避免视觉冗余。

2.2 深度变体同步 (Variant Synchronization)

  • 实时监听:组件通过 MutationObserver 实时监测浏览器 URL 变化(Shopify 切换变体时的标准行为)。
  • 动态抓取:切换变体时,JS 会异步请求最新的产品数据,秒级同步横幅内的:
  • 变体名称 (如:Red / XL)
  • 实时售价 (Price)
  • 划线价/促销价 (Compare at Price)

  • 智能清洗:自动过滤并隐藏 Shopify 系统默认生成的 "Default Title"。若产品无变体,横幅将保持简洁,仅显示产品标题。

2.3 价格排列控制 (Price Layout Control)

  • 顺序切换:支持在后台一键切换划线价与现价的左右顺序(Flex-direction 控制),适配不同国家用户的阅读习惯(如:先看原价对比,或先看现价)。

2.4 原生加购继承 (Native Add-to-Cart Proxy)

  • 零冲突方案:点击横幅按钮不执行复杂的 AJAX 代码,而是通过 JS 触发页面原有的加购按钮点击事件。
  • 兼容性:完美兼容主题自带的抽屉购物车 (Drawer Cart)、弹窗提醒 (Popup) 以及第三方购物车 App。

3. 技术参数 (Technical Specifications)

特性 技术栈 / 实现方式
框架依赖 无(Vanilla JavaScript / Native Liquid)
价格处理 自动读取 shop.money_format 全局格式
移动端优化 隐藏长标题、按钮通栏化、增大点击热区
性能损耗 < 10ms 执行延迟(异步请求与主流程解耦)
CSS 层级 z-index: 2147483647 (确保在全站最顶层)

4. 后台配置说明 (Schema Settings Guide)

管理员可在 Theme Customize 界面进行以下配置:

4.1 核心配置

  • Target Selector: 监听的目标 CSS 选择器(默认 .product-page)。
  • Price Order: 划线价位置。可选 row(左侧)或 row-reverse(右侧)。

4.2 UI 样式

  • Colors: 背景、文字、现价(高亮色)、原价(置灰)的颜色选择器。
  • Button Styling: 按钮文字内容、背景色、圆角(0-50px)实时调整。

4.3 布局宽度

  • Max Width: 横幅内容的显示上限宽度,确保在大屏显示器下与主题对齐。

5. 开发者维护指南 (Maintenance)

  1. 类名适配:若在不同主题部署,需通过浏览器 F12 确认产品主容器的 Class,并更新至 Target Selector 配置项。
  2. 样式覆盖:如遇某些特定主题的 Header 遮挡,可适当降低 z-index 或调整 bottom 偏移量。
  3. 多货币支持:代码已适配多货币切换,会自动抓取当前币种符号。

最终代码,代码位置位于sections/sticky-cart-bar.liquid,使用时添加名为“:

{% comment %}
  Sticky Add to Cart Bar - 最终旗舰版
  功能:
  1. 变体实时同步:跟随页面选择器自动更新价格和名称
  2. 智能显示:隐藏无变体产品的 "Default Title"
  3. 划线价位置:支持后台自由切换(左/右)
  4. 样式配置:背景、文字、价格颜色、按钮圆角均可自定义
  5. 性能优化:原生JS,零依赖
{% endcomment %}

{%- if template.name == 'product' -%}
  {%- assign target_class = section.settings.target_selector -%}
  {%- assign cart_btn_selector = section.settings.add_to_cart_selector -%}
  {%- assign current_variant = product.selected_or_first_available_variant -%}

  <div id="sticky-cart-wrapper" class="sticky-cart-bar" style="display: none; background-color: {{ section.settings.bg_color }};">
    <div class="sticky-cart-container">

      <div class="sticky-cart-info">
        <div class="sticky-cart-image">
          <img id="sticky-variant-image" src="{{ current_variant.featured_image | default: product.featured_image | img_url: '100x100', crop: 'center' }}" alt="{{ product.title }}" width="50" height="50">
        </div>
        <div class="sticky-cart-details">
          <span class="sticky-product-title" style="color: {{ section.settings.text_color }};">{{ product.title }}</span>
          <span id="sticky-variant-title" class="sticky-variant-title" style="color: {{ section.settings.text_color }}; {% if current_variant.title contains 'Default Title' %}display:none;{% endif %}">
            {{ current_variant.title }}
          </span>
          <div class="sticky-price-wrapper" style="flex-direction: {{ section.settings.price_order }};">
            <span id="sticky-compare-price" class="sticky-product-compare-price" style="color: {{ section.settings.compare_price_color }}; {% unless current_variant.compare_at_price > current_variant.price %}display:none;{% endunless %}">
              {{ current_variant.compare_at_price | money }}
            </span>
            <span id="sticky-current-price" class="sticky-product-price" style="color: {{ section.settings.price_color }};">
              {{ current_variant.price | money }}
            </span>
          </div>
        </div>
      </div>

      <div class="sticky-cart-actions">
        <button type="button" class="sticky-submit-btn" onclick="handleStickyAdd()">
          {{ section.settings.button_text }}
        </button>
      </div>
    </div>
  </div>

  <style>
    /* 基础样式优化 */
    .sticky-cart-bar { position: fixed; bottom: 0; left: 0; width: 100%; box-shadow: 0 -4px 15px rgba(0,0,0,0.1); z-index: 2147483647; padding: 10px 15px; transform: translateY(100%); transition: transform 0.4s ease; box-sizing: border-box; }
    .sticky-cart-bar.is-visible { display: block !important; transform: translateY(0); }
    .sticky-cart-container { max-width: {{ section.settings.max_width }}px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; gap: 10px; }

    /* 左侧信息栏 */
    .sticky-cart-info { display: flex; align-items: center; gap: 10px; min-width: 0; flex-shrink: 1; } /* min-width: 0 是防止flex子元素溢出的关键 */
    .sticky-cart-image { flex-shrink: 0; }
    .sticky-cart-image img { border-radius: 4px; object-fit: cover; display: block; }

    .sticky-cart-details { display: flex; flex-direction: column; min-width: 0; }
    .sticky-product-title { font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .sticky-variant-title { font-size: 11px; opacity: 0.7; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

    /* 价格栏 */
    .sticky-price-wrapper { display: flex; align-items: center; gap: 6px; margin-top: 2px; flex-wrap: wrap; }
    .sticky-product-compare-price { text-decoration: line-through; font-size: 12px; opacity: 0.6; white-space: nowrap; }
    .sticky-product-price { font-size: 14px; font-weight: 700; white-space: nowrap; }

    /* 按钮 */
    .sticky-cart-actions { flex-shrink: 0; }
    .sticky-submit-btn { background: {{ section.settings.btn_bg_color }}; color: {{ section.settings.btn_text_color }}; border: none; padding: 10px 20px; font-size: 13px; font-weight: bold; cursor: pointer; border-radius: {{ section.settings.btn_radius }}px; text-transform: uppercase; white-space: nowrap; transition: opacity 0.2s; }

    /* 移动端适配修复 */
    @media (max-width: 767px) {
      .sticky-cart-bar { padding: 8px 12px; }
      .sticky-product-title { display: none; } /* 移动端通常隐藏标题以腾出空间 */
      .sticky-cart-image img { width: 40px; height: 40px; }
      .sticky-cart-info { gap: 8px; }

      .sticky-cart-actions { flex-grow: 1; margin-left: 5px; }
      .sticky-submit-btn { width: 100%; padding: 12px 10px; font-size: 12px; letter-spacing: 0; }

      .sticky-price-wrapper { gap: 4px; }
      .sticky-product-price { font-size: 13px; }
      .sticky-product-compare-price { font-size: 11px; }
    }

    /* 针对超小屏幕(iPhone SE等)的极简处理 */
    @media (max-width: 350px) {
      .sticky-variant-title { display: none; }
      .sticky-cart-image { display: none; }
    }
  </style>

  <script>
    // ... 原有脚本保持不变 ...
    function formatMoney(cents) {
      const format = "{{ shop.money_format }}";
      if (!cents) return "";
      let value = (cents / 100).toFixed(2);
      return format.replace(/\{\{\s*amount\s*\}\}/, value).replace(/\{\{\s*amount_no_decimals\s*\}\}/, Math.round(value));
    }

    function handleStickyAdd() {
      const customSelector = "{{ cart_btn_selector }}";
      const selectors = [customSelector, '.cart-btn', 'form[action*="/cart/add"] button[type="submit"]', 'button[name="add"]'];
      let mainBtn = null;
      for (let selector of selectors) {
        if (!selector) continue;
        const found = document.querySelector(selector);
        if (found && found.offsetParent !== null) { 
          mainBtn = found;
          break; 
        }
      }
      if (mainBtn) { mainBtn.click(); } else {
        const form = document.querySelector('form[action*="/cart/add"]');
        if (form) form.submit();
      }
    }

    document.addEventListener('DOMContentLoaded', function() {
      const stickyBar = document.getElementById('sticky-cart-wrapper');
      const target = document.querySelector("{{ target_class }}");
      if (target && stickyBar) {
        const observer = new IntersectionObserver((entries) => {
          entries.forEach(entry => {
            const shouldShow = !entry.isIntersecting && entry.boundingClientRect.top < 0;
            if (shouldShow) stickyBar.classList.add('is-visible');
            else stickyBar.classList.remove('is-visible');
          });
        }, { threshold: 0 });
        observer.observe(target);
      }
      let lastUrl = location.href;
      new MutationObserver(() => {
        if (location.href !== lastUrl) {
          lastUrl = location.href;
          fetch(window.location.pathname + '.js').then(res => res.json()).then(product => {
            const urlParams = new URLSearchParams(window.location.search);
            const variantId = urlParams.get('variant');
            const variant = variantId ? product.variants.find(v => v.id == variantId) : product.variants[0];
            if (variant) {
              const vt = document.getElementById('sticky-variant-title');
              if (variant.title.includes('Default Title')) { vt.style.display = 'none'; } 
              else { vt.style.display = 'block'; vt.textContent = variant.title; }
              document.getElementById('sticky-current-price').innerHTML = formatMoney(variant.price);
              const cp = document.getElementById('sticky-compare-price');
              if (variant.compare_at_price > variant.price) {
                cp.style.display = 'inline'; cp.innerHTML = formatMoney(variant.compare_at_price);
              } else { cp.style.display = 'none'; }
            }
          });
        }
      }).observe(document, {subtree: true, childList: true});
    });
  </script>
{%- endif -%}

{% schema %}
{
  "name": "Sticky Cart Bar",
  "settings": [
    {
      "type": "header",
      "content": "按钮绑定设置"
    },
    {
      "type": "text",
      "id": "add_to_cart_selector",
      "label": "页面加购按钮类名 (Selector)",
      "default": ".cart-btn"
    },
    {
      "type": "text",
      "id": "target_selector",
      "label": "监听触发块 (Target Class)",
      "default": ".product-page"
    },
    {
      "type": "select",
      "id": "price_order",
      "label": "价格顺序",
      "options": [
        { "value": "row", "label": "划线价在前" },
        { "value": "row-reverse", "label": "现价在前" }
      ],
      "default": "row"
    },
    { "type": "color", "id": "bg_color", "label": "背景色", "default": "#ffffff" },
    { "type": "color", "id": "text_color", "label": "文字色", "default": "#121212" },
    { "type": "color", "id": "price_color", "label": "现价色", "default": "#ff0000" },
    { "type": "color", "id": "compare_price_color", "label": "原价色", "default": "#9b9b9b" },
    { "type": "text", "id": "button_text", "label": "按钮文字", "default": "ADD TO CART" },
    { "type": "color", "id": "btn_bg_color", "label": "按钮背景", "default": "#000000" },
    { "type": "color", "id": "btn_text_color", "label": "按钮文字颜色", "default": "#ffffff" },
    { "type": "range", "id": "btn_radius", "label": "圆角", "min": 0, "max": 50, "step": 1, "default": 4 },
    { "type": "range", "id": "max_width", "label": "最大宽度", "min": 800, "max": 1600, "step": 10, "default": 1200 }
  ],
  "presets": [{ "name": "Sticky Cart Bar" }]
}
{% endschema %}

本文由 EgbertZhang 原创,转载请注明出处。

📖相关推荐