说说我从业十年来前端响应式的发展历程

作者:最后一个农民工@掘金,转载需注明出处。

文章中部分示例的源码地址:github.com/fffmoon/res...

前言

从事前端工作这么多年,我有幸亲眼见证和参与了前端页面从"能用"到"精致体验"的演进过程。今天想和大家聊聊响应式设计(Responsive Web Design)从诞生到如今(2025 年)的发展历程,希望能给各位前端伙伴一些启发和思考。同时本文带有自己的理解,某些观点可能是狭隘、片面的,请不吝指正。

1. 远古时代:移动端与桌面端分离

在移动互联网初期,"移动端"和"桌面端"确实是两个完全不同的东西。老前端应该都记得,当时的主流做法是为手机单独制作移动版站点。

例如:百度首页,当我们在 url 加上 m 时候,跳转的是手机版。

实现原理:

javascript 复制代码
// 典型的UA检测跳转代码
if (
  /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
    userAgent
  )
) {
  deviceType = "移动设备";
  isMobile = true;
}

即使现在还有不少项目使用移动桌面分离的方式,因为响应式开发不仅仅是对前端有所要求,对设计师、UED、IXD 也有要求。本人也对两套开发乐此不疲,因为两套意味着更多的收入。

2. 响应式设计的诞生

2010 年,Ethan Marcotte 大佬 在《A List Apart》上发表了一篇开创性文章《Responsive Web Design》,提出了"响应式网页设计"的概念。这真正开启了响应式时代的大门。

先看看效果:

面对设备碎片化日益严重,响应式设计实现了一套代码适配多种设备,并且具有更好的 SEO 表现。 下面看看具体实现:

2.1 流体网格

百分比(%) ,而不是像素(px)来规定每个模块的宽度,让整个骨架结构 能随着屏幕大小平滑地伸缩

css 复制代码
    .container {
      width: 90%;
      max-width: 1200px;
      margin: 0 auto;
    }
    ​
    .column {
      width: 48%;
      float: left;
      margin-right: 4%;
    }
    ​
    .column:last-child {
      margin-right: 0;
    }

2.2 弹性媒体

让页面里的内容(图片、视频等) 也能自适应容器的宽度,保持布局的完整性和美观。

css 复制代码
    img,
    video,
    iframe {
      max-width: 100%;
      height: auto;
    }

2.3 媒体查询

特定的屏幕宽度下不同的样式处理

css 复制代码
    /* 基础样式(移动优先) */
    .sidebar {
      display: none;
    }
    ​
    /* 平板样式 */
    @media (min-width: 768px) {
      .main-content {
        width: 66.66%;
        float: left;
      }
    ​
      .sidebar {
        display: block;
        width: 33.33%;
        float: right;
      }
    }
    ​
    /* 桌面样式 */
    @media (min-width: 1024px) {
      .container {
        width: 80%;
      }
    }

3. Bootstrap 与栅格系统时代

众所周知,推广技术的从来不是提案本身,Bootstrap 承担着将响应式设计推广的责任。他的出现极大地降低了响应式设计的实践门槛,让java开发都够快速构建响应式界面。

先看看效果:

再看看具体的实现:

html 复制代码
    <div class="container">
      <h1 style="text-align:center;margin:20px 0">Bootstrap栅格系统核心演示</h1>
    ​
      <div class="info">调整浏览器窗口大小查看响应式变化</div>
    ​
      <!-- 基本栅格示例 -->
      <div class="row">
        <div class="col col-sm-6 col-md-4 col-lg-3">
          <div class="box">col-sm-6<br />col-md-4<br />col-lg-3</div>
        </div>
        <div class="col col-sm-6 col-md-4 col-lg-3">
          <div class="box">col-sm-6<br />col-md-4<br />col-lg-3</div>
        </div>
        <div class="col col-sm-6 col-md-4 col-lg-3">
          <div class="box">col-sm-6<br />col-md-4<br />col-lg-3</div>
        </div>
        <div class="col col-sm-6 col-md-4 col-lg-3">
          <div class="box">col-sm-6<br />col-md-4<br />col-lg-3</div>
        </div>
      </div>
    ​
      <div class="info">
        <p>移动优先策略:默认样式针对小屏幕,然后通过媒体查询为更大屏幕增强体验</p>
      </div>
    </div>

