基于容器大小的字体缩放

1755

我很难理解字体缩放。

目前我有一个网站,正文的font-size为100%。100%是相对于什么呢?这似乎计算出16像素。

我一直以为100%会与浏览器窗口大小有关, 但显然不是这样,因为无论窗口是否调整大小到移动设备或全屏桌面,都是16像素。

我该如何使网站上的文本根据其容器自适应缩放?我试过使用em,但它也不能缩放。

我的想法是,当您调整大小时,像菜单这样的东西会变形,因此我需要减少与容器宽度相关的.menuItem等元素的pxfont-size。(例如,在大型桌面上的菜单中,22px非常完美。在平板电脑宽度下,16px更合适。)

我知道我可以添加断点,但我真的希望文本能够自适应缩放,并且拥有额外的断点,否则,我将控制文本的每100像素宽度而拥有数百个断点。


51
你需要的是响应式或视口尺寸字体。http://css-tricks.com/viewport-sized-typography/ - gearsdigital
12
看看 **FitText**。 - Patsy Issa
4
使用CSS容器查询 - Janosh
@PatsyIssa 有没有 fittext 的 React 版本? - undefined
41个回答

1852

容器查询现在可能是最好的选择,取决于您的使用情况,在所有主要浏览器中都得到支持。在此处查看支持级别:https://caniuse.com/mdn-css_properties_container

现在非常容易控制。

.module h2 {
  font-size: 1em;
  container-name: sidebar
}

@container sidebar (min-width: 700px) {
  .module h2 {
    font-size: 2em;
  }
}

请查看 MDN 的容器查询文章获取更详细的信息: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Container_Queries 更多可实现的示例:https://css-tricks.com/a-few-times-container-size-queries-would-have-helped-me-out/

=========================

旧答案

如果容器不是body,则CSS Tricks在将文本适应容器大小中涵盖了您所有的选项。

如果容器是body,您要寻找的是视口百分比长度

视口百分比长度相对于初始包含块的大小。当初始包含块的高度或宽度发生变化时,它们会相应地缩放。但是,当根元素上的overflow值为auto时,假定不存在任何滚动条。

这些值是:

  • vw(视口宽度的百分比)
  • vh(视口高度的百分比)
  • vi(根元素内联轴方向上视口大小的1%)
  • vb(根元素块轴方向上视口大小的1%)
  • vminvwvh 中的较小值)
  • vmaxvwvh 中的较大值)

1 v* 等于初始包含块的1%。

使用方式如下:

p {
    font-size: 4vw;
}

正如您所看到的,当视口宽度增加时,font-size也会增加,而无需使用媒体查询。

这些值是一种尺寸单位,就像pxem一样,因此它们也可以用于调整其他元素的大小,例如宽度、边距或填充。

浏览器支持相当不错,但您可能需要一个备选方案,例如:

p {
    font-size: 16px;
    font-size: 4vw;
}

查看支持的统计数据:http://caniuse.com/#feat=viewport-units

此外,还要查看 CSS-Tricks 以获得更广泛的了解:Viewport Sized Typography

这里有一篇关于设置最小/最大尺寸并对尺寸进行更精确控制的好文章:Precise control over responsive typography

还有一篇文章介绍如何使用 calc() 设置大小,使文本填满视口:http://codepen.io/CrocoDillon/pen/fBJxu

此外,请阅读这篇文章,其中使用一种被称为“熔岩导线”的技术来调整行高。Molten Leading in CSS


4
有时候你不想缩放得太过夸张,而是更加微妙 - 比如在移动端略微缩小字体大小,但不要太小以至于难以阅读。在这种情况下,你可以使用 font-size: calc(1rem + 2vw) 或类似的东西来设置一个基础大小,然后添加视口的百分比到该基础上。调整数字以获得所需的正确缩放量。 - Simon East
容器查询现在是答案。但这个答案已经过时了。 - Joshua Robison
1
容器查询似乎还没有很好的工具支持。Visual Studio Code告诉我container-typecontainer-namecontainer不是被认可的CSS属性,它也不认识@container规则。使用@container查询也会导致SvelteKit出现错误,告诉我这是不正确的语法。我不会说容器查询已经过时了。或许再给它几年时间,让它在工具社区中得到适当的采用。 - OOPS Studio

419

但是如果容器不是视口(body)呢?

这个问题是由Alex2507rkt3的回答下的评论中提出的。

实际上,这并不意味着不能使用vw来为该容器设置大小。现在要看到任何变化,就必须假设容器在某种程度上可以灵活调整大小。无论是通过直接百分比设置width,还是通过设置100%减去边距。如果容器始终被设置为固定宽度,比如200px,那么只需设置适合该宽度的font-size即可,这点变得“没有意义”。

