响应式设计实现
响应式设计是现代网站开发的基础要求。本指南介绍如何在 Shopify 主题中实现专业级的响应式设计。
移动优先设计原则
基础CSS架构
/* 移动端优先样式 */
.container {
width: 100%;
padding: 0 1rem;
margin: 0 auto;
}
.grid {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
/* 平板端 */
@media (min-width: 768px) {
.container {
max-width: 1200px;
padding: 0 2rem;
}
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 桌面端 */
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
响应式组件
响应式导航
<!-- snippets/responsive-navigation.liquid -->
<nav class="navigation" data-navigation>
<div class="navigation__container">
<div class="navigation__brand">
<a href="/" class="brand-link">
{{ shop.name }}
</a>
</div>
<button class="navigation__toggle" data-menu-toggle aria-label="打开菜单">
<span></span>
<span></span>
<span></span>
</button>
<div class="navigation__menu" data-menu>
{% for link in linklists.main-menu.links %}
<a href="{{ link.url }}" class="navigation__link">
{{ link.title }}
</a>
{% endfor %}
</div>
</div>
</nav>
<style>
.navigation {
background: white;
padding: 1rem 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.navigation__container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.navigation__toggle {
display: block;
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
}
.navigation__toggle span {
display: block;
width: 25px;
height: 3px;
background: #333;
margin: 5px 0;
transition: 0.3s;
}
.navigation__menu {
position: fixed;
top: 70px;
left: -100%;
width: 100%;
height: calc(100vh - 70px);
background: white;
transition: 0.3s;
display: flex;
flex-direction: column;
padding: 2rem;
}
.navigation__menu.active {
left: 0;
}
.navigation__link {
padding: 1rem 0;
border-bottom: 1px solid #eee;
text-decoration: none;
color: #333;
}
@media (min-width: 768px) {
.navigation__toggle {
display: none;
}
.navigation__menu {
position: static;
height: auto;
background: none;
flex-direction: row;
padding: 0;
gap: 2rem;
}
.navigation__link {
padding: 0;
border: none;
}
}
</style>
响应式产品网格
<!-- snippets/product-grid.liquid -->
<div class="product-grid">
{% for product in collection.products %}
<div class="product-card">
<a href="{{ product.url }}" class="product-link">
<div class="product-image">
{% if product.featured_image %}
<img src="{{ product.featured_image | img_url: '400x400' }}"
alt="{{ product.title }}"
loading="lazy">
{% endif %}
</div>
<div class="product-info">
<h3 class="product-title">{{ product.title }}</h3>
<p class="product-price">{{ product.price | money }}</p>
</div>
</a>
</div>
{% endfor %}
</div>
<style>
.product-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(2, 1fr);
}
.product-card {
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
transition: transform 0.3s;
}
.product-card:hover {
transform: translateY(-2px);
}
.product-image img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
.product-info {
padding: 1rem;
}
.product-title {
font-size: 0.9rem;
margin: 0 0 0.5rem;
}
@media (min-width: 768px) {
.product-grid {
grid-template-columns: repeat(3, 1fr);
}
.product-title {
font-size: 1rem;
}
}
@media (min-width: 1024px) {
.product-grid {
grid-template-columns: repeat(4, 1fr);
}
}
</style>
图片响应式处理
智能图片加载
<!-- snippets/responsive-image.liquid -->
{% comment %}
参数:
- image: 图片对象
- alt: 替代文本
- sizes: 响应式尺寸
{% endcomment %}
<picture class="responsive-image">
<!-- WebP 格式 -->
<source
srcset="{{ image | img_url: '400x400', format: 'webp' }} 400w,
{{ image | img_url: '600x600', format: 'webp' }} 600w,
{{ image | img_url: '800x800', format: 'webp' }} 800w,
{{ image | img_url: '1200x1200', format: 'webp' }} 1200w"
sizes="{{ sizes | default: '(max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw' }}"
type="image/webp">
<!-- 后备格式 -->
<img
src="{{ image | img_url: '400x400' }}"
srcset="{{ image | img_url: '400x400' }} 400w,
{{ image | img_url: '600x600' }} 600w,
{{ image | img_url: '800x800' }} 800w,
{{ image | img_url: '1200x1200' }} 1200w"
sizes="{{ sizes | default: '(max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw' }}"
alt="{{ alt | escape }}"
loading="lazy"
class="responsive-image__img">
</picture>
懒加载脚本
// assets/lazy-loading.js
class LazyImageLoader {
constructor() {
this.images = document.querySelectorAll('img[loading="lazy"]')
this.imageObserver = null
this.init()
}
init() {
if (!('IntersectionObserver' in window)) {
this.loadAllImages()
return
}
this.imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target)
this.imageObserver.unobserve(entry.target)
}
})
}, {
rootMargin: '50px'
})
this.images.forEach(img => {
this.imageObserver.observe(img)
})
}
loadImage(img) {
if (img.dataset.src) {
img.src = img.dataset.src
img.removeAttribute('data-src')
}
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset
img.removeAttribute('data-srcset')
}
img.classList.add('loaded')
}
loadAllImages() {
this.images.forEach(img => this.loadImage(img))
}
}
// 初始化懒加载
document.addEventListener('DOMContentLoaded', () => {
new LazyImageLoader()
})
性能优化
CSS 关键路径优化
<!-- layout/theme.liquid -->
<style>
/* 关键CSS内联 - 首屏内容 */
.header { background: white; padding: 1rem 0; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
.grid { display: grid; gap: 1rem; }
@media (min-width: 768px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
.grid { grid-template-columns: repeat(3, 1fr); }
}
</style>
<!-- 非关键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>
JavaScript性能优化
// assets/performance.js
class PerformanceOptimizer {
constructor() {
this.init()
}
init() {
this.deferNonCriticalJS()
this.optimizeScroll()
this.preloadCriticalResources()
}
deferNonCriticalJS() {
const scripts = document.querySelectorAll('script[data-defer]')
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
scripts.forEach(script => this.loadScript(script))
})
} else {
setTimeout(() => {
scripts.forEach(script => this.loadScript(script))
}, 2000)
}
}
loadScript(scriptElement) {
const script = document.createElement('script')
script.src = scriptElement.dataset.src
script.async = true
document.head.appendChild(script)
}
optimizeScroll() {
let ticking = false
const handleScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
this.updateScrollElements()
ticking = false
})
ticking = true
}
}
window.addEventListener('scroll', handleScroll, { passive: true })
}
updateScrollElements() {
const scrollTop = window.pageYOffset
// 更新需要scroll位置的元素
document.querySelectorAll('[data-scroll-effect]').forEach(el => {
const effect = el.dataset.scrollEffect
const offset = parseInt(el.dataset.scrollOffset || 0)
if (effect === 'parallax') {
el.style.transform = `translateY(${scrollTop * 0.5 + offset}px)`
}
})
}
preloadCriticalResources() {
// 预加载重要资源
const preloadLinks = [
{ href: '/collections/featured-products.json', as: 'fetch' },
{ href: '/cart.js', as: 'fetch' }
]
preloadLinks.forEach(link => {
const linkEl = document.createElement('link')
linkEl.rel = 'preload'
linkEl.href = link.href
linkEl.as = link.as
linkEl.crossOrigin = 'anonymous'
document.head.appendChild(linkEl)
})
}
}
// 初始化性能优化
new PerformanceOptimizer()
测试和调试
响应式测试工具
// assets/responsive-test.js
class ResponsiveTestTool {
constructor() {
this.breakpoints = {
mobile: 320,
tablet: 768,
desktop: 1024,
wide: 1440
}
this.init()
}
init() {
if (window.location.search.includes('debug=responsive')) {
this.createTestPanel()
}
}
createTestPanel() {
const panel = document.createElement('div')
panel.innerHTML = `
<div style="position: fixed; top: 10px; right: 10px;
background: black; color: white; padding: 10px;
z-index: 10000; border-radius: 5px;">
<div>当前宽度: <span id="current-width">${window.innerWidth}px</span></div>
<div>断点: <span id="current-breakpoint">${this.getCurrentBreakpoint()}</span></div>
<div style="margin-top: 10px;">
${Object.entries(this.breakpoints).map(([name, width]) =>
`<button onclick="this.resizeWindow(${width})"
style="margin: 2px; padding: 5px; font-size: 12px;">
${name} (${width}px)
</button>`
).join('')}
</div>
</div>
`
document.body.appendChild(panel)
window.addEventListener('resize', () => {
document.getElementById('current-width').textContent = window.innerWidth + 'px'
document.getElementById('current-breakpoint').textContent = this.getCurrentBreakpoint()
})
}
getCurrentBreakpoint() {
const width = window.innerWidth
if (width < this.breakpoints.tablet) return 'mobile'
if (width < this.breakpoints.desktop) return 'tablet'
if (width < this.breakpoints.wide) return 'desktop'
return 'wide'
}
resizeWindow(width) {
// 仅在开发环境中有效
if (window.location.hostname === 'localhost') {
window.resizeTo(width, 800)
}
}
}
// 初始化测试工具
new ResponsiveTestTool()
通过这些响应式设计技术,您可以创建在所有设备上都表现出色的 Shopify 主题!
最后更新时间: