使用Sass Map实现响应式排版

本来要管理Rhythm排版一致不是一件易事,响应式中的Rhythm排版就更加困难。幸运的是,Sass的Map可以更好的管理和实现响应式排版。

Rhythm is…
a strong, regular, repeated pattern of movement or sound


Vertical rhythm is clearly an important part of Web design, yet on the subject of baseline, our community seems divided and there is no consensus as to how it fits in — if at all — with our growing and evolving toolkit for designing online.

有关于Rhythm相关知识可以点击下面链接进行详细的了解:

编写代码是一回事,但让段落的font-size根据响应式的断点保持一定的轨道(track)是另外一回事。从h1h6为每个断点给字体大小设置一个变量,如此把事情变得更加繁琐,特别当类型不在一个线性比例上时。

如果你试图在响应式中解决这样的排版,下面的代码看起来非常的熟悉:

p { font-size: 15px; }

@media screen and (min-width: 480px) {
  p { font-size: 16px; }
}
@media screen and (min-width: 640px) {
  p { font-size: 17px; }
}
@media screen and (min-width: 1024px) {
  p { font-size: 19px; }
}

Sass的变量能在一个项目中得到很好的重用,但用于管理响应式排版中的字体大小时就得非常的鸡肋:

$p-font-size-mobile : 15px;
$p-font-size-small  : 16px;
$p-font-size-medium : 17px;
$p-font-size-large  : 19px;

$h1-font-size-mobile: 28px;
$h1-font-size-small : 31px;
$h1-font-size-medium: 33px;
$h1-font-size-large : 36px;

// 我认为你是明白的…

这里演示了Sass的Map循环的强大之处: 他们能帮助我们更好的理管z-index的值颜色,不一会你将看到他们帮助我们更好的管理字体大小。

使用Sass Maps管理字体大小

根据Sass Map的key-value关系创建一个Sass Map。将断点设置为key,将字体大小设置为对应keyvalue

$p-font-sizes: (
  null  : 15px,
  480px : 16px,
  640px : 17px,
  1024px: 19px
);

记住移动先行(Mobile-first),我们看到第一个断点的key设置为null,表示默认的字体大小(不是媒体查询),并且按断点的升序排列。

接下来创建一个mixin,遍历Sass map中的key,并生成适当的媒体查询。

@mixin font-size($fs-map) {
  @each $fs-breakpoint, $fs-font-size in $fs-map {
    @if $fs-breakpoint == null {
      font-size: $fs-font-size;
    }
    @else {
      @media screen and (min-width: $fs-breakpoint) {
        font-size: $fs-font-size;
      }
    }
  }
}

注意:这是一个mixin,值得一提的是,Sass具有编程逻辑特性,Sass借助SassScript的扩展,可以在Sass中使用一些基本的编程结构,比如@if/@else@for@each等逻辑控制命令。我建议您花一些时间去阅读相关文档。Sass的特性中介绍了一些Sass的新特性,了解一些Sass可以做哪些事情。

现在我们可以要段落中调用前面声明的混合宏:

p {
  @include font-size($p-font-sizes);
}

编译出来的CSS:

p { font-size: 15px; }

@media screen and (min-width: 480px) {
  p { font-size: 16px; }
}
@media screen and (min-width: 640px) {
  p { font-size: 17px; }
}
@media screen and (min-width: 1024px) {
  p { font-size: 19px; }
}

管理和跟踪元素的字体大小变得容易多。对于新元素增加,只需要创建一个新的Sass Map和选择器中调用混合宏:

$h1-font-sizes: (
  null  : 28px,
  480px : 31px,
  640px : 33px,
  1024px: 36px
);

h1 {
  @include font-size($h1-font-sizes);
}

编译出来的CSS:

h1 {
  font-size: 28px;
}

@media screen and (min-width: 480px) {
  h1 {
    font-size: 31px;
  }
}

@media screen and (min-width: 640px) {
  h1 {
    font-size: 33px;
  }
}

@media screen and (min-width: 1024px) {
  h1 {
    font-size: 36px;
  }
}

字体大小一致的元素可以这样调用:

p, ul, ol {
  @include font-size($p-font-sizes);
}

编译出来的CSS:

p, ul, ol {
  font-size: 15px;
}

@media screen and (min-width: 480px) {
  p, ul, ol {
    font-size: 16px;
  }
}

@media screen and (min-width: 640px) {
  p, ul, ol {
    font-size: 17px;
  }
}

解决断点分段

等等,新问题又来了。如果我们想要的段落p的字体大小为17pxh1标题的字体大小为33px,而断点是700px,不是640px。如果使用上面的解决方案,需要在每个实例中更改断点640px。我们试图解决这个问题,无意之中创建了另一个:断点分段。

试想一下,我们可以使用Sass Map来管理字体大小,是不是可以使用Sass Map来管理断点,对吗?完全正确!

给常见的断点创建一个Sass Map,并且给每个值取一个适当的名称。我们也会改为font-size的Map中断点名称(key的名称),名称和断点Map $breakpoints相匹配。

$breakpoints: (
  small : 480px,
  medium: 700px, // Previously 640px
  large : 1024px
);

$p-font-sizes: (
  null  : 15px,
  small : 16px,
  medium: 17px,
  large : 19px
);

$h1-font-sizes: (
  null  : 28px
  small : 31px,
  medium: 33px,
  large : 36px
);

最后一步是调整mixin,通过断点的名称映射到字体断点,并且遍历整个map,取到适当的值:

