Skip to Content
🎉 探索 Shopify 的无限可能 结构化知识 + 实战案例,持续更新中...
Liquid 开发布局模板开发

布局模板开发

布局模板是 Shopify 主题的骨架,定义了网站的整体结构和共享元素。本指南将详细介绍如何开发高效、灵活的布局模板。

布局模板基础

1. theme.liquid - 主布局文件

<!doctype html> <html class="no-js" lang="{{ request.locale.iso_code }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="theme-color" content="{{ settings.color_accent }}"> <link rel="canonical" href="{{ canonical_url }}"> {%- if settings.favicon != blank -%} <link rel="icon" type="image/png" href="{{ settings.favicon | img_url: '32x32' }}"> {%- endif -%} {%- capture seo_title -%} {%- if request.page_type == 'search' and search.terms != blank -%} {{ 'general.search.heading' | t: count: search.results_count }}: {{ 'general.search.results_with_count' | t: terms: search.terms, count: search.results_count }} {%- else -%} {{ page_title }} {%- endif -%} {%- if current_tags %} &ndash; {{ 'general.meta.tags' | t: tags: current_tags | join: ', ' }}{% endif -%} {%- if current_page != 1 %} &ndash; {{ 'general.meta.page' | t: page: current_page }}{% endif -%} {%- assign title_separator = ' | ' -%} {%- unless page_title contains shop.name -%} {{ title_separator }}{{ shop.name }} {%- endunless -%} {%- endcapture -%} <title>{{ seo_title | strip }}</title> {%- if page_description -%} <meta name="description" content="{{ page_description | escape }}"> {%- endif -%} {% render 'meta-tags' %} <script> document.documentElement.className = document.documentElement.className.replace('no-js', 'js'); window.theme = { settings: { cartType: {{ settings.cart_type | json }}, moneyFormat: {{ shop.money_format | json }}, moneyWithCurrencyFormat: {{ shop.money_with_currency_format | json }} }, strings: { addToCart: {{ 'products.product.add_to_cart' | t | json }}, soldOut: {{ 'products.product.sold_out' | t | json }}, unavailable: {{ 'products.product.unavailable' | t | json }} }, routes: { cart: '{{ routes.cart_url }}', cartAdd: '{{ routes.cart_add_url }}', cartChange: '{{ routes.cart_change_url }}' } }; </script> {% render 'css-variables' %} <link rel="stylesheet" href="{{ 'theme.css' | asset_url }}"> {%- unless settings.type_header_font.system? -%} <link rel="preconnect" href="https://fonts.shopifycdn.com" crossorigin> {%- endunless -%} <script>window.performance && window.performance.mark && window.performance.mark('shopify.content_for_header.start');</script> {{ content_for_header }} <script>window.performance && window.performance.mark && window.performance.mark('shopify.content_for_header.end');</script> {% render 'microdata-schema' %} </head> <body class="gradient{% if customer %} customer-logged-in{% endif %} template-{{ request.page_type | handle }}"> <a class="skip-to-content-link button visually-hidden" href="#MainContent"> {{ 'accessibility.skip_to_text' | t }} </a> {% section 'announcement-bar' %} {% section 'header' %} <main id="MainContent" class="content-for-layout focus-none" role="main" tabindex="-1"> {{ content_for_layout }} </main> {% section 'footer' %} <ul hidden> <li id="a11y-refresh-page-message">{{ 'accessibility.refresh_page' | t }}</li> <li id="a11y-new-window-message">{{ 'accessibility.link_messages.new_window' | t }}</li> </ul> <script> window.shopUrl = {{ request.origin | json }}; window.routes = { cart_add_url: {{ routes.cart_add_url | json }}, cart_change_url: {{ routes.cart_change_url | json }}, cart_update_url: {{ routes.cart_update_url | json }}, cart_url: {{ routes.cart_url | json }}, predictive_search_url: {{ routes.predictive_search_url | json }} }; </script> {% if settings.predictive_search_enabled %} <script src="{{ 'predictive-search.js' | asset_url }}" defer="defer"></script> {% endif %} <script src="{{ 'theme.js' | asset_url }}" defer="defer"></script> {% render 'cart-drawer' %} {% if template contains 'product' %} <script src="{{ 'product-form.js' | asset_url }}" defer="defer"></script> {% endif %} </body> </html>

