一直以来,习惯在 flex 布局中使用 gap 这个属性设置间距,一直以来也都是在最新的 Chrome 上调试,所以从来没有想在 flex gap 在其他浏览器上存在兼容性问题。最近看了一下文档才反应过来,gap 原来只是 grid 布局的属性,虽然近些年来主流浏览器都已经支持了,但是一些使用人数不少的浏览器其实仍然没有支持,包括 UC、QQ,以及运行在 Android 11 上的最新版 MS Edge。

这么方便的属性怎么可能放着它不用呢,于是有人做了 PostCSS 的插件(flex-gap-polyfill),自动对 CSS 里面的 flex box 进行处理,尝试过一下,基本上能用,但是要命的是里面用了很多 css 变量,在 css 代码压缩的时候很容易出问题(有些变量会被 esbuild 判定为无效,直接丢掉了),另外这个插件对处理过 flex box 后,会对 box 里面使用 absolute 定位的元素产生不可预见的影响,总之我并不推荐使用这个插件。

事实上 gap 可以用 margin 很轻易地实现,原理可以看这里,我给它封装了一套 SCSS 的 mixin。

// _polyfills.scss
@use 'sass:math';
@mixin _flex-gap($gap, $row: true) {
  $margin: math.div($gap, 2);
  $transform: -$margin;
  @if $row {
    margin-left: $transform;
    margin-right: $transform;
  } @else {
    margin-top: $transform;
    margin-bottom: $transform;
  }
  > * {
    @if $row {
      margin-left: $margin;
      margin-right: $margin;
    } @else {
      margin-top: $margin;
      margin-bottom: $margin;
    }
  }
}

@mixin flex-gap($gap, $flex-flow: 'row nowrap') {
  @if $flex-flow== 'row nowrap' or $flex-flow== 'row-reverse nowrap' {
    @include _flex-gap($gap, true);
  } @else if $flex-flow== 'column nowrap' or $flex-flow== 'column-reverse nowrap' {
    @include _flex-gap($gap, false);
  } @else if $flex-flow== 'row wrap' or $flex-flow== 'row-reverse wrap' {
    @include _flex-gap($gap, true);
    @include _flex-gap($gap, false);
  } @else if $flex-flow== 'column wrap' or $flex-flow== 'column-reverse wrap' {
    @include _flex-gap($gap, true);
    @include _flex-gap($gap, false);
  } @else {
    @error "The second paramater $flex-flow is set to be '#{$flex-flow}', which is illegal.";
  }
}

调用方法:

@use 'polyfills';
.pagination__container {
  width: 100%;
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
  align-items: center;
  // gap: 6px;
  @include polyfills.flex-gap(6px, 'row wrap');
  .item__wrapper {
    flex: 1 1 auto;
    width: 100%;
    font-size: 24px;
  }
}

注意这套 mixin 借助 margin 来实现,所以建议不用在 flex box 和 flex item 上设置任何 margin,如果需要,在里面或者外面再用一层 wrapper 套起来,在 wrapper 上设置 margin。

另外还有做自适应的需求,在不同屏幕宽度下可能需要设置不同的 flex-flow,可以用下面的 mixin 清除前面的 flex gap:

// _polyfills.scss
@mixin _flex-gap-unset($row: true) {
  $margin: 0;
  $transform: 0;
  @if $row {
    margin-left: $transform;
    margin-right: $transform;
  } @else {
    margin-top: $transform;
    margin-bottom: $transform;
  }
  > * {
    @if $row {
      margin-left: $margin;
      margin-right: $margin;
    } @else {
      margin-top: $margin;
      margin-bottom: $margin;
    }
  }
}

// unset flex-gap, used in @media screen width rules
@mixin flex-gap-unset($flex-flow: 'row nowrap') {
  @if $flex-flow== 'row nowrap' or $flex-flow== 'row-reverse nowrap' {
    @include _flex-gap-unset(true);
  } @else if $flex-flow== 'column nowrap' or $flex-flow== 'column-reverse nowrap' {
    @include _flex-gap-unset(false);
  } @else if $flex-flow== 'row wrap' or $flex-flow== 'row-reverse wrap' {
    @include _flex-gap-unset(true);
    @include _flex-gap-unset(false);
  } @else if $flex-flow== 'column wrap' or $flex-flow== 'column-reverse wrap' {
    @include _flex-gap-unset(true);
    @include _flex-gap-unset(false);
  } @else {
    @error "The second paramater $flex-flow is set to be '#{$flex-flow}', which is illegal.";
  }
}

使用:

.flex-box {
        position: relative;
        display: flex;
        flex-flow: row nowrap;
        justify-content: space-between;
        align-items: center;
        @include polyfills.flex-gap(12px, 'row nowrap');
        @media screen and (max-width: 800px) {
          flex-flow: column nowrap;
          @include polyfills.flex-gap-unset('row nowrap');
          @include polyfills.flex-gap(12px, 'column nowrap');
        }
}

Q.E.D.