同时期还提出的"移动优先"的设计模式:即先为移动设备设计基础样式,然后通过媒体查询为更大屏幕增强体验,渐进式的网页开发。

4. Flex

flex 布局很早之前叫做 display: box,现代化的 flex 大概在2012年定型,2016 年主流浏览器支持。

即使到现在也可以一套 flex 走天下,flex基本是每个前端的必备技能,就不老生常谈。 这里介绍一个 flex 和流体网格的结合的技巧,在实际业务开发中,经常会遇见搜索表格布局。 具体场景是:搜索在顶部,表格在下面;表格可以滚动,搜索始终保持在顶部。

先看看效果:

表格容器需要使用到 scroll 属性,我遇到很多开发是写一个 js 实现表格高度的计算。但是使用 flex:1;min-h-0; 更高效。

核心代码:

css 复制代码
    .container {
      // 注意这里使用了集成父容器的100%高度,如果父容器不存在高度,需要设置 100vh
      height: 100%;
      width: 100%;
    ​
      display: flex;
      flex-direction: column;
    }
    ​
    .table-container {
      flex: 1;
      min-height: 0;
      overflow: auto;
    }

5. CSS Grid 布局

Grid 于 2017 年得到主流浏览器支持,提供了二维布局能力。

Grid 相比 Flex 能用更少的代码实现更加复杂的布局,但遇到复杂的布局后续修改心智负担较重。

css 复制代码
    .container {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
      gap: 20px;
    }
    ​
    /* 响应式调整 */
    @media (max-width: 768px) {
      .container {
        grid-template-columns: 1fr;
      }
    }

6. REM 与动态字体大小方案

淘宝的 lib-flexible 库是这一方案的典型代表,主要解决移动端适配问题。

在实践中还需要配合 cssrem 使用,或者使用vscode的插件px2rem。在vw没有成为标准之前我认为这方案无敌了。

实现原理:

JavaScrip 复制代码
    function setRem() {
      const docEl = document.documentElement;
      const width = docEl.clientWidth;
      const rem = width / 10; // 将屏幕分为10份
      docEl.style.fontSize = rem + "px";
    }
    ​
    window.addEventListener("resize", setRem);
    setRem();
    

在CSS中使用rem

CSS 复制代码
    .box {
      width: 2rem; /* 相当于屏幕宽度的20% */
      height: 1.5rem;
      font-size: 0.16rem;
    }

7. VW 视口单位方案

vw 单位直接相对于视口宽度,提供了更简洁的响应式方案。

先看看效果:

从预览效果可以看出,字体始终保持和宽度等比例缩放。

核心代码:

css 复制代码
    :root {
      --px-to-vw: calc(100vw / 375); /* 基于375px设计稿 */
    }

    .element {
      /* 设计稿中50px的元素 */
      width: calc(50 * var(--px-to-vw));
      height: calc(100 * var(--px-to-vw));
    }

    /* 或者使用PostCSS等工具自动转换 */
    /* 编译前: */
    .box {
      width: 50px;
      padding: 15px;
    }

    /* 编译后: */
    .box {
      width: 13.333vw; /* 50/375*100 */
      padding: 4vw; /* 15/375*100 */
    }

从代码中可以看出,其实是计算了设计稿的比例,后续所有单位都需要乘以这个比例有点麻烦了,如果是scss类的工具,可以新建函数解决:

scss 复制代码
    /* 移动端页面设计稿宽度 */
    $design-width: 750;
    @function pxtovw($px) {
      @return calc($px / $design-width * 100vw);
    }

