目录
CSS错误的常见原因
一般调试技巧
调试溢出
溢出的常见原因
调试浏览器不一致
视口单元和滚动条溢出
排版元素属性
浏览器功能支持的差异
意外的级联布局继承
DOM更改导致的CSS弹性失败
布局交换有助于避免CSS错误
布局间距
处理元素宽度
累积布局移位
调试CSS的资源
在CSS中调试意味着当你有意想不到的布局结果时找出可能是什么问题。我们将查看一些通常适合的错误类别,了解我们如何评估情况,并探索有助于防止这些错误的技术。
我们都去过那里,在完成CSS布局的最后——那是什么?啊! 一个额外的滚动条!或者,一个元素可能是一种意想不到的颜色。在某些浏览器上,该新功能似乎不起作用。
调试——不管是什么语言——从来都不是最喜欢的任务。CSS与其他语言一样,当您花时间了解它的怪癖时,它会变得更容易调试。它还有助于熟悉工具,以帮助您从一开始就进行调试和防止创建错误。
调试的第一步是退后一步,找出问题的主要原因。根据我的经验,CSS布局问题通常属于以下类别之一:
我们将通过了解这些问题的常见罪魁祸首来审查每个类别的调试,并了解如何使用开发工具和其他方法来查明有问题的样式。当然,我们也会讨论这些错误的可能解决方案。
当您的CSS出现问题时,您可以使用您喜欢的浏览器的内置开发工具开始:
由于CSS的全局性,一个元素的有问题的样式可能位于其父级、祖父级,甚至更远的树中。DevTools将在窗格顶部根据特性显示最适用于元素的规则,然后提供级联和继承样式的堆栈跟踪。
(大预览)
您还可以尝试通过仅将该部分放入本地文件或使用CodePen或CodeSandbox等在线编辑器来隔离特定布局问题。请注意,使用这些编辑器可能会插入您的本地环境没有的额外意见。例如,CodePen默认使用Normalize重置,如果您还没有使用它可能会引入新问题。关闭任何不适用于您的项目的设置。然后,您可以使用DevTools复制相关的HTML。
之后,另一个方便的功能是打开元素的上下文菜单(“右键单击”或等效菜单),然后从菜单中选择“复制 > 复制样式”。根据需要对每个嵌套元素重复。
(大预览)
如果隔离后问题不再存在,则很可能是祖先元素导致了问题。您可以选择从更高的DOM树开始进行隔离,或者您可能需要更仔细地检查继承。稍后我们将讨论继承。
如果您可以解决隔离组件中的问题,您可以将更新后的样式带回您的主项目样式表中。
Firefox中显示的“更改”面板,在本例中表示段落规则的边距更改。(大预览)
Chrome、Edge、Firefox和Safari还可以跟踪您所做的更改并保存它们,以便您更轻松地复制任何更新。
推荐阅读:“检查和编辑CSS ”,Firefox 开发者工具,MDN Web 文档
接下来将讨论更多的想法、技巧和工具。
溢出通常是最明显的问题之一,并且可能非常令人沮丧。并不总是一眼就能看出哪个元素导致溢出,尝试使用开发工具检查器梳理元素可能很乏味。
“CSS旨在不丢失内容,不造成伤害。为了不造成伤害,我们默认显示溢出的内容。”
— Miriam Suzanne,为什么CSS如此奇怪?(视频)
换个角度想,“CSS is Awesome”这个臭名昭著的模因实际上是正确和有意的行为,如果故意将基本框设置为小于容纳该文本所需的大小。(如果您有兴趣,原始创建者Steven Frank会在CSS Tricks中停下来解释起源)。
(大预览)
开始确定哪个元素负责溢出的一种经过验证的真实方法是添加以下CSS:
* {
outline: 1px solid red;
}
为什么是outline而不是border?因为它不会添加到元素的计算DOM大小。如果元素已经在使用边框,添加边框会改变元素的外观,并且可能会错误地导致额外的溢出问题。
(大预览)
使用outline的目的是揭示元素边界并可视化元素的嵌套方式。例如,如果溢出导致意外滚动条,轮廓可以帮助指出哪个元素被推出视口。
除了这种手动方法外,Firefox还显示滚动元素并指定哪些元素具有导致溢出的子元素,如此屏幕截图所示。
HTML有一个指示标记scroll,注意它已成为一个可滚动区域,而main有一个指示标记overflow,因为其中一个段落超出了其边界。(大预览)
通常,当我们担心溢出问题时,它是由于父元素与其子元素之间的宽度允许不匹配造成的。
首先要检查的事情之一是元素是否设置了一个绝对值width而没有响应方法来允许它完全向下调整大小。例如,一个600px盒子会在小于600px的视口上触发溢出。 相反,您可以调整以添加一个 max-width: 100%,以便元素也可以响应调整大小:
.wide-element {
width: 600px;
max-width: 100%;
}
这不是一个完整的解决方案的一个例子是触发溢出的元素也有边距,这会增加其在其父元素中的计算大小。在下面的示例中,由于其水平边距,段落仍被强制在main元素之外。
(大预览)
幸运的是,我们可以通过使用CSS数学函数calc减去水平边距使用的总面积来计算边距:
p:first-of-type {
width: 800px;
max-width: calc(100% - 6rem);
margin: 3rem;
}
现在,说了这么多,我们通常不应该为元素提供绝对宽度。更常见的是,最好仅在需要控制元素大小时进行max-width定义。同样,这减少了与响应调整大小相关的错误。事实上,我们可以通过删除width和max-width值来完全解决我们示例的问题,这允许段落的默认行为让它在main父级的可用空间内适当地调整。
但是,在某些情况下,我们研究的解决方案是有意义的,例如应用于max-width: 100%创建响应式图像。并且对于一些基于flexbox的布局方法,您还会看到在calc中使用width或flex-basis来计算差额。
另一个常见的溢出触发因素与CSS工作组在CSS设计中公认的错误之一有关。第6点,事实上, box-sizing应该默认为border-box而不是content-box。
当前所有浏览器都带有将元素框模型设置为使用content-box的传统决定,这意味着元素的边框和填充被添加到元素大小的计算中。因此,如果您为宽度和/或高度设置绝对尺寸,则需要为任何添加的边框和填充考虑额外的空间。
为了简化此行为,最佳实践是确保您的样式包括重置所有要使用border-box的元素。这在许多情况下减少了溢出的机会,并且由于消除了在最终元素尺寸中包含边框和填充的复杂性,从而使CSS布局更加可预测。
*,
*::before,
*::after {
box-sizing: border-box;
}
其他导致溢出的原因更适合我们接下来要查看的错误类别。
虽然我们生活在关键功能浏览器之间几乎相同的黄金时代,但仍有一些遗留选择可能会干扰。每个浏览器都附带一个称为用户代理(UA)样式的默认样式表。
以下是这些样式在主要浏览器中如何显示的示例:
从上到下:body的margin属性的Firefox、Safari和Chrome用户代理样式(大预览)
这些恰好突出了可能成为溢出的另一个常见触发器的默认值之一。该body元素在每个浏览器中都附加了边距,顶部的注释如下:
body {
/* Chromium and Firefox */
margin: 8px;
/* Safari/webkit */
margin-top: 8px;
}
您可能熟悉CSS重置的概念。这些年来随着CSS和浏览器支持的改进而不断发展,但将body背面重置为margin: 0通常是一项功能。
如果你使用CodePen,默认的重置是Normalize.css,由Nicolas Gallagher创建,最初发布于2012年。它可以在很多很多项目中找到,并且是一个相当固执的重置。但是,值得注意的是,它自2018年11月以来一直没有更新。
注意:这里的另一个及时注意是,Internet Explorer将于2022年6月15日正式结束生命周期)。这意味着不仅在重置中需要许多功能,而且在我们的样式表中,通常将不再需要!
CSS在过去几年中一直在迅速改进,当然,浏览器也在不断现代化。另一种更适合现代项目的重置是Andy Bell的Modern CSS Reset。这种重置足以消除大多数项目中仍然存在跨浏览器不一致的少数常见问题,同时又不会过于固执己见。它还考虑了一些基本的辅助功能。安迪解释了重置的每条规则,我发现它是一个可靠的起点。
让我们看一些可能导致布局错误的现代浏览器不一致。
在不删除body的margin情况下,我们在溢出部分的简单示例会触发溢出。那是因为我们使用了100vw作为main元素的width可能值之一。由于它没有减去margin,因此由于100vw比正文边缘之间的可用空间宽16px,它会发生溢出。
根据浏览器和操作系统的不同,您可能还会遇到浏览器滚动条宽度扰乱100vw计算的情况。目前,修复是更新到100%如果可以的话。很快我们还将拥有帮助我们计算滚动条宽度的scrollbar-gutter属性。这个属性在CSS溢出模块第4级中得到了扩展,并且从版本94开始在Chromium浏览器中获得了支持。Bramus对它的工作原理以及它将帮助解决scrollbar-gutter的问题进行了很好的分解。 解决方案的总结是确保您为滚动条留出空间,同时通过添加以下内容在内容的两侧实现均匀的间隙外观:overflow: auto; scrollbar-gutter: stable both-edges;
注意:如果您不确定您的内容是否需要滚动条,但知道有时可能需要滚动条,请务必使用auto溢出的值。这告诉浏览器仅在元素需要它们来容纳内容时才添加它们。也就是说,用户可以自定义他们的偏好以始终显示滚动条,这就是为什么该scrollbar-gutter属性将更加有价值!
在没有先移除边距的情况下设置min-height: 100vh在主体上也可能会出现问题。具体的100vh还有其他问题,这些问题与不同设备和浏览器组合如何实现100vh的计算。
虽然100vh似乎可以在大多数桌面环境中工作,但您可能已经经历过看似意外行为的挫败感,尤其是在iOS上的WebKit浏览器中进行测试时。在设置为100vh的固定布局内,网站内容的底部卡在浏览器chrome后面,在阻止滚动时使其无法访问。例如,在固定高度的移动菜单或单页应用程序或游戏中,尝试填充不超过可用视口高度。
Matt Smith寻求解决方案并发现在某些情况下可以使用以下组合解决100vh问题:
html {
height: -webkit-fill-available;
}
body {
min-height: 100vh;
/* mobile viewport bug fix */
min-height: -webkit-fill-available;
}
此解决方案不完善,我建议在实际设备上进行测试以确保它适用于您的布局。
Jen Simmons还分享了一种在Safari 15中可用的技术(时间戳:13m),可借助CSS环境变量来调整此行为。当不适用时,safe-area-inset-bottom将为0,并在适用时动态调整。此环境变量可用于填充、边距和calc内,如下所示:
body {
min-height: calc(100vh — env(safe-area-inset-bottom));
}
CSS工作组在草案中有一个改进的解决方案来解决这类问题,这将是一组用于“大、小和动态视口大小”的新单位。这些旨在更好地与不断变化的浏览器chrome的动态行为相对应,因为这是导致WebKit问题的原因。
这是当前草案的摘要(请注意,在它们在浏览器中稳定之前,它们可能仍有一些变化):
UA样式还包括常见排版元素(例如标题、段落和列表)的默认样式。通常,CSS重置或框架已经解决了这些问题。而且,虽然您可能不会将这些属性中的差异视为“错误”,但了解它们不是跨浏览器的默认默认值是有帮助的,因为这些样式是最有影响力的。
这里的主要注意事项是,如果您发现不一致,您可能需要选择您的首选值(例如font-size的特定值h1)并将其添加到您的样式表中。
浏览器在功能支持方面的差异成为最令人沮丧的类别,从现代浏览器一直延伸到CSS的开始。很简单,并非所有浏览器都同等支持所有CSS属性。
如前所述,我们正处于一个功能接近的时代,也是一个CSS语言快速发展和壮大的时代。因此,我们因功能支持而遇到的问题正在缩小,但与此同时,我们处于等待新事物全面上市的状态。
幸运的是,我们有多种工具可用于帮助研究开发过程中的功能支持并帮助解决不一致问题。
您可能已经知道的一个是caniuse,它列出了CSS和JavaScript功能的支持表。这里需要注意的是,浏览器使用数据基于Statcounter,它是200万个网站的样本。因此,这些百分比可能与您的受众不匹配,并且应该只是尝试确定为您的项目使用特定功能是否“安全”的一个数据点。
另一个有用的工具是VSCode扩展webhint,它也是Edge中问题面板的一部分。Webhint会提醒您可能具有较低浏览器支持的功能,以帮助您在开发过程中意识到这一点。您可以通过在包中包含一个browserslist来配置考虑哪些浏览器。
在开发过程中了解功能支持有助于您制定明智的解决方案。但有时,这与是否严格支持某个功能无关,而是该属性是否经历了语法更改。例如,有时会使用“供应商前缀”发布属性。您可能已经看过这些——以-webkit或-moz开头的。
通常,该属性的稳定版本不会继续有前缀,但最好包含前缀版本以获得最广泛的支持。幸运的是,您可以自动执行此步骤,而不是使用流行的autoprefixer包手动执行。此外,还支持将它包含在许多构建过程中,例如postCSS(我使用的方法)。与webhint一样,它会查看您的browserlist来确定提供前缀属性的支持级别。
除了这些工具之外,每个浏览器的开发工具都有一种方法来表示该浏览器何时不支持某个属性。Chromium、Safari和Firefox在其他样式旁边显示一个黄色警告三角形和一个悬停触发的属性以表示不受支持的属性。
从上到下:Chromium、Firefox和Safari 开发工具表明缺乏对lch颜色的支持,Safari也不支持宽高比。(大预览)
回顾如何处理缺乏对功能的支持有点超出了本文的范围。而且,在需要时提供备用方案或将解决方案设置为渐进式增强功能将是独一无二的。这就是为什么在开发期间使用工具帮助检查支持如此重要的原因。然后,您可以创建一个适合您需要的支持级别的解决方案,而不是拥有一个完整的解决方案,然后您必须在收到错误报告后对其进行调试和定义回退。
好的,所以您觉得您正在使用受良好支持的CSS功能。而且您刚刚向您的站点添加了一个新部分,并且您对CSS非常有信心。但是当你在浏览器中查看它时,事情似乎不正确。
特别是在使用框架或设计系统,然后还编写自定义CSS时,我们可能会遇到与级联和继承相关的错误。当放置在屏幕布局中时,孤立地工作的东西可能不起作用。
幸运的是,所有浏览器开发工具都允许跟踪级联。通过查看样式面板,我们可以看到哪些规则覆盖了我们的样式。或者,我们可以找到一个父组件,该父组件正在为我们的子组件添加我们不期望的布局方法。
这个简单的例子表明,规则main *是“赢得”应用于color段落的级联。开发工具操纵样式面板中的顺序以在顶部显示最适用的样式。。然后,我们可以看到仅p的color属性被划掉了,这是一个额外的指示符,表明该规则定义没有被应用。
开发工具显示,main *的规则被应用,而不是所选段落的p规则,使其成为深蓝色而不是灰色。(大预览)
让我们快速回到级联上的CSS基础知识。对于这个例子,你们中的一些人可能已经意识到main *与p具有相同的特异性。 但是级联的另一个非常关键的部分是规则的顺序,下面是示例样式表中的顺序:
body {
color: #222;
}
p {
color: #444;
}
main * {
color: hsl(260, 85%, 25%);
}
如果我们想确保规则只是p“获胜”,那么我们要么需要确保它遵循main规则,要么增加它的特异性。
CSS的这个基本特性非常强大,但如果您不熟悉它的工作原理,可能会被视为“错误”。如果您的任务是在遗留代码库上部署新功能,或者需要使用一个框架来解决继承的特异性更具挑战性,那肯定会令人沮丧。
解决由于级联引起的问题通常没有一个简单的答案。值得花时间从眼前的问题中退后一步,检查样式的层次,以确定进行更改的最佳位置。认识到这!important可能会给您带来更多与特异性相关的问题,因此请尽可能先尝试重新排序属性。或者,您可能想要切换到设置“组件”,它为样式提供了一层范围,并鼓励更加有意地继承。
说到层——加快规范流程是另一个新功能,专门用于协助协调级联和减轻冲突。目前,Cascade Layers规范(@layer)已在所有顶级浏览器中获得实验性支持。有关此即将推出的功能的更多信息,请查看Bramus对CSS层的出色概述。
注意:请务必查看本文末尾的资源,以获取与检查CSS特异性相关的一些链接。
有时,精心设计的CSS解决方案会停止工作。以我的经验,这通常是由于底层DOM发生变化而发生的。当我们基于当前DOM添加CSS时,我们的解决方案无法适应变化。
例如,如果我们为定义为.grid li的列表创建网格布局规则,然后DOM结构更改为一组article元素,则CSS将中断。
或者,如果我们设置了一系列完全适合原始空间的图标,但是客户端需要添加一个图标,这会导致溢出。
这些例子真正说明了为什么CSS是一项有价值的技能。如果您可以编写可扩展的、独立于DOM的CSS,您的解决方案将可扩展,并且您将减少此类错误的可能性。
与用另一种编程语言创建API类似,考虑如何在当前解决的问题之外使用CSS规则是值得的。
调试此类别通常意味着追溯原始规则以查看它们是否可以扩展以适用于更新的上下文。同样,您可以使用开发工具查找应用的规则,甚至可以通过参考链接找到源代码。
注意:有关如何处理此类别以及需要考虑的具体事项的更多提示,请查看我关于面向未来的CSS样式的文章。
让我们看一些可能导致布局错误的具体示例以及如何解决它们。
引入flexbox后,发布了许多网格布局解决方案,它们都有一些数学来计算flex子项的宽度。此外,该宽度必须注意添加margin以添加子项之间的空间。
好消息:现在所有常青浏览器都支持flexbo的gap属性!
CSS网格也支持该gap属性。margin上的gap的优点是它始终只适用于元素之间,而与方向无关。因此,不再需要尝试附加margin到“正确”端或必须在父级上使用负边距来抵消嵌套边距的时髦业务。
与margin不同,使用gap不太可能导致溢出,因为它从不应用于元素的外边缘。但是,如果子级无法调整到较窄的宽度,您仍然可能会遇到溢出。
如果您的flex或grid子级导致溢出,您可以尝试以下两种方法作为升级。
对于flex,请确保您使用flex-basis而不是width,并且该flex-shrink值设置为1。这些属性将确保允许减小元素的大小。
对于网格,我经常使用的一种方法来实现网格子项的自动换行行为如下:
grid-template-columns: repeat(auto-fit, minmax(30ch, 1fr));
但是,这可以防止元素缩小到最小宽度30ch以下。因此,相反,我们可以更新到这个解决方案,在更大的视口/更大的父级中保持我们的最小值,同时仍然允许子元素在更窄的空间内缩小:
grid-template-columns: repeat(auto-fit, minmax(min(100%, 30ch), 1fr));
我经常发现CSS函数在这些场景中非常方便。如果你不熟悉,你可能会喜欢我关于CSS函数的实际使用的文章。
在网格或flex上下文之外,我们可以为元素实现类似的行为,以防止设置绝对宽度。我们在溢出部分讨论了绝对值如何经常导致问题。但是,当然,有时我们确实想提供一些宽度参数。再一次,我设置灵活宽度的首选方法是使用CSS的min()函数。在此示例中,它是同时设置width和max-width两者的简写。该min()函数将使用较小的计算值,该值会随着元素上下文的变化而动态变化:
width: min(100vw - 2rem, 80ch);
该min()函数接受两个以上的值,因此您甚至可以在此处添加一个百分比值。然后,您的容器不仅可以响应视口,还可以响应嵌套在其他容器中,从而进一步减少溢出错误——并实现我们创建可扩展、独立于DOM的样式的使命!
回顾min()作为容器样式的使用以及在此CodePen中使子网格更加灵活: min()和CSS Grid for responsive containers (codepen.io)
网站性能和相关指标中最近的一个热门话题是Cumulative Layout Shift (CLS)。这是在页面加载期间元素移动或跳跃的多少的Google Lighthouse分数。有些违规行为非常明显,例如广告和弹出横幅。这些东西使用运动并且通常延迟或故意延迟加载。
现在,在你着手解决问题之前,请确保有一个问题。Chrome和Edge在开发工具中包含“Lighthouse”面板,您可以使用该面板为您的网站的桌面版和移动版运行报告。如果CLS没有问题,该分数将小于0.1并以绿色指示器显示。
累积布局偏移的示例得分结果显示值为0.006,这是绿色的,因为它在允许的范围内。(大预览)
可能影响您网站的另外两件事是图像和自定义字体。
最近,所有浏览器都开始为图像保留空间,如果它们包含width和height属性的话。这些属性为浏览器提供了两条必要的信息来计算图像的纵横比并在页面布局中保留该空间。
然而,由于响应式设计,我们中的许多人习惯于剥离这些属性,假设CSS将接管。正如Jen Simmons解释的那样,是时候重新添加它们了。此外,您可能需要稍微调整您的响应式图像CSS以包含以下更具体的规则,这将允许图像响应更窄的上下文而不会丢失其纵横比:
img[width] {
height: auto;
}
至于自定义字体,当自定义字体和指定的系统后备字体严重不匹配时,问题就会出现在这里。在过去的几年里,我们称之为FLOUT(无样式文本的闪烁)。这种“闪烁”来自初始页面加载和自定义字体加载之间的时间延迟。
在我自己的站点中出现此问题的示例视频中,我使用Edge中的网络条件面板加载站点,并将“网络节流”设置为“慢3G”。
就我而言,实际的CLS分数并不表明问题严重到需要解决问题。但是,如果字体加载延迟导致大量元素移动,那么就值得研究如何缓解移动问题。
有时您可以选择与自定义字体更好匹配的后备系统字体,以便相对大小更接近匹配。或者,您可以通过在父元素上设置最小尺寸或调整其他布局属性来减少效果,以便在字体加载时不会导致显着变化。
如果您想了解有关该问题的更多信息,尤其是如果您正在处理对CLS的影响更显着的文本较多的网站,Simon Hearne也深入研究了导致布局变化的原因。他们得出结论,严格解决布局转变的最终解决方案是使用font-display: optional,但承认从设计角度来看这不是最佳的。Simon提供了更多解决方案来帮助您为您的站点选择正确的路径,包括一个方便的CodePen来帮助您测试回退。Google还提供了描述预加载字体的资源。
我们介绍了CSS错误的一些常见原因以及如何解决它们。以下是可以帮助您调试CSS的更多资源:
此外,在流行浏览器的开发工具之外,您可能会发现这两个替代品很有帮助,因为它们具有额外的工具和功能:
https://www.smashingmagazine.com/2021/10/guide-debugging-css/