逐渐探索完善的一套响应式方案

296 阅读7分钟

整个响应式方案基于 vw, sass mixin, @media 来实现。在多个项目中应用该方案并迭代,目前看起来算是相对完善了吧。摸索的过程其实遇到很多问题,但人生不也正是不断发现问题,解决问题的过程。
效果展示:www.agora.io/cn/communit…

项目一

需求

只做pc端,要求所有元素随浏览器窗口大小而缩放

想法

首先想到的是类似于栅格布局的那种对宽度进行百分比分配的方案。可以将浏览器宽度平分成若干份,每一份为一个长度单位, 页面上所有元素的宽高和字号都可以用该单位进行表示。当浏览器宽度变化时,长度单位也在变化,页面上的所有元素也就实现了响应式。

方案

常用的能满足需求的有两种长度单位,rem 和 vw

  • rem:rem单位是相对根元素的 font-size 来决定大小的,可以用 js 去监听页面宽度的变化,并根据自己想要的效果来动态改 变根元素 font-size 的大小。
  • vw:任意层级元素,在使用 vw 单位的情况下,1vw始终等于浏览器视口口宽度的百分之一。

rem方案 js 和 css 产生了耦合,需要通过 js 来控制这个长度单位,看起来很灵活,但是这个东西完全可以交给 css 的媒体查询来做。而 vw 会自动变化,无耦合

遇到的问题

问题

我们使用 vw 的流程是从设计稿上取出 px 值转换为 vw 使用,通常不同的设计稿这个转换关系是不同的,每次都手动由 px 换算到 vw 会很麻烦

解决
  1. 采用 postcss 插件:postcss-px-to-viewport,可以自动将 css 代码中的 px 转成 vw 。这样我们可以在代码中肆意使用 px,打包时会自动将所有 px 转换成 vw。这种方案没有什么心智负担,但是灵活性不足,只能使用同一种转换关系,不适用于多端设计稿的场景
  2. Sass函数:每次使用 vw 时只需调用转换函数,将 px 值传入即可。好处是直观、灵活、易于扩展,最后选用了这种方案
@function px2vw($args) {
  $vwNumber: inspect($args / 19.2);
  @return $vwNumber + vw;
}

项目二

需求

PC 和移动端都要做响应式,PC 优先

响应式方案

  1. 小于 500px(移动端):内容区域与两侧的侧边栏均等比例缩放
  2. 500px - 950px(ipad 端):内容区域固定为移动端 500px 的效果,仅侧边栏放缩
  3. 大于 950px(pc端):内容区域与侧边栏均等比例缩放

想法

继续沿用之前的 vw 方案,通过 css 媒体查询实现双端样式。先写 pc 端样式,移动端继承 pc 端样式,并进行覆盖改动即可

遇到的问题

问题一

移动端、ipad端、pc端的 px 到 vw 转换关系都不一致,移动端要如何来继承 pc 端的基础样式

问题一解决

首先我们可以将样式分别写成 mixin 的形式,将转换函数以参数的形式传入。最终的三端样式可以通过媒体查询来做,我们在不同的媒体查询中注入不同的 mixin,在 mixin 中又可以传入不同的转换函数来实现多端样式的继承

// 转换函数定义
@function mobilePx2vw($args) {
  $vwNumber: inspect($args / 3.75);
  @return $vwNumber + vw;
}

@function ipadPx2vw($args) {
  $vwNumber: inspect($args * 500 / 375);
  @return $vwNumber + px;
}

@function px2vw($args) {
  $vwNumber: inspect($args / 19.2);
  @return $vwNumber + vw;
}
// 通过 mixin 注入
@mixin pc($fn) {
  .header {
    height: call($fn, 60);
  }
}

@mixin mobile($fn) {
  .header {
    height: call($fn, 20);
  }
}

@media screen and (max-width: 500px) {
  @include pc(get-function('px2vw'));
  @include mobile(get-function('mobilePx2vw'));
}

@media screen and (min-width: 500px) and (max-width: 950px) {
  @include pc(get-function('px2vw'));
  @include mobile(get-function('ipadPx2vw'));
}

@media screen and (min-width: 950px) {
  @include pc(get-function('px2vw'));
}
问题二

font-size 在浏览器上有一个最小值,当设计稿上的文字很小且浏览器窗口也较小时,容易达到最小值而不再变化,文字大小间的对比度减弱甚至消失

问题二解决

这个问题其实并没有从技术上解决,而是经过和产品/设计讨论后决定增加一个响应式区间,给中间内容区域一个最小的响应式宽度,这样字体大小就不会变形了
调整了响应式的方案: 新增 950px - 1600px 区间, 内容区域固定为浏览器窗口宽度 1600px 时的大小,仅两侧侧边栏放缩

问题三

内容区域的两侧侧边栏的宽度是个不定值,如何处理

问题三解决

要统一处理处理这个值可以将不同的间距分别用前文的转换函数表示,并当作参数注入到 mixin 中,在 mixin 中直接使用 padding 即可,无任何负担

$mobilePadding: mobilePx2VW(15);
$ipadPadding: ipadPx2VW(48);
$middlePadding: calc(50vw - 500px);
$largePadding: calc(50vw - 600px);
@mixin pc($fn, $padding) {
  .header {
    height: call($fn, 60);
    padding: 0 $padding;
  }
}

@mixin mobile($fn, $padding) {
  .header {
    height: call($fn, 20);
    padding: 0 $padding;
  }
}