但是这还是比较麻烦,有没有更加方便的办法?最好是什么额外代码都不加,就能实现!有的,借助 postcss-px-to-viewport 插件将CSS中的px转化为vw。

  1. 首先我们创建一个vite项目
sh 复制代码
    pnpm create vite@latest px-to-view

选择vue -> 选择JavaScript

  1. 安装依赖
sh 复制代码
    pnpm i postcss-px-to-viewport -D
  1. 添加postcss的配置文件 根目录新建 postcss.config.js 添加如下代码,注意要使用ES模块语法
javascirpt 复制代码
export default {
  plugins: {
    'postcss-px-to-viewport': {
      // 需要转换的单位,默认为 'px'
      unitToConvert: 'px',
      
      // 视窗宽度(设计稿宽度),默认 320px
      // 建议设置为设计稿的实际宽度(如 750px 对应移动端标准)
      viewportWidth: 750,
      
      // 视窗高度(可选,用于计算 vh 单位)
      // 如果不需要 vh 转换可不设置
      // viewportHeight: 1334,
      
      // 转换后的单位精度(小数点位数)
      unitPrecision: 5,
      
      // 需要转换的 CSS 属性列表
      // '*' 表示所有属性,'!font*' 表示排除字体相关属性
      propList: ['*'],
      
      // 转换后的视窗单位(vw 或 vh)
      viewportUnit: 'vw',
      
      // 字体使用的视窗单位
      fontViewportUnit: 'vw',
      
      // 不进行转换的选择器黑名单
      // 可以是字符串或正则表达式
      selectorBlackList: [
        // '.ignore',   // 忽略所有包含 .ignore 的类
        // '.el-button' // 忽略特定组件
      ],
      
      // 最小的转换像素值(小于此值不转换)
      minPixelValue: 1,
      
      // 是否转换媒体查询中的 px 值
      mediaQuery: false,
      
      // 是否直接替换值而不是添加备用
      replace: true,
      
      // 排除特定文件(正则表达式)
      // 如:/node_modules/ 排除 node_modules 中的文件
      exclude: /node_modules/,
      
      // 包含特定文件(优先级高于 exclude)
      // include: /src/,
      
      // 是否处理横屏模式
      landscape: false,
      
      // 横屏模式下的单位
      landscapeUnit: 'vw',
      
      // 横屏模式下的视窗宽度
      landscapeWidth: 1334,
      
      // 自定义转换函数(高级用法)
      // selectorConverter: (selector) => selector,
      
      // 转换规则覆盖(高级用法)
      // convertRule: (decl, options) => {}
    }
  }
}
  1. 修改HelloWorld.vue组件 添加如下代码
xml 复制代码
    <template>
      <div class="test-viewport">我是一个转化过后的元素</div>
    </template>

    <style lang="css" scoped>
    .test-viewport {
      width: 750px;
      font-size: 40px;
      text-align: center;
      background: #add8e6;
    }
    </style>
  1. 运行项目
markdown 复制代码
    pnpm dev

效果如下:如果页面元素宽度占满则说明配置成功。

8. 混合方案:REM + VW

结合 REM 和 VW 的优点,既能享受 vw 的便捷,又能限制最大最小尺寸。

先看看效果:

核心代码:

css 复制代码
    /* 核心混合方案 */
    html {
      /* 基于375px设计稿的响应式字体大小 */
      font-size: calc(100vw / 375 * 16);
    }

    /* 限制最大最小尺寸 */
    @media (min-width: 768px) {
      html {
        font-size: calc(768px / 375 * 16);
      }
    }

    @media (max-width: 320px) {
      html {
        font-size: calc(320px / 375 * 16);
      }
    }

同样可以借助插件,实现方便的转化。使用 postcss-pxtorem 插件。

  1. 首先我们创建一个vite项目
sh 复制代码
    pnpm create vite@latest px-to-rem

选择vue >>> 选择JavaScript

  1. 安装依赖