2. 密码保护页面布局

<!-- layout/password.liquid --> <!doctype html> <html class="no-js" lang="{{ request.locale.iso_code }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="robots" content="noindex,nofollow"> <title>{{ shop.name }}</title> {% if page_description %} <meta name="description" content="{{ page_description | escape }}"> {% endif %} {% render 'meta-tags' %} <link rel="stylesheet" href="{{ 'password.css' | asset_url }}"> {{ content_for_header }} </head> <body class="password-page"> <div class="password-container"> <header class="password-header"> {% if shop.logo %} <img src="{{ shop.logo | img_url: '200x' }}" alt="{{ shop.name }}" class="password-logo"> {% else %} <h1 class="password-shop-name">{{ shop.name }}</h1> {% endif %} </header> <main class="password-content"> {{ content_for_layout }} </main> <footer class="password-footer"> <p>&copy; {{ 'now' | date: '%Y' }} {{ shop.name }}</p> </footer> </div> <script src="{{ 'password.js' | asset_url }}" defer></script> </body> </html>

布局组件化

1. 头部组件片段

<!-- snippets/layout-head.liquid --> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1"> <!-- SEO 元标签 --> {% render 'seo-meta-tags' %} <!-- 性能优化 --> <link rel="dns-prefetch" href="https://cdn.shopify.com"> <link rel="dns-prefetch" href="https://monorail-edge.shopifysvc.com"> <!-- 字体预加载 --> {% unless settings.type_header_font.system? %} <link rel="preconnect" href="https://fonts.shopifycdn.com" crossorigin> {% endunless %} <!-- 关键 CSS 内联 --> {% render 'critical-css' %} <!-- 非关键 CSS 异步加载 --> <link rel="preload" href="{{ 'theme.css' | asset_url }}" as="style" onload="this.onload=null;this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="{{ 'theme.css' | asset_url }}"></noscript> <!-- 主题配置 --> <script> window.theme = { settings: {{ settings | json }}, strings: { addToCart: {{ 'products.product.add_to_cart' | t | json }}, soldOut: {{ 'products.product.sold_out' | t | json }}, unavailable: {{ 'products.product.unavailable' | t | json }} }, routes: { cart: {{ routes.cart_url | json }}, cartAdd: {{ routes.cart_add_url | json }}, cartChange: {{ routes.cart_change_url | json }} } }; </script>

2. 页脚脚本组件

<!-- snippets/layout-scripts.liquid --> <!-- 核心功能脚本 --> <script src="{{ 'theme.js' | asset_url }}" defer></script> <!-- 条件加载脚本 --> {% if settings.cart_type == 'drawer' %} <script src="{{ 'cart-drawer.js' | asset_url }}" defer></script> {% endif %} {% if template contains 'product' %} <script src="{{ 'product-form.js' | asset_url }}" defer></script> <script src="{{ 'product-gallery.js' | asset_url }}" defer></script> {% endif %} {% if template contains 'collection' %} <script src="{{ 'collection-filters.js' | asset_url }}" defer></script> {% endif %} {% if settings.predictive_search_enabled %} <script src="{{ 'predictive-search.js' | asset_url }}" defer></script> {% endif %} <!-- 第三方脚本 --> {% render 'analytics-scripts' %}

条件布局系统

1. 多布局支持