示例1

然而,在可调整宽度的容器中,必须意识到在某种程度上容器仍然是基于视口大小进行调整大小。因此,需要根据与视口的百分比大小差异调整vw设置,这意味着考虑到父包装器的大小。请参阅此示例

div {
    width: 50%;
    border: 1px solid black;
    margin: 20px;
    font-size: 16px;
    /* 100 = viewport width, as 1vw = 1/100th of that
       So if the container is 50% of viewport (as here)
       then factor that into how you want it to size.
       Let's say you like 5vw if it were the whole width,
       then for this container, size it at 2.5vw (5 * .5 [i.e. 50%])
    */
    font-size: 2.5vw;
}
假设这里的 divbody 的子元素,那么它的宽度为 100%50%,也就是视口尺寸。基本上,你需要设置一个你认为合适的 vw。就像你在上面的 CSS 内容中看到的我的注释一样,你可以通过数学方式进行推导,但你并不需要这样做。文本将通过容器的弹性来自动伸缩以适应视口大小的变化。这里有两个不同大小的容器的示例示例 2 你可以通过强制按照视口大小计算来帮助确保视口大小。请参考此示例
html {width: 100%;} /* Force 'html' to be viewport width */
body {width: 150%; } /* Overflow the body */

div {
    width: 50%;
    border: 1px solid black;
    margin: 20px;
    font-size: 16px;
    /* 100 = viewport width, as 1vw = 1/100th of that
       Here, the body is 150% of viewport, but the container is 50%
       of viewport, so both parents factor  into how you want it to size.
       Let's say you like 5vw if it were the whole width,
       then for this container, size it at 3.75vw
       (5 * 1.5 [i.e. 150%]) * .5 [i.e. 50%]
    */
    font-size: 3.75vw;
}

尺寸仍然基于视口,但实际上是根据容器大小设置的。

如果容器的大小动态变化......

如果容器元素的大小通过@media断点或JavaScript以其百分比关系动态改变,则无论基础“目标”是什么,都需要重新计算以保持文本大小的相同“关系”。

以上面的示例#1为例。如果div通过@media或JavaScript切换为25%宽度,则同时,font-size需要在媒体查询或JavaScript中调整为新计算 5vw * .25 = 1.25。这将使文本大小与原始50%容器的“宽度”由于视口大小减半而减小时的大小相同,但由于其自身的百分比计算而减小。

挑战

使用CSS的calc()函数很难进行动态调整,因为该函数目前不能用于font-size。因此,如果您的宽度正在calc()上更改,则无法进行纯CSS调整。当然,对于边距的宽度进行微小调整可能不足以引起对font-size的任何更改,因此这可能无关紧要。


35
好的答案,但是如果包含元素有最大宽度,它将不起作用。 - Himmators

155

使用SVG的解决方案:

.resizeme {
  resize: both;
  margin: 0;
  padding: 0;
  height: 75px;
  width: 500px;
  background-color: lightblue;
  overflow: hidden;
}
<div class="resizeme">
  <svg
    width="100%"
    height="100%"
    viewBox="0 0 500 75"
    preserveAspectRatio="xMinYMid meet"
    style="background-color:green"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
  >
        <text
          x="0"
          y="75"
          font-size="75"
          fill="black"
        >█Resize This█</text>
      </svg>
</div>

使用SVG和文本换行的解决方案,利用foreignObject元素:

.resizeme {
  resize: both;
  margin: 0;
  padding: 0;
  height: 200px;
  width: 500px;
  background-color: lightblue;
  overflow: hidden;
}
<div class="resizeme">
  <svg
    width="100%"
    height="100%"
    viewBox="0 0 500 200"
    preserveAspectRatio="xMinYMin meet"
  >
      <foreignObject width="100%" height="100%" xmlns="http://www.w3.org/1999/xhtml">
        <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:lightgreen;">
          <h1>heading</h1>
          <p>Resize the blue box.</p>
        </div>
      </foreignObject>
    </svg>
</div>


118

CSS容器查询

2022年底新增的CSS功能使得根据容器调整字体大小变得简单直观。

容器查询引入了一组新的CSS单位cqw/cqhcontainer query width/height)。要使用它们,您需要在要使用其大小的父元素上设置container-type属性(不一定是直接父元素)。最简示例:

<div>
  <p>Lorem ipsum dolor sit amet</p>
</div>

<style>
  div {
    container-type: inline-size;
  }
  p {
    font-size: 5cqw;
  }
</style>

