使用相对 CSS 单位构建可调整大小的组件

Avatar of Ahmad Shadeed
Ahmad Shadeed

DigitalOcean 提供适用于您旅程各个阶段的云产品。立即开始使用 200 美元的免费积分!

以下内容是来自 Ahmad Shadeed 的客座文章。Ahmad 提供了大量的示例来展示使用相对单位对我们有何益处。我认为我们许多人认为像 em 这样的单位是用于 font-size 的,它确实可以用于 font-size,但也可以用于许多其他事物,将字号大小和其他视觉元素联系在一起。

我们生活在一个充满活力的世界中,我们所做的任何事都可能随时发生变化。作为前端开发者,我们应该以一种动态的方式构建我们的布局。

在这篇文章中,我们将探讨一种概念,它允许我们通过使用 CSS 相对单位(%、em 或 rem)来调整组件的大小。不仅是字号,还有该组件中的所有 UI。我们将查看实际示例,该方法的优缺点,甚至是一个以此方式构建的完整网页。

比例字体大小的简单示例

这里有三个元素

  1. 副标题
  2. 标题
  3. 左侧边框

使用类似于这样的 HTML

<article class="post">
   <a href="#">
     <span class="post-category">Featured</span>
     <h2 class="post-title">Building Dynamic Components is Awesome</h2>
   </a>
</article>

我希望所有内容都彼此成比例,这样当内容调整大小时,它们可以一起调整。

假设我们从像素开始

.post {
  border-left: 4px solid #4a90e2;
}
.post-category {
  font-size: 14px;
  color: #7d7d7d;
}
.post-title {
  font-size: 36px;
  font-weight: bold;
  color: #4a90de;
}

因此,调整整个组件大小的唯一方法是手动调整每个元素的 font-size,以使它们在新的尺寸下相互匹配。也许您的客户告诉您,他们希望看到该区域大 1.5 倍,您需要将这些尺寸调整为 21px54px

为了使调整变得更容易,我们可以使用百分比字体大小。

.post-category {
  font-size: 85%;
}
.post-title {
  font-size: 135%;
}

这表示:font-size 应该是其最近的父元素(具有定义的 font-size)的 85%。

我们可以将该 font-size 设置在父元素上

.post {
  font-size: 24px;

  /* 
    Child elements with % font sizes...

    85%
    0.85 * 24 = 20.4
    
    135%
    1.35 * 24 = 32.4
  */
}

当您使用百分比时,浏览器会为您计算这些字体大小,您实际上不需要关心最终的值是什么。它只是一个比例。

如果需要,我们可以使用 em 值执行完全相同的事情。对于字体大小,百分比和 em 基本上是相同的。

.post-category {
  font-size: 85%;
  /* the same as */
  font-size: 0.85em;
}
.post-title {
  font-size: 135%;
  /* the same as */
  font-size: 1.35em;
}

当我们使用 em 来表示除 font-size 以外的值时,它所代表的计算像素值仍然基于 font-size。这与例如百分比不同,百分比 width 是基于父元素的 width,而不是 font-size

例如,如果我们设置

.post {
  font-size: 24px;
  border-left: 0.25em solid #4a90e2;
}

border-left-width 将计算为 6px

在以下交互式演示中,滑块会更改两个组件的 font-size。第一个将所有内容都设置为像素:字体大小、边框、边距和填充。第二个将所有这些内容都设置为 em。

查看演示。

所有内容的比例缩放!

比例按钮

有时我们需要按钮的变体。也许一个更大的版本来强调更重要的号召性用语。我们可以通过使用 em 单位来获益,因为它可以用于填充,这样我们就可以轻松地通过 font-sizepadding 来增加按钮的大小。

<button class="button">Save Settings</button>

<button class="button button--medium">Save Settings</button>

<button class="button button--large">Save Settings</button>

如果我们将所有这些变体都设置为像素,我们将得到类似于以下内容:

.button {
  font-size: 16px;
  padding: 10px 16px;
  border-radius: 3px;
}

.button--medium {
  font-size: 24px;
  padding: 15px 24px;
  border-radius: 4px;
}

.button--large {
  font-size: 32px;
  padding: 20px 32px;
  border-radius: 5px;
}

相反,我们可以结合百分比和 em 值来使所有内容成比例,包括 border-radius