@mixin font-size($fs-map, $fs-breakpoints: $breakpoints) {
  @each $fs-breakpoint, $fs-font-size in $fs-map {
    @if $fs-breakpoint == null {
      font-size: $fs-font-size;
    }
    @else {
      // If $fs-font-size is a key that exists in
      // $fs-breakpoints, use the value
      @if map-has-key($fs-breakpoints, $fs-breakpoint) {
        $fs-breakpoint: map-get($fs-breakpoints, $fs-breakpoint);
      }
      @media screen and (min-width: $fs-breakpoint) {
        font-size: $fs-font-size;
      }
    }
  }
}

注意:mixin中默认的断点Map是$breakpoints。如果你的断点变量名不同,一定要在参数中修改成你需要的断点变量名。

瞧!现在,我们要想给元素添加一个新的断点中字体字体大小,而且这个断点在$breakpoints中并不存在。只需要在字体大小的map中设置,只需要把需要的断点设置为一个key,并且设置对应的font-size值。那么mixin将这样工作:

$p-font-sizes: (
  null  : 15px,
  small : 16px,
  medium: 17px,
  900px : 18px,
  large : 19px,
  1440px: 20px,
);

p {
  @include font-size($p-font-sizes);
}

在mixin中Sass的map-has-key函数起到很强大的作用。他会检查Map的key的值是否在$breakpoints中,如果存在,它将使用key的值;如果不存在,它会认为key是一个定制的key,并且会使用这个定制key的值生成媒体查询。

p { font-size: 15px; }

@media screen and (min-width: 480px) {
  p { font-size: 16px; }
}
@media screen and (min-width: 700px) {
  p { font-size: 17px; }
}
@media screen and (min-width: 900px) {
  p { font-size: 18px; }
}
@media screen and (min-width: 1024px) {
  p { font-size: 19px; }
}
@media screen and (min-width: 1440px) {
  p { font-size: 20px; }
}

使用line-height来控制Vertical Rhythm

line-height对于实现Vertical Rhythm来说是一个很重要的参数。所以,我们提供一个line-height的解决方案,来解决Vertical Rhythm。

扩展font-size的map,在里面增加line-height。这里使用了Sass的list:

$breakpoints: (
  small : 480px,
  medium: 700px,
  large : 1024px
);

$p-font-sizes: (
  null  : (15px, 1.3),
  small : 16px,
  medium: (17px, 1.4),
  900px : 18px,
  large : (19px, 1.45),
  1440px: 20px,
);

注意:line-height值可以使用任何有效的CSS单位来定义(%pxem等),不过更建议使用不带任何单位来定义line-height,用来避免继承造成意想不到的结果。

然后,修改mixin,生成包括line-height的CSS:

@mixin font-size($fs-map, $fs-breakpoints: $breakpoints) {
  @each $fs-breakpoint, $fs-font-size in $fs-map {
    @if $fs-breakpoint == null {
      @include make-font-size($fs-font-size);
    }
    @else {
      // If $fs-font-size is a key that exists in
      // $fs-breakpoints, use the value
      @if map-has-key($fs-breakpoints, $fs-breakpoint) {
        $fs-breakpoint: map-get($fs-breakpoints, $fs-breakpoint);
      }
      @media screen and (min-width: $fs-breakpoint) {
        @include make-font-size($fs-font-size);
      }
    }
  }
}

// Utility function for mixin font-size
@mixin make-font-size($fs-font-size) {
  // If $fs-font-size is a list, include
  // both font-size and line-height
  @if type-of($fs-font-size) == "list" {
    font-size: nth($fs-font-size, 1);
    @if (length($fs-font-size) > 1) {
      line-height: nth($fs-font-size, 2);
    }
  }
  @else {
    font-size: $fs-font-size;
  }
}

mixin检查font-sizeMap中的值是不是一个列表。如果是一个列表,通过nth()函数索引出需要的值。理论上第一个值是字体大小,第二个是行高的值。我们来看看效果:

p {
  @include font-size($p-font-sizes);
}

编译出来的CSS:

p {
  font-size: 15px;
  line-height: 1.3;
}

@media screen and (min-width: 480px) {
  p {
    font-size: 16px;
  }
}

@media screen and (min-width: 700px) {
  p {
    font-size: 17px;
    line-height: 1.4;
  }
}

@media screen and (min-width: 900px) {
  p {
    font-size: 18px;
  }
}

@media screen and (min-width: 1024px) {
  p {
    font-size: 19px;
    line-height: 1.45;
  }
}

@media screen and (min-width: 1440px) {
  p {
    font-size: 20px;
  }
}

最后的解决方案是可易扩展,可以适应其他属性的加入,比如font-weightmargin等属性。关键是要修改make-font-size这个混合宏,并且通过nth()函数从列表中索引出相匹配的值。

总结

有各种各样的方法来处理响应式排版和Vertical Rhythm,并不局限于我的建议。然而,这是我很多次工作中总结出来的经验之谈。

使用Sass的混合宏可以在你编译出来的CSS中会产生重复的媒体查询。有关于重复媒体查询和媒体查询分组有很多讨论。使用@extend来替代mixin有助于性能和文件更小,然而测试得到的结论,并没有太大区别。最坏的情况就是比较丑,其他问题基本上是不存在

我也意识到,我的解决方案并不健壮(没有设计处理媒体查询的范围,max-width或视窗方向)。这些特性可以在mixin中实现(我个人版本也将px转换为em做单位),但对于复杂的媒体查询,我更喜欢用手写。不要你别忘记了,从现在开始,你可以使用map-get()函数来检索出需要的值。

扩展阅读

本文根据@Jonathan Suh的《Responsive Typography With Sass Maps》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://www.smashingmagazine.com/2015/06/17/responsive-typography-with-sass-maps/

原文链接:https://www.w3cplus.com/preprocessor/responsive-typography-with-sass-maps.html

发表评论

登录后才能评论