<!-- layout/theme.liquid --> {%- liquid assign layout_class = 'layout-default' case template.name when 'product' assign layout_class = 'layout-product' when 'collection' assign layout_class = 'layout-collection' when 'page' if page.handle contains 'landing' assign layout_class = 'layout-landing' endif when 'blog' assign layout_class = 'layout-blog' endcase if customer assign layout_class = layout_class | append: ' customer-logged-in' endif -%} <body class="{{ layout_class }} template-{{ template.name | handle }}"> <!-- 根据不同布局加载不同的头部 --> {% case layout_class %} {% when 'layout-landing' %} {% section 'header-landing' %} {% when 'layout-product' %} {% section 'header-product' %} {% else %} {% section 'header' %} {% endcase %} <main id="MainContent" class="content-for-layout"> {{ content_for_layout }} </main> <!-- 根据不同布局加载不同的页脚 --> {% unless layout_class contains 'layout-landing' %} {% section 'footer' %} {% endunless %} </body>

2. 响应式布局容器

<!-- snippets/responsive-container.liquid --> {%- liquid assign container_class = 'container' assign max_width = section.settings.container_width | default: 'default' case max_width when 'narrow' assign container_class = container_class | append: ' container--narrow' when 'wide' assign container_class = container_class | append: ' container--wide' when 'full' assign container_class = container_class | append: ' container--full' endcase if section.settings.container_padding assign container_class = container_class | append: ' container--padded' endif -%} <div class="{{ container_class }}"> <div class="container__inner"> {{ content }} </div> </div> <style> .container { max-width: 1200px; margin: 0 auto; padding: 0 15px; } .container--narrow { max-width: 800px; } .container--wide { max-width: 1400px; } .container--full { max-width: none; padding: 0; } .container--padded { padding: 30px 15px; } @media (min-width: 768px) { .container { padding: 0 30px; } .container--padded { padding: 60px 30px; } } </style>

性能优化布局

1. 关键路径优化

<!-- snippets/critical-css.liquid --> <style> /* 关键 CSS - 首屏内容样式 */ .header { background: white; padding: 1rem 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; } .main-content { min-height: 50vh; padding: 2rem 0; } /* 加载动画 */ .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: white; display: flex; align-items: center; justify-content: center; z-index: 9999; transition: opacity 0.3s; } .loading-overlay.hidden { opacity: 0; pointer-events: none; } @media (min-width: 768px) { .container { padding: 0 2rem; } } </style>

2. 异步组件加载

<!-- snippets/async-components.liquid --> <script> // 异步组件加载器 class AsyncComponentLoader { constructor() { this.components = new Map(); this.observer = new IntersectionObserver(this.handleIntersection.bind(this)); this.init(); } init() { // 注册延迟加载的组件 document.querySelectorAll('[data-async-component]').forEach(el => { this.observer.observe(el); }); } handleIntersection(entries) { entries.forEach(entry => { if (entry.isIntersecting) { this.loadComponent(entry.target); this.observer.unobserve(entry.target); } }); } async loadComponent(element) { const componentName = element.dataset.asyncComponent; const componentData = element.dataset.componentData; try { // 显示加载状态 element.innerHTML = '<div class="component-loading">加载中...</div>'; // 异步获取组件内容 const response = await fetch(`/apps/components/${componentName}?data=${componentData}`); const html = await response.text(); // 渲染组件 element.innerHTML = html; // 初始化组件脚本 this.initializeComponentScripts(element); } catch (error) { console.error(`Failed to load component ${componentName}:`, error); element.innerHTML = '<div class="component-error">加载失败</div>'; } } initializeComponentScripts(element) { const scripts = element.querySelectorAll('script'); scripts.forEach(script => { const newScript = document.createElement('script'); newScript.textContent = script.textContent; document.head.appendChild(newScript); }); } } // 初始化异步组件加载器 document.addEventListener('DOMContentLoaded', () => { new AsyncComponentLoader(); }); </script>

移动端优化布局

1. 移动端专用布局