sh 复制代码
    pnpm i postcss-pxtorem -D
  1. 添加postcss的配置文件 根目录新建 postcss.config.js 添加如下代码,注意要使用ES模块语法
javascript 复制代码
    export default {
      plugins: {
        'postcss-pxtorem': {
           //根元素字体大小
           rootValue: 16,
           //匹配CSS中的属性,* 代表启用所有属性
           propList: ['*'],
           //转换成rem后保留的小数点位数
           unitPrecision: 5,
           //小于12px的样式不被替换成rem
           minPixelValue: 12,
           //忽略一些文件,不进行转换,比如我想忽略 依赖的UI框架
           exclude: ['node_modules']
         }
      },
    };
  1. 在styles文件夹下新建vw-rem.css
css 复制代码
/* vw-rem.css */
:root {
  /* 核心公式:基于375px设计稿的响应式字体大小 */
  /* 16px * (100vw / 375) = 动态rem基准 */
  font-size: calc(100vw / 375 * 16);
}

/* 限制最大最小尺寸 */
@media (min-width: 768px) {
  :root {
    /* 最大尺寸限制 */
    font-size: calc(768px / 375 * 16);
  }
}

@media (max-width: 320px) {
  :root {
    /* 最小尺寸限制 */
    font-size: calc(320px / 375 * 16);
  }
}
  1. 在main.js中引入
arduino 复制代码
import './styles/vw-rem.css'
  1. 修改 HelloWorld.vue 组件
xml 复制代码
<script setup>
</script>

<template>
  <h1>我是h1标题</h1>
  <h2>我是h2标题</h2>
  <h3>我是h3标题</h3>
  <p>我是p段落</p>
</template>

<style scoped>
h1{
  font-size: 48px;
}
</style>
  1. 预览效果:可以看到在大于768px、小于320px字体是没有变化的。

9. UI组件库的布局组件

在主流UI库中,都有封装好的布局容器,比如:element的Layout响应式布局、Ant Design的Grid栅格、naiveui 的 Grid。这些组件库和Bootstrap大同小异。

我主要讲讲 naiveui 的 Grid 组件。它支持根据自身宽度进行响应式布局,比较有新意。 虽然现在的容器查询能方便快捷的支持这一特性,但在当时容器查询还没成为标准。

先看看效果:

核心代码:

  1. 使用ResizeObserver API监听容器尺寸变化
javascript 复制代码
                // 使用ResizeObserver监听容器大小变化
                const resizeObserver = new ResizeObserver(entries => {
                    for (let entry of entries) {
                        if (entry.target === gridContainer) {
                            updateGridLayout();
                        }
                    }
                });
                
                resizeObserver.observe(gridContainer);
  1. 预设多个断点(XS/SM/MD/LG/XL),由于是html实例,这里我将端点值写死,如果在react、vue框架中,可以使用组件化进行封装。
  2. 动态布局计算,获取当前宽度,根据传入的端点值,设置grid属性
javascript 复制代码
                // 更新网格布局的函数
                function updateGridLayout() {
                    containerWidth = gridContainer.offsetWidth;
                    
                    // 根据容器宽度设置不同的列数
                    let cols;
                    if (containerWidth < 576) {
                        cols = 1; // XS
                    } else if (containerWidth < 768) {
                        cols = 2; // SM
                    } else if (containerWidth < 992) {
                        cols = 3; // MD
                    } else if (containerWidth < 1200) {
                        cols = 4; // LG
                    } else {
                        cols = 6; // XL
                    }
                    
                    // 应用列数
                    responsiveGrid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
                }

实现完成,如果是在react、vue项目中,可以进行组件封装。

10. 容器查询

随着组件化开发的普及,容器查询是响应式设计的又一次革命,允许组件根据容器尺寸而非视口尺寸进行响应。

先看看效果

核心实现:

css 复制代码
    .card-container {
      container-type: inline-size;
      container-name: card-container;
    }

    @container card-container (min-width: 400px) {
      .card {
        display: flex;
      }

      .card-image {
        width: 30%;
      }

      .card-content {
        width: 70%;
      }
    }

    @container card-container (max-width: 399px) {
      .card-image {
        width: 100%;
      }
    }