.button {
  font-size: 1em; /* Let's say this computes to 16px */
  padding: 0.625em 1em; /* 0.1875 * 16 = 10px */
  border-radius: 0.1875em; /* 0.1875 * 16 = 3px */
}

.button--medium {
  font-size: 130%;
}

.button--large {
  font-size: 160%;
}

查看所有内容是否按比例缩放

查看演示。

图像的比例宽度/高度

这是一个示例,其中我们需要将头像图像保持比旁注和发布时间更大一些。请注意蓝色高亮显示。当我们缩放 font-size 时,其高度应发生变化。

HTML 可以像这样

<div class="bio">
  <img src="author.jpg" alt="Photo of author Ahmad Shadeed">
  <div class="bio__meta">
    <h3><b>By:</b> Ahmad Shadeed</h3>
    <time>Posted on August 5, 2016</time>
  </div>
</div>

我们不仅需要设置 em 值的字体大小,还需要设置图像的宽度和高度。确保图像本身足够大,以便在缩放时不会损失太多质量!

.bio h3 {
  font-size: 1em;
}
.bio time {
  font-size: 0.875em;
}
.bio img {
  width: 3.125em;
  height: 3.125em;
}

缩放“边框”

另一个可以使用 em 值设置的属性是:box-shadow

我们已经知道边框可以使用 em 值进行缩放。在这里,我们将使用 em 值来设置内嵌 box-shadow 的“高度”,这样它就可以随着文本的大小进行缩放。

.headline {
  box-shadow: inset 0 -0.25em 0 0 #e7e7e7;
}

查看演示。

注意:我们也可以使用具有硬颜色停止的 CSS 渐变来实现这一点,因为这些停止也可以设置为 em 值!

为图标留出空间

假设我们有一个带有自定义图标的装饰性 <blockquote>。我们应该考虑字体大小发生变化时的场景。相对单位在这里可以再次提供帮助。

<blockquote class="quote">
  <p>
    <span>
      Building dynamic web components using modular design concepts is awesome. 
      <em>- Ahmad Shadeed</em>
    </span>
  </p>
</blockquote>

我们将使用我们已经提到的相对单位来设置所有内容。我们将通过伪元素应用的 SVG 图像来放置装饰性图标。我们将使用相对定位和大小来绝对定位该伪元素,并为它(使用相对填充)在父元素上留出足够的空间。

.quote {
  position: relative;
  padding: 1.5em 2em;
  padding-left: 4.5em;
  border-radius: 0.3125em;
}
.quote p {
  font-size: 2em;
}
.quote span {
  box-shadow: inset 0 -0.25em 0 0 rgba(255, 255, 255, 0.4);
}
.quote:before {
  content: "";
  position: absolute;
  top: 2.125em;
  left: 1.875em;
  background: url("quotes.svg") no-repeat;
  height: 1.875em;
  width: 1.875em;
}

有了它,如果我们更改字体大小,所有内容都会很好地缩放

请注意所有内容是如何按比例缩放的,就像我们在设计应用程序中对这些元素进行分组并进行拖动缩放一样!

如果我们使用像素值,那么事物将不会那么好地缩放。特别是图标,它可能会意外地靠近文本,或者意外地离文本很远

交互式示例

查看演示。

带有标题的图片

想象一下这样排列的照片和标题

我们可以基于字体大小来完成大部分设计,例如它向左和向上的偏移量、填充,甚至阴影。

<figure class="figure">
  <img src="sunrise.jpg" alt="Sunrise">
  <figcaption>The feeling you got from watching the sunrise is amazing.</figcaption>
</figure>
.figure figcaption {
  position: absolute;
  top: 1.25em;
  left: -1.875em;
  right: 0;
  padding: 1em;
  box-shadow: -0.3125em 0.3125em 0 0 rgba(0, 0, 0, 0.15);
  font-size: 1.75em;
}

查看演示。

装饰性背景

以这个内容块中标题后面的暗色圆圈为例

让我们确保当字体大小发生变化时,它也会相应地调整大小。但是这里还有更多细微的细节。边框半径应该也是相对的,虚线厚度也应该是相对的。

<section class="block">
  <h3 class="block__title">Content outline</h3>
  <div class="block__content">
    <p>Description to be there....</p>
  </div>
</section>
.block__title {
  position: relative;
  font-size: 1.5em;
  padding: 0.5em;
}

.block__title:after {
  content: "";
  position: absolute;
  left: 0.25em;
  top: 0;
  width: 2.5em;
  height: 2.5em;
  border-radius: 50%;
  background: #000;
  opacity: 0.5;
  transform: scale(1.75);
}

.block__title:before {
  content: "";
  margin-left: 0.5em;
  border-bottom: 0.0625em dashed rgba(255, 255, 255, 0.5);
}

尽可能多地使用相对单位,所有内容都会缩放

查看演示。

带有图标的搜索输入

在按钮中使用图标很常见,但您也可以在输入框中使用它们。这是一个常见的示例,我们使用放大镜图标来指示搜索输入。

使用 background-image 来放置图标,并添加 padding-left 以防止文本与图标重叠。如果字体大小增加,我们应该相应地增加图标的大小。

<form class="search">
  <label for="search">Enter keyword:</label>
  <input type="search" id="search" placeholder="What are you searching about?">
</form>
.search input {
  width: 25em;
  font-size: 1em;
  padding: 0.625em;
  padding-left: 2.5em;
  border-radius: 0.3125em;
  border: 0.125em solid #b4b4b4;
  background: url("search.svg") left 0.625em center/1.5em 1.5em no-repeat;
}

所有经典内容都使用相对单位设置:填充、边框、边框半径... 但我们还将相对单位用于背景位置和背景大小。现在,所有内容都会很好地缩放!

查看演示。

切换

考虑将自定义复选框设计为来回切换开关

这里没有我们无法缩放的东西!

<form action="" class="switch">
  <p>Do you want to subscribe?</p>
  <input type="checkbox" name="" id="switch" class="off-screen">
  <label for="switch"><span class="off-screen">Do you want to subscribe?</span></label>
</form>
.switch label {
  width: 5.625em;
  height: 2.5em;
  border: 0.125em solid #b4b4b4;
  border-radius: 2.5em;
}

.switch label:before {
  content: "";
  right: 0.25em;
  top: 0.21875em;
  width: 2em;
  height: 2em;
}

随便缩放

查看演示。

限制行长,仅在需要时

像这样的一段内容

我们这里有相当大的水平空间。如果不限制任何内容,这段文字的行长就会过长,让人不舒服。设置max-width是一个限制行长的绝佳方法。我们可能不会用像素来设置它(因为我们一直在讨论的所有相同原因:它不会缩放),我们可能也不会使用百分比,因为在宽度较窄的情况下,100% 可能没问题。那就用相对单位吧!

<div class="hero">
  <h2>This is title for this hero section</h2>
  <p>And this paragraph is a sub title, as you know I'm writing an article about using em units to build dynamic components.</p>
  <a href="#">Read about hero</a>
</div>
.hero h2 {
  margin-bottom: 0.25em;
  font-size: 1.75em;
}

.hero p {
  margin-bottom: 1em;
  max-width: 28.125em; /* limit line length */
  font-size: 1.25em;
}

.hero a {
  display: inline-block;
  background: #4a90e2;
  padding: 0.7em 1.5em;
}

现在行长被限制了,但当可用宽度较小时,它会愉快地全宽显示。

查看演示。

按钮中的 SVG 图标

人们喜欢使用图标字体的原因之一是,字体会随着文本的大小自动调整。但这也可以用<img>(正如我们所见),也可以用内联<svg>图标来实现!

我们将使用 em 值来设置widthheight,然后图标将随着字体的缩放而按比例缩放。

<ul class="social">
    <li class="social__item">
      <a href="#">
        <svg width="32" height="32" viewBox="0 0 32 32">
        <!-- SVG Data -->                    
        </svg> 
        Like on Facebook
      </a>
    </li>
    <li class="social__item">
      <a href="#">
        <svg width="32" height="32" viewBox="0 0 32 32">
        <!-- SVG Data -->                    
        </svg>  
        Follow on Twitter
      </a>
    </li>
    <li class="social__item">
      <a href="#">
        <svg width="32" height="32" viewBox="0 0 32 32">
        <!-- SVG Data -->                    
        </svg> 
        Follow on Dribbble
      </a>
    </li>
</ul>
.social__item svg {
  display: inline-block;
  vertical-align: middle;
  width: 2.1875em;
  height: 2.1875em;
}

查看演示。

列表计数器

假设我们用自定义设计的计数器创建了一个列表。用这种方式将文本设置到容器中,我们最好确保所有内容都按比例缩放,否则就会出现这样的问题

<ul class="list">
  <li>Go to example.com and click on Register</li>
  <li>Enter your email address</li>
  <li>Pick a strong password</li>
  <li>Congrats! You now have an account</li>
</ul>
.list li {
  position: relative;
  padding-left: 3.125em;
  margin-bottom: 1em;
  min-height: 2.5em;
}

.list li:before {
  font-size: 1em;
  width: 2.5em;
  height: 2.5em;
  text-align: center;
  line-height: 2.5em;
  border-radius: 50%;
}

快乐地缩放

查看演示。

定位图标 - 列表/警报/模态

我想你已经明白了,所以我们再举几个例子,用一个演示让你自己看看相对缩放是如何有帮助的。

查看演示。

汉堡菜单图标

也许你用元素和伪元素“伪造”了一个图标。这也应该是可缩放的。

查看演示。

渐变

我们已经了解了如何使用相对单位来设置 background-size。我们还暗示了渐变中的颜色停靠点可以使用相对单位来设置。让我们试试吧!

.box-1 {
  background: 
    linear-gradient(
      to right, 
      #4a90e2 0, 
      #4a90e2 0.625em, 
      #1b5dab 0.625em, 
      #1b5dab 1.875em, 
      #4a90e2 0, 
      #4a90e2 3.125em
    );
  background-size: 1.25em 100%;
}

查看演示。

图像精灵

有些东西有固定的尺寸,用像素来思考更容易,比如光栅图像。但这并不意味着我们不能用相对单位来处理它们。如果我们将 background-position 和 background-size 组合在 ems 中,我们可以使用CSS 精灵,它可以按尺寸缩放。

查看演示。


组合emrem

我们在这篇文章中主要使用了em单位。我们已经确定em单位是基于font-size并且是级联的。但是em有一个表亲单位:rem。rem 单位仍然是相对的,但只相对于根元素(例如html {}:root {})。因此,它不像 em 那样级联,但如果你改变根元素的font-size,它也会相应地改变。

通过组合 em 和 rem,我们可以保持一些尺寸固定,而其他尺寸保持动态。例如,假设你想让这些组件中的文本只相对于根元素,但其他元素相对于更直接的字体大小。比如图片,例如

看看调整直接的字体大小现在只影响图片了。

查看演示。


使用相对尺寸构建完整的网站

我构建了一个完整的页面,演示了如何将动态组件的概念应用到真实世界,而不仅仅局限于单个例子。

这里的所有东西都是动态尺寸的:徽标、标签、标题、作者、章节标题、编号列表、表单输入、按钮、引用块……几乎所有东西。

如果我们将默认的浏览器字体大小从 16px 更改为 20px,网站的缩放效果如下

太棒了,对吧?你注意到所有东西都缩放了吗,不仅仅是类型?这就是em的魅力! 😍

查看完整的页面演示。

请注意,字体大小也会随着媒体查询断点而变化。

缩放

我们基于 em 的设计也与浏览器缩放兼容。

而基于像素的设计可能会遇到问题

em带来的挑战

使用 em 时需要注意的一点是,当你在其中设置字体大小时,它是基于最近的父元素中声明的字体大小。

.parent {  
  font-size: 20px;
  .child { 
    /* This is based on 20px, so it's 30px */
    font-size: 1.5em;
  }
}

我想我们已经很清楚地表明了这一点。

但是当我们在 em 中调整其他东西的大小,它现在是基于当前元素新调整的字体大小。例如

.parent {
  font-size: 20px;
  .child {

    /* This is based on 20px, so it's 30px */
    font-size: 1.5em;
    
    /* This is based on 1.5em (not 20px), so it's also 30px */
    border: 1em solid black;
    
  }
}

看到同一个元素中的两个不同的 em 值评估为相同的最终值,这可能很奇怪。

除了 ems 的级联效果本身有时很具有挑战性之外,还会发生这种情况。如果你在组件内部用 ems 调整大小,而这些组件可以嵌套,这会导致大小的级联,这可能是不可取的。

总结

  • 用像素调整大小更难维护。它们不与任何其他东西相关。你需要手动调整所有像素才能改变比例。这很困难、费时且容易出错。
  • 在 ems 中设置值会使事物与字体大小成比例,因此更改字体大小会缩放该元素上的所有值(并级联到子元素)。
  • 用像素设置字体大小可能会阻止用户通过浏览器设置更改其默认字体大小,这对于可访问性来说是不好的

进一步阅读