字体大小会随着容器的增长而平滑增加。在容器宽度为1000px时,p标签的字体大小将为1000px / 100 * 5 = 50pxcontainer-type可以是sizeinline-sizesize跟踪容器的高度和宽度,允许您同时使用cqwcqh。 在Web上,大多数情况下,高度是基于内容计算的,您只需指定宽度。为了节省浏览器的工作量,通常应将container-type: inline-size;设置为浏览器仅跟踪内联维度,通常是宽度(除非您将writing-mode设置为垂直)。 容器查询的浏览器支持正在迅速增长:
  • 2023年01月01日:75%
  • 2023年04月03日:83%
  • 2023年11月24日:90%

4
虽然这不是详尽的答案,但在2022年及以后,它值得获得更多的赞。 - oelna
4
这就是答案。我们如何标记此问题以引起注意?因为这没有被标记为答案,所以我差点错过了它。 - Joshua Robison
3
这种方法和其他大多数方法的问题在于,当文本更改时,值需要进行调整。这意味着5cqw(或vw或其他任何内容)取决于第一次设置时的文本,而当文本是可变或本地化的时候,这是一个大忌。 - Christian

56

在我的某个项目中,我使用了vw和vh的“混合”方式来调整字体大小以满足我的需求,例如:

font-size: calc(3vw + 3vh);

我知道这并不能回答提问者的问题,但也许对其他人有帮助。


55

使用calc()、CSS单位和数学的纯CSS解决方案

这恰恰不是OP所要求的,但可能会让某些人过得愉快。这个答案并不是易于理解的,需要开发者自己进行一些研究。

我最终得出了一个纯CSS解决方案,使用不同的单位和calc()。您需要一些基本的数学公式来计算calc()表达式。

当我研究这个问题时,我需要在DOM中的一些父元素上设置全页面宽度响应式标题的一些填充。我将在此处使用我的值,您可以用自己的值替换它们。

到数学部分

你需要:

  • 在某个视口上调整好的比例。我使用了320像素,因此我得到了24像素高和224像素宽,所以比例是9.333...或28/3
  • 容器的宽度,我设置了padding: 3em和全宽度,所以这个宽度为100wv - 2 * 3em

X是容器的宽度,所以用您自己的表达式替换它或调整该值以获取全页文本。 R是比例,您可以通过在某个视口中调整值、检查元素的宽度和高度并用自己的值替换它们来获得它。此外,它是宽度/高度 ;)

x = 100vw - 2 * 3em = 100vw - 6em
r = 224px/24px = 9.333... = 28 / 3

y = x / r
  = (100vw - 6em) / (28 / 3)
  = (100vw - 6em) * 3 / 28
  = (300vw - 18em) / 28
  = (75vw - 4.5rem) / 7

瞬间生效!我写下了代码,然后咔擦一声就好了!

font-size: calc((75vw - 4.5rem) / 7)

添加到我的标题中,它在每个视口中都适当地调整了。

但是它是如何工作的?

我们需要一些常量。 100vw 表示视口的完整宽度,我的目标是建立一个带有一些填充的全宽标题。

比例。在一个视口中获取宽度和高度给我一个比例可以使用,在比例的基础上,我知道其他视口宽度下应该是什么高度。手动计算会花费大量时间并且至少需要大量的带宽,因此这不是一个好答案。

结论

我想知道为什么没有人想出这个方法,甚至有些人说这是不可能通过 CSS 调整的。我不喜欢使用 JavaScript 来调整元素,所以我不接受只使用 JavaScript(忘记 jQuery 吧)的答案。总之,很高兴找到了这个方法,这是网站设计中纯 CSS 实现的一步。

对于我文本中的任何不寻常约定,我表示歉意,我不是英语母语者,而且我也是 Stack Overflow 回答写作方面的新手。

还应该注意的是,某些浏览器中存在恶意滚动条。例如,在使用 Firefox 时,我注意到 100vw 表示视口的完整宽度,延伸到滚动条下方(其中内容无法扩展!),因此全宽文本必须仔细地进行边距设置,并最好在许多浏览器和设备上进行测试。


30
只有在以下三个条件同时满足的情况下,此解决方案才有效:(1)你知道字体名称,(2)该字体已经存在于用户设备中,(3)文本内容始终不变。这使得它的使用范围非常有限。 - Andrei Volgin
2
滚动条并不是邪恶的。我个人鄙视新的简化设计,即在悬停在过去的滚动条位置上之前隐藏滚动条。这关乎无障碍性等问题。 - Bobort

29

这个问题有一个重要的哲学原则。