容器查询和组件化类似,可以将代码样式仅依赖于其容器尺寸,而非全局视口尺寸。做到和组件一样复用。

11. UnoCSS 与原子化响应式

原子化 CSS 框架如 UnoCSS、TailwindCSS 提供了新的响应式思路。

首先需要在 uno.config.ts 中设置响应式变量,如果不设置就是默认。但实际开发中,断点变量不仅是在unocss使用,通常还会在js、hook中使用。

php 复制代码
      import { defineConfig, presetAttributify, presetUno, transformerDirectives } from 'unocss'
      // ... 其他代码
      presets: [
        presetUno({
          theme: {
            breakpoints: {
              xs: '575.98px',
              sm: '767.98px',
              md: '991.98px',
              lg: '1199.98px',
              xl: '1399.98px',
            },
          },
        }),
      ],

这里我自定义了 UnoCSS 的响应式断点,其值借鉴了 Bootstrap 框架的默认断点设置

xml 复制代码
    <div class="w-full md:w-1/2 lg:w-1/3 xl:w-1/4 p-4 md:p-6 lg:p-8">
      <!-- 内容 -->
    </div>

    <!-- UnoCSS中的响应式 -->
    <div class="text-14px sm:text-16px md:text-18px">响应式文字大小</div>

总结当前响应式方案选择

项目类型 推荐方案 优势说明
移动端H5 REM + VW混合方案+PostCSS 兼顾灵活性和可控性
数据可视化大屏 VW/VH视口单位 +PostCSS 完美适配各种分辨率
管理后台、复杂应用 Flex + Grid + 容器查询 复杂布局处理能力强
内容型网站 CSS Grid + 媒体查询 SEO友好,语义化强

未来趋势

纵观响应式设计的发展历程,都是围绕html、css的能力的不断增强,从最开始的Bootstrap到现在的原子css,html和css结合似乎是未来趋势。我敢断定,Bootstrap 的思想还将继续影响前端生态系统。

参考资料

  1. alistapart.com/article/res... - 响应式设计的开创性文章
  2. developer.mozilla.org/en-US/docs/... - 最权威的 CSS Grid 参考资料
  3. developer.mozilla.org/en-US/docs/... - 完整的 Flexbox 指南
  4. www.w3.org/TR/css-cont... - W3C 官方容器查询规范
  5. getbootstrap.com/docs/5.3/la... - 最流行的栅格系统实现
  6. github.com/amfe/articl... - 淘宝移动端适配方案分析
  7. web.dev/articles/re... - Google Web Dev 的响应式设计指南
  8. developer.mozilla.org/en-US/docs/... - MDN 容器查询实践指南
相关推荐
namehu1 个月前
深度解析:移动端 1px 边框问题与 rem 方案
前端·javascript·响应式设计
数据智能老司机2 个月前
函数式事件驱动架构——交易系统(可观测性)
架构·scala·响应式设计
数据智能老司机2 个月前
函数式事件驱动架构——带副作用的流
架构·scala·响应式设计
Mr_Swilder2 个月前
基于物理的天空、大气与云渲染在 Frostbite 引擎中的应用
前端·编程语言·响应式设计
Codebee2 个月前
OneCode3.0 ComboInput 开发使用手册
人工智能·低代码·响应式设计
代码小学僧2 个月前
「双端 + 响应式」企业官网开发经验分享
前端·css·响应式设计
用户7579419949702 个月前
Vue响应式原理推导过程
vue.js·响应式设计
断竿散人3 个月前
📏CSS尺寸单位终极手册:从px到fr的完全征服指南
前端·css·响应式设计
charlee443 个月前
给Markdown渲染网页增加一个目录组件(Vite+Vditor+Handlebars)(下)
css·markdown·响应式设计·flexbox·粘性定位