<!-- layout/theme.mobile.liquid --> <!doctype html> <html class="no-js mobile-layout" lang="{{ request.locale.iso_code }}"> <head> {% render 'layout-head' %} <!-- 移动端特定样式 --> <link rel="stylesheet" href="{{ 'mobile.css' | asset_url }}"> <!-- PWA 配置 --> <link rel="manifest" href="{{ 'manifest.json' | asset_url }}"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="default"> </head> <body class="mobile-optimized"> <!-- 移动端导航 --> {% section 'mobile-header' %} <!-- 主内容 --> <main id="MainContent" class="mobile-content"> {{ content_for_layout }} </main> <!-- 移动端页脚 --> {% section 'mobile-footer' %} <!-- 移动端专用脚本 --> <script src="{{ 'mobile.js' | asset_url }}" defer></script> <!-- PWA 注册 --> <script> if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); } </script> </body> </html>

2. 自适应布局切换

<!-- snippets/adaptive-layout.liquid --> <script> class AdaptiveLayout { constructor() { this.breakpoint = 768; this.isMobile = window.innerWidth < this.breakpoint; this.init(); } init() { this.setupLayout(); window.addEventListener('resize', this.handleResize.bind(this)); } setupLayout() { if (this.isMobile) { this.enableMobileLayout(); } else { this.enableDesktopLayout(); } } enableMobileLayout() { document.body.classList.add('mobile-layout'); document.body.classList.remove('desktop-layout'); // 加载移动端特定组件 this.loadMobileComponents(); } enableDesktopLayout() { document.body.classList.add('desktop-layout'); document.body.classList.remove('mobile-layout'); // 加载桌面端特定组件 this.loadDesktopComponents(); } handleResize() { const wasMobile = this.isMobile; this.isMobile = window.innerWidth < this.breakpoint; if (wasMobile !== this.isMobile) { this.setupLayout(); } } loadMobileComponents() { // 异步加载移动端组件 import('./mobile-components.js').then(module => { module.init(); }); } loadDesktopComponents() { // 异步加载桌面端组件 import('./desktop-components.js').then(module => { module.init(); }); } } // 初始化自适应布局 new AdaptiveLayout(); </script>

布局调试工具

1. 开发模式布局调试

<!-- snippets/layout-debugger.liquid --> {% if settings.debug_mode %} <div class="layout-debugger" style="position: fixed; top: 10px; right: 10px; z-index: 10000; background: black; color: white; padding: 10px; font-size: 12px;"> <div>模板: {{ template.name }}</div> <div>页面类型: {{ request.page_type }}</div> <div>设备: <span id="device-type"></span></div> <div>断点: <span id="current-breakpoint"></span></div> <div>性能: <span id="load-time"></span>ms</div> </div> <script> // 布局调试器 class LayoutDebugger { constructor() { this.init(); } init() { this.updateDeviceInfo(); this.measurePerformance(); window.addEventListener('resize', () => this.updateDeviceInfo()); } updateDeviceInfo() { const width = window.innerWidth; let deviceType = 'Desktop'; let breakpoint = 'xl'; if (width < 576) { deviceType = 'Mobile'; breakpoint = 'xs'; } else if (width < 768) { deviceType = 'Mobile Large'; breakpoint = 'sm'; } else if (width < 992) { deviceType = 'Tablet'; breakpoint = 'md'; } else if (width < 1200) { deviceType = 'Desktop'; breakpoint = 'lg'; } document.getElementById('device-type').textContent = deviceType; document.getElementById('current-breakpoint').textContent = breakpoint; } measurePerformance() { window.addEventListener('load', () => { const loadTime = window.performance.timing.loadEventEnd - window.performance.timing.navigationStart; document.getElementById('load-time').textContent = loadTime; }); } } new LayoutDebugger(); </script> {% endif %}

通过这些布局模板开发技术,您可以创建高效、灵活且用户友好的 Shopify 主题布局!

下一步学习

完成布局模板开发后,建议继续学习:

  1. 代码组织和结构 - 项目架构最佳实践
  2. 最佳实践 - 开发规范和约定
最后更新时间: