Skip to content

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 图片比例和盒子比例不一定相同,直接设宽高会挤压变形。怎么想:让图片铺满盒子,同时保持比例并裁掉多余部分。怎么做: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 把总宽撑爆了——转写稿里专门提醒过这个最常见错误。

常见错误 ​

  1. 搜索区域"消失",鼠标点上去落在头部上

    • 现象:给 header 加 position: fixed 后,.search 不见了,或被头部压住。
    • 原因:固定定位脱标不占位,后面的盒子直接顶到页面最上方,被头部盖住。
    • 修法:给被顶上去的第一个兄弟盒子补 margin-top: (50 / @vw),把位置"挤"回来。
  2. banner / 歌单图片被压扁拉长

    • 现象:img 宽高都设成 100% 后,图片比例失真,人脸变胖。
    • 原因:父盒子宽高比与图片原始比例不一致,强制撑满必然变形。
    • 修法:保留 width/height: 100%,追加 object-fit: cover(需要完整显示则用 contain)。
  3. 排行榜每条左侧图片被挤成一条缝

    • 现象:.pic 明明写了 105/@vw,实际渲染却很窄,图片跟着变窄。
    • 原因:.txt 里多个 <a> 是行内元素被当成一行排,右侧内容宽度撑爆,弹性盒子里左侧被压缩。
    • 修法:二选一——给 .txt 加 flex: 1(并让 a 转块级/竖排),或给 .pic 加 flex-shrink: 0。根本上先把右侧 a 排成竖列。
  4. 手机预览出现左右横向滑动条

    • 现象:页面能左右拖动,右侧多出一条空白。
    • 原因:某个元素总宽度超过 375,最常见是 width: 100% 的盒子又加了左右 padding(如 header)。
    • 修法:给该盒子加 box-sizing: border-box,或把 width: 100% 换成不设宽 + padding 让内容自适应;再逐个元素排查 padding + width 是否超 375。
  5. .cover(播放量角标)跑到页面左下角

    • 现象:绝对定位的小黑块没有待在图片里。
    • 原因:父级 .pic 没写 position: relative,绝对定位向上找到了视口。
    • 修法:给直接父级补 position: relative,并确认父级有明确宽高。
  6. Less 改完刷新没变化

    • 现象:样式不生效,css 目录里没有新文件。
    • 原因:首行没写输出路径注释,或 @import "./base" 路径写错,编译产物落到了 less 目录。
    • 修法:文件首行保留 // out: ../css/,检查 @import 相对路径,重新保存触发编译。
  7. 图标显示成方块或空白

    • 现象:icon-sousuo、icon-right 渲染不出来。
    • 原因:iconfont.css 未引入、引入路径错误,或类名拼错(多写/少写 icon-)。
    • 修法:确认 <link> 在 index.css 之前且路径正确,对照字体文件里的类名逐个核对。
  8. (15 / @vw) 写成 15 / @vw 或 15px

    • 现象:某个属性不生效,或尺寸明显不对(要么 15px 太小,要么计算被当成字符串)。
    • 原因:Less 除法需要括号包裹,且变量本身带单位,写成 px 会与 vw 混用。
    • 修法:统一写法 (数值 / @vw),全项目只保留这一种换算形式。

练习 ​

  1. 把 header 改成 position: fixed 后,页面出现"搜索框被盖住"和"横向滚动条"两个问题。请分别定位原因并修复,写出改动的那几行代码。

  2. 给排行榜的 .pic 和 .txt 各写一种方案,保证左侧图片宽度恒为 105/@vw 不被压缩;两种方案都写完后,说明你更倾向哪一种及理由。

  3. 把推荐歌单的 .pic 单独拎出来做成一个可复用的类(要求:图片等比裁切、圆角 10、左下角半透明角标只有左下与右下两角为圆角)。写出这个类以及它在 HTML 中需要的最小结构。

  4. 人为制造一个 bug:在 .list li 上把 height: (105 / @vw) 改成 height: 105px,在手机模拟器里观察排行榜的显示差异,记录现象并解释为什么。

  5. 用浏览器 DevTools 的"选中元素"功能,找出你页面中宽度超过 375 的元素(至少两个),列出它们的类名、实际宽度、以及是哪条样式导致超宽。

原视频对照 ​

#视频时长
1项目(搜索框)10:59
2项目(banner区域)09:33
3项目(标题)11:20
4项目(排行榜)15:37
5项目(推荐歌单)15:07
6项目(定位)11:10

小结 ​

  1. 移动端整页布局的正确顺序是:先按"大盒子 → 上下分区 → 左右分区"切结构,再写样式;样式永远在结构稳定之后再动。
  2. vw 换算必须全项目统一成一种写法,混用 px 是后期尺寸对不上号的头号来源。
  3. 页面出现横向滚动条,几乎只有一种可能:某个元素总宽度超了设计稿宽度。这应该成为你的第一反射,而不是去改 body { overflow: hidden }。
  4. 图片变形问题一律先问"父盒子和图片比例是否一致",不一致就用 object-fit,不要靠调宽高凑。
  5. 定位相关问题先看父级有没有 position: relative,绝对定位的参照物错位是最常见的定位 bug。
  6. 头部、标题这类会重复出现的模块,第一遍就抽成公共样式写在模块外面,比事后重构省一半时间。
  7. 弹性盒子中"某个子项被莫名压窄",本质是它被允许收缩,加 flex-shrink: 0 或给兄弟项 flex: 1 即可。
  8. 遇到没见过的 CSS 属性,去 MDN 查定义和取值,别背速记、别靠猜——这条习惯比记住任何一个属性都值钱。