项目三

问题一

前文我们处理了小屏幕下的问题,但是忽略了超大屏的体验。 由于是全响应式,在>2200px窗口下,字体和各个模块都有些过大了。

问题一解决

给中间内容区域一个最大的响应式宽度。 响应式方案可以继续分化出 >2200px 的方案:内容区域固定为窗口 2200px 宽时的大小,仅两侧侧边栏放缩

完整的响应式方案

  1. 小于 500px(移动端):内容区域与两侧的侧边栏均按移动端设计稿等比例缩放
  2. 500px - 1100px(小屏 / ipad 端):两侧侧边栏固定为50px,仅内容区域放缩
  3. 1100px - 1600px(中屏): 内容区域固定为窗口 1600px 宽时的大小,仅两侧侧边栏放缩
  4. 1600px - 2200px(大屏):内容区域与两侧侧边栏均等比例缩放
  5. 大于 2200px(超大屏):内容区域固定为窗口 2200px 宽时的大小,仅两侧侧边栏放缩
问题二

移动端样式与 pc 端样式高度耦合,经常修改 pc 端样式时移动端受到很大影响。而移动端由于继承了 pc 样式导致不可读,难以修改。对于后期的维护产生了很大的困扰。

问题二解决

再次审视之前的方案,当初将样式耦合在一起似乎是一个不 太好的选择,是时候让它们分家了。如右图所示,借用媒体查询,我们直接将公用样式写在 commonMixin 中,pc端样式写在 largeMixin 中,中小屏样式写在 mediumMixin 中, 移动端样式写在 smallMixin 中即可实现样式的解耦。

/**
* 页面公用样式集合,不同尺寸下相同的样式代码可以放在这里
* 这里定义是为了避免每个页面都要重复定义,增加代码复用性
*/
@mixin commonMixin($fn, $padding) {
}

/**
* 大屏样式集合,对应的是>1100px的样式集合
*/
@mixin largeMixin($fn, $padding) {
}

/**
* 中屏样式集合,对应的是[500px, 1100px]的样式集合
*/
@mixin mediumMixin($fn, $padding) {
}

/**
* 小屏样式集合,对应的是<500px的样式集合
*/
@mixin smallMixin($fn, $padding) {
}

/**
* 媒体查询集合,在需要做响应式的页面,增加 `@include mediaMixin;` 即可,增加代码复用性
* Note: `@include mediaMixin;` 一定要放在样式的最后!!!
* 原因:在`nuxt.config.js` 的 `styleResources` 字段会定义scss的全局的变量、方法和mixin等,即在当前页面的style解析之前就存在全局的`commonMixin`了,在组件页面的style中先定义
* `commonMixin` 会覆盖全局的`commonMixin`,然后再引入`mediaMixin`时,其引入的`commonMixin`一定是该组件定义的(覆盖后的),而不是全局的。相反,如果我们先不覆盖`commonMixin`,在开始就引入
* `mediaMixin`,其引入的`commonMixin`一定是全局的,这样会导致我们的样式失效
*/
@mixin mediaMixin() {
  // <500px, padding与内容区域均等比例缩放
  @media screen and (max-width: 500px) {
    @include commonMixin(get-function('smallPx2vw'), $smallPadding);
    @include smallMixin(get-function('smallPx2vw'), $smallPadding);
  }

  // 500px - 1100px, padding固定为50px,仅内容区域放缩
  @media screen and (min-width: 500px) and (max-width: 1100px) {
    @include commonMixin(get-function('ipadPx2vw'), $mediumFixedPadding);
    @include mediumMixin(get-function('ipadPx2vw'), $mediumFixedPadding);
  }

  // 1100px - 1600px, 内容区域固定为1600px时的大小,仅padding放缩
  @media screen and (min-width: 1100px) and (max-width: 1600px) {
    @include commonMixin(get-function('mediumPx2vw'), $mediumPadding);
    @include largeMixin(get-function('mediumPx2vw'), $mediumPadding);
  }

  // 1600px - 2200px, padding与内容区域均等比例缩放
  @media screen and (min-width: 1600px) and (max-width: 2200px) {
    @include commonMixin(get-function('largePx2vw'), $largePadding);
    @include largeMixin(get-function('largePx2vw'), $largePadding);
  }

  // >2200px, 内容区域固定为2200px时的大小,仅padding放缩
  @media screen and (min-width: 2200px) {
    @include commonMixin(get-function('superLargePx2vw'), $superLargePadding);
    @include largeMixin(get-function('superLargePx2vw'), $superLargePadding);
  }
}
问题三

媒体查询的代码我们可以看到,是非常冗杂的,如果每个文件都写一遍,使用负担还是挺大的

问题三解决

于是可以将整个媒体查询也封装成一个 mixin,并在每个文件自动引入,其他页面需要使用时直接导入即可(详情可见问题二中的代码)。如下所示,我们之后在使用时,只需要写几个 mixin,然后最后加一行 include 代码即可实现响应式

@mixin commonMixin($fn, $padding) {
    .header {
        padding: 0 $padding;
        display: flex;
    }
}

@mixin largeMixin($fn, $padding) {
    .header {
        height: call($fn, 60)
    }
}

@mixin mediumMixin($fn, $padding) {
    .header {
        height: call($fn, 40)
    }
}

@mixin smallMixin($fn, $padding) {
    .header {
        height: call($fn, 20)
    }
}

@include mediaMixin;