布局模板开发
布局模板是 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 %} – {{ 'general.meta.tags' | t: tags: current_tags | join: ', ' }}{% endif -%}
{%- if current_page != 1 %} – {{ '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>© {{ '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 主题布局!
下一步学习
完成布局模板开发后,建议继续学习:
最后更新时间: