主题
Less 与响应式(二)
本页对应 Less 与响应式,共 6 段视频,约 1 小时 13 分。
学习目标
- 能够独立完成酷我音乐页面中搜索框、banner、标题、排行榜、推荐歌单和底部下载定位六个模块的结构搭建与样式实现。
- 能够使用内边距、外边距、宽高和定位解决元素被遮挡、内容贴边及模块间距问题,并说明
top、padding、margin在页面布局中的作用。 - 能够使用 Flex 布局实现标题左右分布、排行榜内容左右排列、推荐歌单横向排列与自动换行,并完成图标与文字的对齐。
- 能够使用
object-fit: cover等图片适配方式解决图片宽高同时设置导致的变形问题,并为图片和卡片设置正确的圆角。 - 能够抽取标题等公共样式进行复用,避免重复编写相同模块样式。
- 能够使用固定定位完成底部下载模块布局,并排查移动端页面因元素超出视口导致横向滚动的问题。
知识点
项目准备:先搭好结构和变量

做综合案例时,先把页面拆成“头部、搜索、banner、排行榜、推荐歌单、下载条”几个模块。为什么需要:模块化能让样式和结构一一对应,后面调试时容易定位。怎么想:先引入图标字体和公共样式,再用 Less 的 @vw 变量统一做移动端适配。怎么做:HTML 里先引资源,Less 里先导入基础样式并输出到 ../css/。

html
<link rel="stylesheet" href="./iconfont/iconfont.css">
<link rel="stylesheet" href="./css/index.css">less
// out: ../css/
@import "./base";
body {
background-color: #f9fafb;
}
@vw: 3.75vw;易错点:@vw 要写在样式前面;图片路径、字体路径要按目录层级写对。
头部布局与头部内容:左右两端对齐
为什么需要:头部左边是 logo,右边是下载按钮,必须一左一右。怎么想:用 flex 的 justify-content: space-between,垂直方向 align-items: center。怎么做:左边用背景图做 logo,右边 a 做胶囊按钮。易错点:固定定位后宽度不会自动占满,要补 width: 100%。
html
<header>
<div class="left">left</div>
<a href="#">下载APP</a>
</header>less
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 (15 / @vw);
height: (50 / @vw);
background-color: #fff;
.left {
width: (235 / @vw);
height: (50 / @vw);
background-image: url(../assets/head.png);
background-size: contain;
background-repeat: no-repeat;
}
a {
width: (80 / @vw);
height: (30 / @vw);
background-color: #ffe31b;
border-radius: (15 / @vw);
text-align: center;
line-height: (30 / @vw);
font-size: (14 / @vw);
}
}搜索区域:胶囊用内层盒子做
为什么需要:搜索区域是灰色胶囊,里面包含图标和提示文字。怎么想:外层 .search 负责上下左右留白,内层 .txt 负责灰色背景、圆角和文字居中。怎么做:.search 加 padding 和高度,.txt 设 height、line-height 相同实现垂直居中。转写稿里调试过:头部固定后搜索被盖住,所以给 .search 加 margin-top: 50/@vw。易错点:只给外层高度不加内边距,胶囊会贴边。
html
<div class="search">
<div class="txt">
<span class="iconfont icon-sousuo"></span>
<span>搜索你想听的歌曲</span>
</div>
</div>less
.search {
margin-top: (50 / @vw);
padding: (10 / @vw) (15 / @vw);
height: (52 / @vw);
.txt {
height: (32 / @vw);
background-color: #f2f4f5;
border-radius: (16 / @vw);
text-align: center;
line-height: (32 / @vw);
color: #a1a4b3;
font-size: (14 / @vw);
.iconfont {
font-size: (16 / @vw);
}
}
}banner 区域:图片不变形用 object-fit
为什么需要:banner 图片比例和盒子比例不一定相同,直接设宽高会挤压变形。怎么想:让图片铺满盒子,同时保持比例并裁掉多余部分。怎么做:img 宽高 100%,加 object-fit: cover,再给圆角。易错点:cover 是覆盖裁剪,contain 是完整显示但可能留白。
html
<div class="banner">
<ul>
<li><a href="#"><img src="./assets/banner01.jpeg" alt="banner图片"></a></li>
</ul>
</div>less
.banner {
padding: 0 (15 / @vw);
height: (108 / @vw);
ul li {
width: (345 / @vw);
height: (108 / @vw);
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: (5 / @vw);
}
}
}标题公共样式:排行榜和推荐歌单复用
为什么需要:两个模块标题结构一样,重复写会增加维护成本。怎么想:把 .title 抽成公共样式。怎么做:flex 两端对齐,h4 和 a 分别设字号颜色。易错点:标题底部要有间距,否则内容贴太近。
html
<div class="title">
<h4>酷我排行榜</h4>
<a href="#">更多<span class="iconfont icon-right"></span></a>
</div>less
.title {
display: flex;
justify-content: space-between;
margin-bottom: (16 / @vw);
line-height: (25 / @vw);
h4 { font-size: (20 / @vw); }
a { font-size: (12 / @vw); color: #a1a4b3; }
}排行榜内容:每个 li 左右两块
为什么需要:每条榜单左边图片、右边歌名列表。怎么想:li 用 flex 横排,左 .pic 定宽,右 .txt 放多个 a。怎么做:a 设 display: block 让每个链接独占一行。转写稿里调试过:右侧多个 a 会把左侧图片挤窄,可给 .pic 加 flex-shrink: 0 防止被压缩。易错点:.more 和普通 a 样式要区分。
less
.list {
margin-top: (20 / @vw);
padding: 0 (15 / @vw);
li {
display: flex;
margin-bottom: (16 / @vw);
height: (105 / @vw);
background-color: #fff;
border-radius: (10 / @vw);
.pic {
margin-right: (20 / @vw);
img {
width: (105 / @vw);
height: (105 / @vw);
border-radius: (10 / @vw);
}
}
.txt a {
display: block;
font-size: (12 / @vw);
color: #a1a4b3;
line-height: 1.8;
}
.txt .more {
font-size: (14 / @vw);
color: #333;
.iconfont { font-size: (16 / @vw); }
}
}
}推荐歌单:flex 换行与封面定位
为什么需要:歌单是两行三列的小卡片。怎么想:ul 用 flex-wrap: wrap 和 justify-content: space-between 自动换行。怎么做:li 定宽高,图片外层 .pic 相对定位,播放量 .cover 绝对定位到左下角。易错点:.cover 只想要左下圆角,可写 border-radius: 0 (10/@vw) 0 (10/@vw)。
less
.recommend {
padding: 0 (15 / @vw);
ul {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
li {
margin-bottom: (16 / @vw);
width: (105 / @vw);
height: (143 / @vw);
}
}
}
.pic {
position: relative;
width: (105 / @vw);
height: (105 / @vw);
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: (10 / @vw);
}
.cover {
position: absolute;
left: 0;
bottom: 0;
width: (70 / @vw);
height: (28 / @vw);
background-color: rgba(0,0,0,0.8);
border-radius: 0 (10 / @vw) 0 (10 / @vw);
text-align: center;
line-height: (28 / @vw);
color: #fff;
font-size: (14 / @vw);
}
}
.txt { font-size: (14 / @vw); }下载区域与头部固定:固定定位要占位处理
为什么需要:下载条要悬浮在底部,头部要固定在顶部。怎么想:下载条 position: fixed 到左下;头部固定后脱离文档流,搜索区要补 margin-top。怎么做:下载条用 flex 排 logo、文字、箭头。易错点:移动端出现左右能滑动,通常是某个元素加 padding/margin 后总宽超过 375,要逐个排查超宽元素。
less
.download {
position: fixed;
left: (15 / @vw);
bottom: (30 / @vw);
display: flex;
align-items: center;
padding: 0 (10 / @vw) 0 (15 / @vw);
width: (345 / @vw);
height: (45 / @vw);
background-color: #fff;
border-radius: (22 / @vw);
img {
margin-right: (10 / @vw);
width: (36 / @vw);
height: (36 / @vw);
}
p { flex: 1; font-size: (14 / @vw); }
span {
width: (32 / @vw);
height: (32 / @vw);
background-color: #f2f3f5;
border-radius: 50%;
text-align: center;
line-height: (32 / @vw);
font-size: (16 / @vw);
}
}
header {
position: fixed;
left: 0;
top: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 (15 / @vw);
width: 100%;
height: (50 / @vw);
background-color: #fff;
}代码示例
代码一:头部 + 搜索区域(固定定位 + @vw 适配)
index.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>酷我音乐</title>
<link rel="stylesheet" href="./iconfont/iconfont.css">
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<!-- 头部 -->
<header>
<div class="left">left</div>
<a href="#">下载APP</a>
</header>
<!-- 搜索 -->
<div class="search">
<div class="txt">
<span class="iconfont icon-sousuo"></span>
<span>搜索你想听的歌曲</span>
</div>
</div>
</body>
</html>less/index.less(用 VSCode 的 Easy LESS 插件保存即自动编译到 css/index.css)
less
// out: ../css/
@import "./base";
@vw: 3.75vw;
body {
background-color: #f9fafb;
}
// 头部
header {
position: fixed;
left: 0;
top: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 (15 / @vw);
// 固定定位的盒子,宽度靠内容撑开,希望宽度100%
width: 100%;
height: (50 / @vw);
background-color: #fff;
// 左边 logo
.left {
width: (235 / @vw);
height: (50 / @vw);
background-image: url(../assets/head.png);
background-size: contain;
background-repeat: no-repeat;
}
a {
width: (80 / @vw);
height: (30 / @vw);
background-color: #ffe31b;
border-radius: (15 / @vw);
text-align: center;
line-height: (30 / @vw);
font-size: (14 / @vw);
}
}
// 搜索
.search {
// 头部固定定位,脱标不占位,搜索去最顶了,加上外边距挤下来即可
margin-top: (50 / @vw);
padding: (10 / @vw) (15 / @vw);
height: (52 / @vw);
.txt {
height: (32 / @vw);
background-color: #f2f4f5;
border-radius: (16 / @vw);
text-align: center;
line-height: (32 / @vw);
color: #a1a4b3;
font-size: (14 / @vw);
.iconfont {
font-size: (16 / @vw);
}
}
}要点
@vw: 3.75vw;是移动端适配的核心:设计稿按 375px 宽做,写(15 / @vw)就等于15 / 3.75 = 4vw,页面宽度变化时所有尺寸等比缩放。Less 里除法必须加括号,不写括号15 / @vw会被当成三个值输出成15 / 3.75vw,样式直接失效。header一旦position: fixed就脱标不占位,后面的.search会跑到最顶上被盖住。转写稿里讲得很清楚:给.search加margin-top: (50 / @vw)(至少等于头部高度)把它“挤”下来,而不是加top。- 固定定位盒子默认宽度由内容撑开,必须显式
width: 100%才能撑满屏幕。 .left用背景图 +background-size: contain+background-repeat: no-repeat,比直接塞<img>更好控制 logo 不变形。a默认是行内元素、设宽高无效;这里因为它是header这个 flex 容器的子项(flex item),宽高才生效——换个非 flex 的父级就要补display: block。// out: ../css/是 Easy LESS 的输出路径注释,没有它编译出来的 css 会跟 less 挤在同一目录。
代码二:banner 区域(object-fit 解决图片变形)
html
<!-- banner -->
<div class="banner">
<ul>
<li><a href="#"><img src="./assets/banner01.jpeg" alt=""></a></li>
</ul>
</div>less
// banner
.banner {
// 有内边距说明是 padding,不是 margin
padding: 0 (15 / @vw);
height: (108 / @vw);
ul {
li {
width: (345 / @vw);
height: (108 / @vw);
img {
width: 100%;
height: 100%;
// cover完全覆盖
// 缩放img,图片比例跟父级盒子比例不同,避免图片挤压变形
object-fit: cover;
border-radius: (5 / @vw);
}
}
}
}要点
.banner左右有 15 的留白,这是padding(内边距,把内容往里挤),不是margin(外边距,把自己往里推)。判断依据:图片贴着父级内边缘,说明留白在父级内部。- 坑在这里:
img同时设置width: 100%和height: 100%,只要图片原始比例和盒子比例不一致,图片必然被拉伸变形。转写稿反复强调这一点。 object-fit: cover;让图片保持原比例缩放、完全覆盖盒子,超出的部分裁掉——不变形。对比contain是完整显示但会留白,fill是默认值(就是拉伸变形)。li宽度写死 345,是为了后面做轮播(横向排列、一次显示一张);高度 108 和父级一致,防止图片把盒子撑高。- 圆角要给
img加,加在li上会被图片盖住看不出来。
代码三:标题公共样式 + 排行榜 + 底部下载(固定定位)
html
<!-- 排行榜 -->
<div class="list">
<!-- 标题 -->
<div class="title">
<h4>酷我排行榜</h4>
<a href="#">更多<span class="iconfont icon-right"></span></a>
</div>
<!-- 内容 -->
<div class="content">
<ul>
<li>
<div class="pic"><img src="./assets/icon_rank_hot.png" alt=""></div>
<div class="txt">
<a href="#" class="more">酷我热歌榜<span class="iconfont icon-right"></span></a>
<a href="#">1.樱花树下的约定(完整版) - 旺仔小乔</a>
<a href="#">2.就让这大雨全都落下 - 容祖儿</a>
<a href="#">3.缺氧 - 轩姨(相信光)</a>
</div>
</li>
<li>
<div class="pic"><img src="./assets/icon_rank_new.png" alt=""></div>
<div class="txt">
<a href="#" class="more">酷我新歌榜<span class="iconfont icon-right"></span></a>
<a href="#">1.樱花树下的约定(完整版) - 旺仔小乔</a>
<a href="#">2.就让这大雨全都落下 - 容祖儿</a>
<a href="#">3.缺氧 - 轩姨(相信光)</a>
</div>
</li>
</ul>
</div>
</div>
<!-- 安装,下载 -->
<div class="download">
<img src="./assets/logo.png" alt="">
<p>安装酷我音乐 发现更多好音乐</p>
<span class="iconfont icon-right"></span>
</div>less
// 标题 → 公共样式(.list 和 .recommend 都用,所以写在最外层)
.title {
display: flex;
justify-content: space-between;
margin-bottom: (16 / @vw);
line-height: (25 / @vw);
h4 {
font-size: (20 / @vw);
}
a {
font-size: (12 / @vw);
color: #a1a4b3;
}
}
// 排行榜
.list {
margin-top: (20 / @vw);
padding: 0 (15 / @vw);
li {
display: flex;
margin-bottom: (16 / @vw);
height: (105 / @vw);
background-color: #fff;
border-radius: (10 / @vw);
.pic {
margin-right: (20 / @vw);
// 防止被右侧文字挤压
flex-shrink: 0;
img {
width: (105 / @vw);
height: (105 / @vw);
border-radius: (10 / @vw);
}
}
.txt {
a {
display: block;
font-size: (12 / @vw);
color: #a1a4b3;
line-height: 1.8;
}
.more {
font-size: (14 / @vw);
color: #333;
.iconfont {
font-size: (16 / @vw);
}
}
}
}
}
// 下载(固定定位在底部)
.download {
position: fixed;
left: (15 / @vw);
bottom: (30 / @vw);
display: flex;
align-items: center;
padding: 0 (10 / @vw) 0 (15 / @vw);
width: (345 / @vw);
height: (45 / @vw);
background-color: #fff;
border-radius: (22 / @vw);
img {
margin-right: (10 / @vw);
width: (36 / @vw);
height: (36 / @vw);
}
p {
flex: 1;
font-size: (14 / @vw);
}
span {
width: (32 / @vw);
height: (32 / @vw);
background-color: #f2f3f5;
border-radius: 50%;
text-align: center;
line-height: (32 / @vw);
font-size: (16 / @vw);
}
}要点
.title写在.list外面,是因为推荐歌单那块也用同一个标题结构——转写稿特意强调“这一块是公共样式,一会儿他就不用再写了”。写在.list里就只能排行榜用。.txt里的a必须display: block才能一行一条竖着排;或者给.txt加display: flex; flex-direction: column,转写稿给了这两种思路。不处理的话 4 个a会挤成一行。- 坑:
li是 flex 容器,.pic虽然写了width: 105,但右侧.txt内容一多就会把图片挤压变形。给.pic补flex-shrink: 0;让它不参与收缩(转写稿里就出现过“图片被挤没了”的现象)。 .download是固定定位:left: 15+bottom: 30定位,宽度必须写死 345,否则固定定位盒子会按内容自动收缩,右边就“塌”了。p { flex: 1; }让文字撑满剩余空间,把右边的箭头span顶到最右侧;align-items: center让三个子元素垂直居中。border-radius: 50%;是正圆胶囊(箭头按钮),和border-radius: (22 / @vw)(半高,也是胶囊)是两种写法,别混用。- 做完全部后如果页面能左右滑动,99% 是某个元素宽度超出 375,检查是不是多加了 padding/margin 把总宽撑爆了——转写稿里专门提醒过这个最常见错误。
常见错误
搜索区域"消失",鼠标点上去落在头部上
- 现象:给
header加position: fixed后,.search不见了,或被头部压住。 - 原因:固定定位脱标不占位,后面的盒子直接顶到页面最上方,被头部盖住。
- 修法:给被顶上去的第一个兄弟盒子补
margin-top: (50 / @vw),把位置"挤"回来。
- 现象:给
banner / 歌单图片被压扁拉长
- 现象:
img宽高都设成100%后,图片比例失真,人脸变胖。 - 原因:父盒子宽高比与图片原始比例不一致,强制撑满必然变形。
- 修法:保留
width/height: 100%,追加object-fit: cover(需要完整显示则用contain)。
- 现象:
排行榜每条左侧图片被挤成一条缝
- 现象:
.pic明明写了105/@vw,实际渲染却很窄,图片跟着变窄。 - 原因:
.txt里多个<a>是行内元素被当成一行排,右侧内容宽度撑爆,弹性盒子里左侧被压缩。 - 修法:二选一——给
.txt加flex: 1(并让a转块级/竖排),或给.pic加flex-shrink: 0。根本上先把右侧a排成竖列。
- 现象:
手机预览出现左右横向滑动条
- 现象:页面能左右拖动,右侧多出一条空白。
- 原因:某个元素总宽度超过 375,最常见是
width: 100%的盒子又加了左右padding(如header)。 - 修法:给该盒子加
box-sizing: border-box,或把width: 100%换成不设宽 +padding让内容自适应;再逐个元素排查padding + width是否超 375。
.cover(播放量角标)跑到页面左下角- 现象:绝对定位的小黑块没有待在图片里。
- 原因:父级
.pic没写position: relative,绝对定位向上找到了视口。 - 修法:给直接父级补
position: relative,并确认父级有明确宽高。
Less 改完刷新没变化
- 现象:样式不生效,
css目录里没有新文件。 - 原因:首行没写输出路径注释,或
@import "./base"路径写错,编译产物落到了 less 目录。 - 修法:文件首行保留
// out: ../css/,检查@import相对路径,重新保存触发编译。
- 现象:样式不生效,
图标显示成方块或空白
- 现象:
icon-sousuo、icon-right渲染不出来。 - 原因:
iconfont.css未引入、引入路径错误,或类名拼错(多写/少写icon-)。 - 修法:确认
<link>在index.css之前且路径正确,对照字体文件里的类名逐个核对。
- 现象:
(15 / @vw)写成15 / @vw或15px- 现象:某个属性不生效,或尺寸明显不对(要么 15px 太小,要么计算被当成字符串)。
- 原因:Less 除法需要括号包裹,且变量本身带单位,写成
px会与vw混用。 - 修法:统一写法
(数值 / @vw),全项目只保留这一种换算形式。
练习
把
header改成position: fixed后,页面出现"搜索框被盖住"和"横向滚动条"两个问题。请分别定位原因并修复,写出改动的那几行代码。给排行榜的
.pic和.txt各写一种方案,保证左侧图片宽度恒为105/@vw不被压缩;两种方案都写完后,说明你更倾向哪一种及理由。把推荐歌单的
.pic单独拎出来做成一个可复用的类(要求:图片等比裁切、圆角 10、左下角半透明角标只有左下与右下两角为圆角)。写出这个类以及它在 HTML 中需要的最小结构。人为制造一个 bug:在
.list li上把height: (105 / @vw)改成height: 105px,在手机模拟器里观察排行榜的显示差异,记录现象并解释为什么。用浏览器 DevTools 的"选中元素"功能,找出你页面中宽度超过 375 的元素(至少两个),列出它们的类名、实际宽度、以及是哪条样式导致超宽。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 项目(搜索框) | 10:59 |
| 2 | 项目(banner区域) | 09:33 |
| 3 | 项目(标题) | 11:20 |
| 4 | 项目(排行榜) | 15:37 |
| 5 | 项目(推荐歌单) | 15:07 |
| 6 | 项目(定位) | 11:10 |
小结
- 移动端整页布局的正确顺序是:先按"大盒子 → 上下分区 → 左右分区"切结构,再写样式;样式永远在结构稳定之后再动。
vw换算必须全项目统一成一种写法,混用px是后期尺寸对不上号的头号来源。- 页面出现横向滚动条,几乎只有一种可能:某个元素总宽度超了设计稿宽度。这应该成为你的第一反射,而不是去改
body { overflow: hidden }。 - 图片变形问题一律先问"父盒子和图片比例是否一致",不一致就用
object-fit,不要靠调宽高凑。 - 定位相关问题先看父级有没有
position: relative,绝对定位的参照物错位是最常见的定位 bug。 - 头部、标题这类会重复出现的模块,第一遍就抽成公共样式写在模块外面,比事后重构省一半时间。
- 弹性盒子中"某个子项被莫名压窄",本质是它被允许收缩,加
flex-shrink: 0或给兄弟项flex: 1即可。 - 遇到没见过的 CSS 属性,去 MDN 查定义和取值,别背速记、别靠猜——这条习惯比记住任何一个属性都值钱。