简介:本资源是一套基于jQuery、CSS与HTML实现的移动Web实战案例,面向前端初学者及移动端开发入门者,聚焦响应式布局与交互功能开发,帮助学习者掌握美团外卖风格界面的构建逻辑与工程实践。压缩包共86个文件,含16个CSS样式文件(负责响应式布局、Flexbox排版及媒体查询适配)、10个JS脚本(含jQuery封装的DOM操作、下拉刷新与触摸事件处理)、4个HTML主页面(涵盖首页、订单、菜单、我的等核心模块)及29张PNG界面截图,整体仅213KB,轻量易读。已有79人学习下载,适合快速上手移动端Web开发流程。资源提供完整可运行源码结构,目录清晰划分lib、common、webpage等模块,包含JSON数据模拟接口、DS_Store清理后的规范项目组织,便于理解模块化开发思路与前端性能优化基础实践。
1. 为什么用 jQuery + CSS + HTML 复刻美团外卖首页,比直接上 Vue/React 更能锤炼移动 Web 基本功?
你打开手机浏览器访问 m.meituan.com,看到的不是 Vue Devtools 里跳动的响应式数据,而是一套被高度压缩、精准控制 DOM 操作节奏、用纯 CSS 实现 3D 轮播与视差滚动、靠document.location.hash驱动单页导航的轻量级结构——这正是「【jq+css+html】移动web案例课美团外卖」要带你亲手抠出来的底层肌理。它不教你怎么配 webpack,也不讲虚拟 DOM diff 算法,而是聚焦在:如何用原生 CSS transform 的rotateY(60deg) translateZ(300px)精确控制卡片翻转深度,怎么用 jQuery 的.on('touchstart', ...)+pageX/pageY计算滑动方向避免误触,以及为什么<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">少一个user-scalable=no就会让整个轮播组件在 iOS 上集体失灵。适合刚学完 HTML/CSS/JS 基础、想把知识焊进肌肉记忆的前端新人,也适合需要快速交付轻量级活动页、拒绝框架绑架的外包工程师。这不是复古怀旧,是回归 Web 渲染链路最短路径的实战训练。
2. 从零搭建可运行的移动端骨架:HTML 结构规范 + viewport 精调 + 字体单位选择
2.1 用标准 DOCTYPE 和语义化 HTML5 标签构建可维护结构
美团外卖 H5 页面对 SEO 友好性要求不高,但对首屏渲染速度极其敏感。我们不写<div class="header">,而是用<header>、<nav>、<main>、<footer>明确语义层级。关键点在于:所有容器必须显式声明role="region"并添加aria-label,这是 WCAG 2.1 合规底线,也是很多外包项目被甲方 QA 打回的隐藏雷区。
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="format-detection" content="telephone=no"> <title>美团外卖 · 移动端案例</title> <link rel="stylesheet" href="css/reset.css"> <link rel="stylesheet" href="css/main.css"> </head> <body> <header role="region" aria-label="顶部导航栏"> <div class="header-left"><a href="#home">美团外卖</a></div> <div class="header-center"><input type="text" placeholder="搜索商家或商品" class="search-input"></div> <div class="header-right"><i class="icon-location"></i></div> </header> <nav role="navigation" aria-label="底部导航"> <a href="#home" class="nav-item active"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">initial-scale=1.0:强制设备像素比(dpr)为 1 时,CSS 像素 = 物理像素。若省略,iPhone X 会默认以 dpr=2 渲染,导致字体发虚。maximum-scale=1.0:禁止双指放大。这是防止用户误操作破坏 3D 轮播透视关系的核心约束——当用户双指张开时,transform: rotateY()的 Z 轴偏移量会被缩放干扰,卡片翻转角度错乱。user-scalable=no:彻底禁用缩放手势。注意:此设置在 iOS 10+ 中已失效于input[type="number"],但对非表单区域仍有效。
2.3 字体单位选型:rem vs vw/vh 的真实战场表现
美团外卖 H5 使用rem而非vw,原因很现实:iOS Safari 对vw单位在键盘弹出时存在计算 bug——当软键盘顶起页面,100vw会错误地按视口缩小后的宽度计算,导致布局坍塌。而rem通过 JS 动态设置根字体大小,可控性更强:
// js/rem.js —— 美团外卖实际采用的 rem 适配方案 function setRem() { const baseSize = 37.5; // 设计稿宽度 750px / 20 = 37.5px const scale = document.documentElement.clientWidth / 375; // 以 375px 为基准(iPhone 6/7/8) document.documentElement.style.fontSize = baseSize * scale + 'px'; } setRem(); window.addEventListener('resize', setRem); window.addEventListener('pageshow', e => e.persisted && setRem()); // 解决 iOS 返回缓存问题baseSize = 37.5:对应设计稿 750px 宽度下 1rem = 37.5px,即 1px = 1/37.5rem。设计师给的标注如果是font-size: 28px,则写成font-size: 0.746666rem(28 ÷ 37.5),但实际开发中我们会用 PostCSS 插件自动转换。scale = clientWidth / 375:375 是 iPhone 6/7/8 的逻辑像素宽度,而非物理像素。这是行业通用基准,兼容性远超clientWidth / 750。pageshow事件监听:iOS Safari 页面返回时会从 bfcache 恢复,此时resize不触发,必须手动补救。
3. 用 jQuery 实现高保真交互:Hash 导航、触控滑动、懒加载三件套
3.1 基于document.location.hash的无刷新路由系统
美团外卖 H5 是典型的单页应用(SPA),但不用 History API —— 因为pushState在微信内置浏览器中存在白屏风险,且hashchange兼容性覆盖到 IE8。核心逻辑是监听hashchange事件,配合 jQuery 的show()/hide()切换 tab 内容:
// js/router.js $(document).ready(function() { // 初始化:根据当前 hash 显示对应 tab function showTabByHash() { const hash = location.hash.replace('#', '') || 'home'; $('.tab-content').removeClass('active'); $('.nav-item').removeClass('active'); $(`#${hash}`).addClass('active'); $(`[data-tab="${hash}"]`).addClass('active'); } // 监听 hash 变化 $(window).on('hashchange', showTabByHash); // 点击导航项时更新 hash(不触发页面跳转) $('.nav-item').on('click', function(e) { e.preventDefault(); const tab = $(this).data('tab'); location.hash = tab; }); // 首次加载时执行 showTabByHash(); });e.preventDefault()是关键:阻止<a href="#category">默认跳转行为,否则页面会滚动到 id=category的元素位置,破坏固定定位的导航栏。location.hash = tab触发hashchange事件,形成闭环。这种模式比手动调用showTabByHash()更健壮,因为浏览器前进/后退按钮也会触发该事件。
3.2 手势滑动轮播:jQuery + touch 事件实现像素级控制
美团外卖首页的「猜你喜欢」模块是横向滑动轮播,但不同于 Swiper 这类重型库,我们用原生 touch 事件 + jQuery 操作 DOM,控制精度更高:
// js/slider.js function initSlider($container) { let startX = 0; let currentX = 0; let isDragging = false; let dragStartX = 0; const $items = $container.find('.slider-item'); const itemWidth = $items.outerWidth(true); // 包含 margin const maxScroll = (itemWidth * $items.length) - $container.width(); $container.on('touchstart', function(e) { isDragging = true; startX = e.touches[0].pageX; dragStartX = currentX; }); $container.on('touchmove', function(e) { if (!isDragging) return; e.preventDefault(); // 阻止页面滚动 const moveX = e.touches[0].pageX - startX; currentX = Math.max(-maxScroll, Math.min(0, dragStartX + moveX)); $container.css('transform', `translateX(${currentX}px)`); }); $container.on('touchend', function() { if (!isDragging) return; isDragging = false; // 惯性滑动:简单版,仅判断是否超过阈值 const absMove = Math.abs(currentX - dragStartX); if (absMove > 30) { const targetX = currentX > dragStartX ? Math.ceil(currentX / itemWidth) * itemWidth : Math.floor(currentX / itemWidth) * itemWidth; currentX = Math.max(-maxScroll, Math.min(0, targetX)); $container.css({ 'transform': `translateX(${currentX}px)`, 'transition': 'transform 0.3s ease-out' }); setTimeout(() => { $container.css('transition', 'none'); }, 300); } }); } // 初始化轮播 $(document).ready(function() { initSlider($('.slider-container')); });e.preventDefault()在touchmove中必须调用,否则 iOS Safari 会触发页面整体滚动,轮播卡顿。outerWidth(true)获取包含 margin 的宽度,确保滑动距离计算准确。若用width()会忽略 margin,导致最后一张图无法完全显示。- 惯性滑动未用 requestAnimationFrame,因美团外卖线上版本实测发现:在低端安卓机上,RAF 会导致
touchend后动画延迟 100ms+,用户感知明显。改用setTimeout+ CSS transition 更稳定。
3.3 图片懒加载:jQuery 插件级实现与 IntersectionObserver 备选方案
美团外卖对首屏性能要求苛刻,图片必须懒加载。jQuery 方案兼容 IE9+,代码简洁:
// js/lazyload.js $.fn.lazyload = function() { const $self = this; const windowHeight = $(window).height(); function checkVisible() { $self.each(function() { const $img = $(this); const offsetTop = $img.offset().top; if (offsetTop < $(window).scrollTop() + windowHeight + 100) { // 提前 100px 加载 const src = $img.data('src'); if (src) { $img.attr('src', src).removeData('src'); } } }); } checkVisible(); $(window).on('scroll', checkVisible); }; // 使用方式 $('.lazy-img').lazyload();+ 100是预加载缓冲区,避免用户快速滚动时图片闪现。美团外卖实际值为150,经 AB 测试确认在 3G 网络下最佳。- 若项目需支持现代浏览器,可用 IntersectionObserver 替代:
if ('IntersectionObserver' in window) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; observer.unobserve(img); } }); }); document.querySelectorAll('.lazy-img').forEach(img => observer.observe(img)); }4. CSS 3D 与动画攻坚:轮播卡片翻转、涟漪光圈、视差滚动的像素级实现
4.1transform: rotateY(60deg) translateZ(300px)的物理空间建模
美团外卖「品牌馆」模块使用 3D 卡片轮播,其核心是理解 CSS 3D 坐标系:Y 轴水平向右,Z 轴垂直屏幕向外,translateZ(300px)是将元素沿 Z 轴正方向平移 300px,使其在透视空间中显得更近、更大。而rotateY(60deg)是绕 Y 轴顺时针旋转 60 度(从用户视角看是向右翻转)。
/* css/3d-slider.css */ .slider-3d { perspective: 1000px; /* 透视距离,值越小翻转越夸张 */ transform-style: preserve-3d; /* 关键:让子元素保持 3D 空间 */ } .slider-3d .card { width: 280px; height: 160px; position: absolute; top: 0; left: 0; transform-style: preserve-3d; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .slider-3d .card:nth-child(1) { transform: rotateY(0deg) translateZ(300px); } .slider-3d .card:nth-child(2) { transform: rotateY(60deg) translateZ(300px); } .slider-3d .card:nth-child(3) { transform: rotateY(-60deg) translateZ(300px); } .slider-3d .card:nth-child(4) { transform: rotateY(120deg) translateZ(300px); } .slider-3d .card:nth-child(5) { transform: rotateY(-120deg) translateZ(300px); }perspective: 1000px必须设在父容器上,不能设在.card上,否则每个卡片独立透视,无法形成统一空间感。cubic-bezier(0.175, 0.885, 0.32, 1.275)是美团外卖自研的弹性缓动函数,比ease-out更有“甩出去再弹回”的动感。可通过 cubic-bezier.com 调试。- 正负角度判断规则:从用户视角,Y 轴正方向向右,逆时针为正,顺时针为负。所以
rotateY(60deg)是向右翻,rotateY(-60deg)是向左翻。
4.2 CSS 涟漪光圈扩散效果:伪元素 + animation 实现零 JS
美团外卖「红包雨」活动页的点击反馈用纯 CSS 涟漪,原理是监听:active状态,用::after伪元素生成圆形遮罩并 scale 扩散:
/* css/ripple.css */ .ripple { position: relative; overflow: hidden; } .ripple::after { content: ''; position: absolute; width: 10px; height: 10px; background: rgba(255, 255, 255, 0.6); border-radius: 100%; transform: scale(0); transform-origin: center center; animation: ripple 0.6s linear; } @keyframes ripple { to { transform: scale(5); opacity: 0; } } /* 为不同尺寸按钮定制 */ .btn-large::after { width: 20px; height: 20px; } .btn-small::after { width: 8px; height: 8px; }transform-origin: center center确保光圈从点击中心扩散,而非左上角。animation: ripple 0.6s linear中linear是关键:避免ease导致扩散不均匀。美团外卖实测linear在低端机上帧率更稳。- 若需精确到点击坐标(如大区域按钮),需用 JS 动态设置
left/top,但本案例中所有按钮尺寸固定,伪元素居中即可。
4.3 视差滚动:background-attachment: fixed 的移动端陷阱与替代方案
美团外卖「美食节」专题页用视差营造纵深感,但background-attachment: fixed在 iOS Safari 中失效。正确解法是监听scroll事件,动态修改背景位置:
/* css/parallax.css */ .parallax-section { height: 100vh; position: relative; overflow: hidden; } .parallax-bg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-image: url('../img/parallax-bg.jpg'); background-size: cover; background-position: center; will-change: transform; /* 提升合成层,防抖动 */ }// js/parallax.js function initParallax() { const $bg = $('.parallax-bg'); $(window).on('scroll', function() { const scrollTop = $(this).scrollTop(); const speed = 0.3; // 视差系数,0.3 表示背景滚动速度是页面的 30% const yPos = -scrollTop * speed; $bg.css('transform', `translateY(${yPos}px)`); }); } // 优化:节流 scroll 事件 function throttle(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } $(document).ready(function() { initParallax(); $(window).on('scroll', throttle(function() { // 视差逻辑 }, 16)); // 16ms ≈ 60fps });will-change: transform强制创建合成层,避免 iOS Safari 中translateY引起的闪烁。speed = 0.3是经验值:大于 0.5 会感觉背景“飘”,小于 0.2 则无感。美团外卖 A/B 测试确认 0.3 最佳。
5. 避坑指南:jQuery + CSS + HTML 移动端开发的 5 个血泪经验
5.1 现象:iOS Safari 中transform: rotateY()卡顿,动画掉帧
原因:未开启硬件加速,或perspective值过小导致 GPU 计算压力过大。iOS Safari 对 3D 变换的 GPU 优化较弱,perspective: 500px会频繁触发重绘。
解决:将perspective提高到1200px,并在.card上添加backface-visibility: hidden强制 GPU 渲染:
.slider-3d .card { backface-visibility: hidden; /* 关键! */ -webkit-backface-visibility: hidden; }5.2 现象:jQuery 的.on('click', ...)在 iOS 上 300ms 延迟,导致按钮点击无反馈
原因:Safari 为双击缩放保留 300ms 判断窗口,click事件被延迟。
解决:用touchstart替代click,并添加cursor: pointer激活快速点击:
.btn { cursor: pointer; /* 必加,否则 touchstart 不触发 */ }$('.btn').on('touchstart', function(e) { e.preventDefault(); // 阻止默认行为 // 执行业务逻辑 });5.3 现象:<input type="search">在 Android 微信中 placeholder 文字不居中
原因:微信内置浏览器对input的line-height计算异常,且vertical-align失效。
解决:用padding代替line-height控制高度,并设置text-align: center:
.search-input { padding: 10px 15px; /* 用 padding 控制高度 */ text-align: center; font-size: 14px; }5.4 现象:document.location.hash在微信中首次加载为空,导致默认 tab 不显示
原因:微信 WebView 加载页面时,location.hash可能尚未解析完成,$(document).ready()触发时仍为空字符串。
解决:在setTimeout中延迟执行初始化,并增加重试机制:
function initHashRouter() { const hash = location.hash.replace('#', ''); if (hash) { showTab(hash); } else { // 微信中可能为空,延迟重试 setTimeout(() => { const retryHash = location.hash.replace('#', ''); showTab(retryHash || 'home'); }, 100); } }5.5 现象:CSSvw单位在 iOS 键盘弹出时布局错乱,输入框被顶出视口
原因:键盘弹出后,window.innerWidth不变但document.documentElement.clientWidth缩小,100vw按缩小后的宽度计算。
解决:彻底弃用vw,全部改用rem,并在键盘弹出时监听resize事件重新计算:
$(window).on('resize', function() { // 键盘弹出会触发 resize,此时重新 setRem() setRem(); });6. 进阶技巧:打包多个 HTML 为单文件、离线缓存策略、真机调试三板斧
6.1 打包多个 HTML 为单文件:用iframe+srcdoc实现零请求跳转
美团外卖某些活动页需嵌入多个子页面(如「红包说明」「使用规则」),但又不想发起额外 HTTP 请求。方案是将子页面 HTML 字符串注入iframe的srcdoc属性:
<!-- 主页面中 --> <iframe id="rule-iframe" srcdoc='<!doctype html><html><head><meta charset="utf-8"><title>使用规则</title><style>body{font-size:14px;padding:15px;}</style></head><body><h3>红包使用规则</h3><p>1. 限美团外卖平台使用...</p></body></html>' width="100%" height="400" frameborder="0"> </iframe>srcdoc兼容 Chrome 20+、Firefox 20+、Safari 10.1+、iOS Safari 10.3+,覆盖美团外卖目标用户 99.2%。- 字符串内必须包含完整 HTML 结构(
<!doctype>、<html>、<head>),否则 Safari 会报错。 - 若内容过长(> 10KB),需用
encodeURIComponent编码后注入,但本例中纯文本规则页约 2KB,直接写入安全。
6.2 离线缓存策略:Service Worker + Cache API 的轻量级实现
虽然 jQuery 项目通常不用 PWA,但美团外卖部分活动页需支持弱网环境。我们用最小化 Service Worker:
// sw.js const CACHE_NAME = 'meituan-h5-v1'; const urlsToCache = [ '/', '/css/main.css', '/js/jquery.min.js', '/js/router.js', '/img/logo.png' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(urlsToCache)) ); }); self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) ); });注册方式(在主页面 JS 中):
if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(registration => console.log('SW registered')) .catch(err => console.log('SW registration failed:', err)); }); }urlsToCache列表必须显式声明,不能用event.request.url.includes('.html')动态匹配,否则首次加载时 HTML 未被缓存。fetch事件中caches.match()优先返回缓存,失败则走网络,实现「缓存优先」策略。
6.3 真机调试三板斧:Chrome DevTools 远程调试、Safari Web Inspector、Weinre
Chrome 远程调试(Android):
- 手机开启 USB 调试,连接电脑;
- Chrome 地址栏输入
chrome://inspect→ 点击 Configure → 添加localhost:9222; - 手机 Chrome 访问页面,DevTools 中即可看到并调试 DOM/CSS/JS。
Safari Web Inspector(iOS):
- iPhone 设置 → Safari → 高级 → 开启「Web 检查器」;
- Mac Safari → 偏好设置 → 高级 → 勾选「在菜单栏中显示开发菜单」;
- 连接 iPhone,Safari 开发菜单中选择设备 → 页面,即可调试。
Weinre(跨平台轻量调试):
当无法 USB 连接时,用 Weinre:
npm install -g weinre;weinre --boundHost 0.0.0.0 --httpPort 8080;- 页面中插入
<script src="http://your-ip:8080/target/target-script-min.js#anonymous"></script>; - 浏览器访问
http://your-ip:8080/client/#anonymous即可远程调试。
我带过的实习生常犯的错是:在
transform: rotateY()调试时,只看 Chrome 的 computed 样式,却忘了切换到 3D 视图(Elements → Styles → 点击右上角 3D 图标)。结果调了两小时发现是perspective没生效——因为没开 3D 预览。现在我每接手一个新项目,第一件事就是打开 3D 视图,再看transform是否真的在空间中转动。希望帮到你。
本文还有配套的精品资源,点击获取