最简单的方法是给body设定一个特定的字号(我建议10),然后所有其他元素的字号都使用emrem单位。我会给你举个例子来理解这些单位。Em总是相对于它的父元素:

body{font-size: 10px;}
.menu{font-size: 2em;} /* That means 2*10 pixels  = 20 pixels */
.menu li{font-size: 1.5em;} /* That means 1.5*20 pixels = 30 pixels */

Rem 始终相对于页面的根元素(即 body):

body{font-size: 10px;}
.menu{font-size: 2rem;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5rem;} /* that means 1.5*10 pixels = 15 pixels */

然后您可以创建一个脚本,根据容器宽度相对调整字体大小。但这不是我推荐的方法。例如,在900像素宽度的容器中,您可能会有一个p元素,其字体大小为12像素。根据您的想法,它将成为一个宽度为300像素、字体大小为4像素的容器。必须有一个下限。

其他解决方案是使用媒体查询,以便您可以为不同的宽度设置字体。

但我推荐的解决方案是使用帮助您完成此操作的JavaScript库。我目前发现的其中一个是fittext.js


29

有一种方法可以在不使用JavaScript的情况下实现这个功能!

您可以使用内联SVG图像。如果SVG是内联的,则可以在SVG上使用CSS。您必须记住,使用此方法意味着您的SVG图像将响应其容器大小。

尝试使用以下解决方案...

HTML

<div>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360.96 358.98" >
      <text>SAVE $500</text>
  </svg>
</div>

CSS

div {
  width: 50%; /* Set your container width */
  height: 50%; /* Set your container height */

}

svg {
  width: 100%;
  height: auto;

}

text {
  transform: translate(40px, 202px);
  font-size: 62px;
  fill: #000;
}

Example: https://jsfiddle.net/k8L4xLLa/32/

想要更炫酷的东西吗?

SVG 图像还允许您对形状和杂物进行酷炫的操作。看看这个可伸缩文本的绝佳用例...

https://jsfiddle.net/k8L4xLLa/14/


需要在SVG内编写文本位置(如果内容更改,位置也会发生变化),但如果内容不是动态的,则可以获得非常好的结果!谢谢! - isick

27

以下是函数:

document.body.setScaledFont = function(f) {
  var s = this.offsetWidth, fs = s * f;
  this.style.fontSize = fs + '%';
  return this
};

然后将所有文档子元素的字体大小转换为em%

然后添加类似以下内容的代码以设置基础字体大小。

document.body.setScaledFont(0.35);
window.onresize = function() {
    document.body.setScaledFont(0.35);
}

http://jsfiddle.net/0tpvccjt/


14

这可能不是非常实用,但如果您希望字体直接与父元素相关联,而不需要任何监听/循环(间隔)来读取div /页面大小的JavaScript,则有一种方法。 IFrame。

iframe中的任何内容都将考虑到iframe的大小作为视口的大小。因此,诀窍就是制作一个iframe,其宽度是您希望文本的最大宽度,并且其高度等于特定文本的纵横比*最大高度。

撇开视口单位不能与文本的父级单位并存(例如,使%大小像其他人一样行为)的限制,视口单位确实提供了一个非常强大的工具:能够获取最小/最大维度。在其他任何地方都无法做到这一点-您不能说...将此div的高度设置为父元素的宽度*某个值。

话虽如此,诀窍是使用vmin,并设置iframe大小,以便在高度是限制维度时[fraction]*总高度是良好的字体大小,在宽度是限制维度时[fraction]*总宽度。这就是为什么高度必须是宽度和纵横比的乘积的原因。

对于我的特定示例,您有

.main iframe{
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: calc(3.5 * 100%);
  background: rgba(0, 0, 0, 0);
  border-style: none;
  transform: translate3d(-50%, -50%, 0);
}

使用这种方法的小麻烦在于您必须手动设置iframe的CSS。如果您附加整个CSS文件,那么对于许多文本区域来说,这将占用大量带宽。因此,我直接从我的CSS中附加所需的规则。
var rule = document.styleSheets[1].rules[4];
var iDoc = document.querySelector('iframe').contentDocument;
iDoc.styleSheets[0].insertRule(rule.cssText);

您可以编写一个小函数,以获取影响文本区域的CSS规则/所有CSS规则。

我无法想到另一种方法来完成它,而不需要一些循环/监听JavaScript。真正的解决方案是浏览器提供一种作为父容器函数的文本缩放方式,并提供相同的vmin/vmax类型功能。

JSFiddle: https://jsfiddle.net/0jr7rrgm/3/ (单击一次将红色正方形锁定到鼠标上,再次单击将其释放)

大多数在小提琴中的JavaScript只是我的自定义单击拖动函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接