项目稳定性治理思考:十五个防御性CSS技能
分享十五个实用的、防御性 CSS 技能。
文章转载自前端进阶指南
一、概念解释
防御性 CSS,防的是谁?我把他总结为:一切使表现和行为偏离预期效果的情景。出现这些场景的原因是因为终端环境的多样化,开发及测试用例只能覆盖大多数使用场景,在其他环境下,解析机制差异、内容动态变化等,都是导致非预期效果的原因。
二、防御的必要性
防御性 CSS 不仅仅是为了兼容其他少数场景,避免边界情况,更大的价值在于提升团队协作的可能性。防御性 CSS 的意义类似 JS 中的 try…catch, 他可能无法缩短需求开发的时间,但却是你程序正确运行和稳定运行的最后一道防线,更何况 JS 的错误只有在用户交互后才有感知,而 CSS 一旦出错,直接赤裸裸的展现在用户面前,直接影响用户的使用率和留存率。
都说编程风格分为三种:能跑就行风格、中规中矩风格、锦上添花风格。能跑就行风格代表的是:每一个设定和判断都和当次需求贴合的严丝合缝,如同山羊走钢丝,摇摇欲坠,但就是不倒,不得不令人称奇,但这种风格,不仅对编程人员要求极高,而且十分不利于团队协作,一旦意料意外的情景发生或者需求变更,带来的是雪崩式的改动;中规中矩风格概述为,该写注释的地方写注释,该写思路的地方写自己这么做的理由,该兜底处理的地方做拦截处理,程序的鲁棒性和可维护性直接拉满;万无一失风格更多的像是处女座,追求极致和完美,在中规中矩风格上再增一抹亮色,年轻时候的“雷布斯”就是典型代表。防御性 CSS 的目的就是从技术上尽可能的改变编程者能跑就行的侥幸心理,提升项目的可用性和可维护性。其目的也可以归纳为让你的项目做到:跑起来不出错,改起来不骂人。
防御性 CSS 的作用是对常规 CSS 的兜底,是实现项目稳定性建设重要但极其容易被忽视的一环。
三、防御技能
技能一:flex-wrap
属性背景:flex-wrap 是 flex 布局中的属性,其作用是控制 flex 容器内元素所占空间超出 flex 容器空间时是否折行。
防御原因:flex-wrap 属性默认是不折行,容易忽略多元素溢出兜底;为兜底,请设置 flex-wrap: wrap;
意外后果:内部元素被裁剪,或 flex 容器出现滚动条;
应用场景:
1)开发中 flex 容器空间够用,但小尺寸屏幕会溢出;
2)内容由服务端下发,元素个数无法提前预支,超于预期时导致 flex 容器出现滚动条或内部元素被裁剪;
代码:
1 | .options-list { |
场景示例:
技能二:margin 间距
属性背景:margin 作用是调整元素的外边距。用于指定元素与周围空间的距离关系。
防御原因:防止元素与元素之间挤压空间,造成重叠等情况;
意外后果:元素重叠或被挤压;
应用场景:
1)内容所占空间无法保证与其他元素不存在挤压的场景;
代码:
1 | .section__title { |
场景示例:
技能三:长文本处理
背景:当文本长度超出容器时,该如何显示。
意外后果:文本折行,样式不统一;
应用场景:
1)要求列表表现一致但文本长度不可控;
此处假设与设计最终商定,超出部分以省略显示,那么,意外兜底的样式代码为:
1 | .username { |
场景示例:
技能四:防止图像被拉伸或压缩
背景:通常,服务器下发的图片尺寸以及用户自定义上传的图片,显示在页面时,不可能百分百与容器尺寸贴合,不可避免的会遇到图片的放缩处理。
意外后果:图像被拉伸或压缩;
应用场景:
1)服务端下发多种不确定尺寸的图片;
2)用户自定义上传图片且需要预览和编辑;
1 | .card__thumb { |
demo 链接:https://monageju.github.io/Blog/object_fit.html
场景示例:
技能五:锁定滚动链接
背景:overscroll-behavior 是 overscroll-behavior-x 和 overscroll-behavior-y 的简写属性,它控制的是元素滚动到边界时的表现。换个能听得懂的说法:在 JS 世界里,有事件冒泡机制,你可以通过 event 的 stopPropagation 方法去阻止冒泡的发生,同样,在 CSS 世界里,滚动也有冒泡机制,当内部元素滚动到边界时,如果继续滚动,会带动外层祖先元素发生滚动,这种现象被称为滚动链,为了方便记忆,你也可以把他形象的记忆为滚动冒泡。而 overscroll-behavior 这个属性,就是类似 event 的 stopPropagation 方法阻止冒泡事件一样,提供给开发者去控制内层元素是否可以发生”冒泡“带动外层元素滚动的属性。
意外后果:”滚动冒泡“ 或 ”滚动穿透“;
应用场景:
1)页面存在多层滚动元素,需要单独控制每层滚动是否引起外层滚动;
1 | .child { |
demo 链接:https://monageju.github.io/Blog/overscroll_behavior.html
场景示例:如 demo 链接示例
拓展
理解了 overscroll-behavior 属性的作用,现在我们来看点拓展的东西:
首先来看下 overscroll-behavior 的属性值有哪些:
overscroll-behavior 属性有 3 个值
auto - 默认。元素的滚动会传播给祖先元素。
contain - 阻止滚动链接。滚动不会传播给祖先,但会显示元素内的原生效果。例如,Android 上的炫光效果或 iOS 上的回弹效果,当用户触摸滚动边界时会通知用户。注意:overscroll-behavior: contain 在 html 元素上使用可防止滚动导航操作。
none - 和 contain 一样,但它也可以防止节点本身的滚动效果(例如 Android 炫光或 iOS 回弹)。
这里有两个效果:一是下拉刷新,二是炫光回弹,这里有个 demo 可以看到具体效果:链接传送门
下拉刷新是原生支持的功能,如果项目要求自定义下拉刷新效果,除了要考虑如何实现自定义,还要考虑如何去掉默认原生下拉刷新,否则就会出现两个并存的下拉刷新,而去掉原生的下拉刷新也很简单,只需要在 body 或 html 元素添加如下代码:
1 | body { |
至于禁用炫光和回弹效果,其实是应用 overscroll-behavior 属性的 none 属性值,具体代码如下:
1 | body { |
除了上述描述的两个效果,其实还有一个效果:手势导航,如左滑退出及右滑前进功能;而如果要禁用手势导航,可以使用如下代码:
1 | body { |
技能六:CSS 变量默认值
背景:CSS 变量可以实现动态控制元素属性,但是当 CSS 变量未定义或无效时,造成变量值异常,此时,元素的样式将会脱离预期,而变量默认值可以实现异常兜底,保证变量值异常时页面依然能运行。需要额外说明的是,备用值并不是用于实现浏览器兼容性的。如果浏览器不支持 CSS 自定义属性,备用值也没什么用。它仅对支持 CSS 自定义属性的浏览器提供了一个备份机制,该机制仅当给定值未定义或是无效值的时候生效,函数的第一个参数是自定义属性的名称。如果提供了第二个参数,则表示备用值,当自定义属性值无效时生效。
意外后果:因失去宽高等变量值而不显示或变形;
应用场景:
1 | .item { |
技能七:弹性元素尺寸 min-height / min-width
背景:当需求要求完整展示某个列表数据,但列表数据所占空间无法固定时,为避免部分内容过宽、过高突破固定空间破坏布局,可以使用弹性尺寸 min-_
或者 max-_
, 这样能自动适应部分内容所占空间过大或过小带来的样式美观问题;
意外后果:占用空间过大或过小,破坏布局或不美观;
应用场景:
1 | .hero { |
场景示例:
max-width 的使用场景:
如果对每一个元素使用固定的 width,则当内容空间大于容器尺寸时,将发生溢出,此时,需要使用 min-width 限制最小宽度,当超出尺寸时,能够实现自动适配。
技能八:被遗忘的 background-repeat
背景:使用图片作为容器的背景图,当容器的尺寸大于图片尺寸时,默认背景图会重复,如果你在开发中忽略了上述问题,则会出现背景图重复的问题;
场景示例:
解决办法:
代码如下 :
1 | background-image: url('..'); |
解决后效果:
技能九:媒体查询 @media
背景:媒体查询的使用更像是 CSS 中的条件判断,它会根据你定义的条件,当条件满足时,条件内的样式生效;
举例:当屏幕的宽度小于 600px 时,body 背景色为红色;当屏幕宽度介于 600-800px 之间时,body 背景色为黄色;当屏幕宽度大于 800px 时,body 的背景色为蓝色;
示例代码:
1 | /* 将 body 的背景色设置为蓝色 */ |
demo 链接如下:https://monageju.github.io/Blog/media.html
技能十:图片上的文字
背景:当需要在图片上层展示文字时,如果图片加载失败,而外层容器的背景色和文字颜色接近,那么文字的展示效果就不理想;
举例:容器背景设置为黑色,图片为橙色,文字颜色为近黑色,当图片加载失败时,文字的背景色直接变为容器的背景色,文字与容器背景色重合,示例如下;
解决后效果:
解决代码:
1 | .card__img { |
至此,即使图片加载失败,图片上的问题依然可以正常显示;至于图片加载失败时左上角的“破图”标记,可以使用伪类进行遮挡美化;
技能十一:合理使用滚动条属性
背景:当容器的空间固定时,如果内容超出容器,为正常显示完所有内容,同时不扩展所占空间,会使用 overflow 属性控制超出部分自动滚动展示,同时给与滚动条样式提示有剩余内容,但如果该属性使用不当,会造成样式很难看;
举例:overflow 属性有两个作用很相近的属性值,一个是 scroll, 另一个是 auto; 这两个属性值都能实现当内容大于所占空间时滚动展示,不同点在于使用 scroll 属性无论内容是否超出容器空间,都会展示滚动条,而 auto 属性会分辩条件,内容超出时才会展示滚动条,为超出时则会自动隐藏,样式上较为美观;
解决代码:
1 | .box { |
场景示例:
技能十二:预留滚动条空间,避免重排
背景:接技能十一,当我们正确使用了 overflow:auto 就万事大吉了吗?也不尽然。
设想这样一个场景:有一个宽度 100vw,高度为 100vh 的容器盒子,容器内展示商品卡片,滑动到页面底部时,触发滑动加载,当触发懒加载时,容器内商品卡片占用的高度已经超出 100vh,依据外层容器设置的 overflow:auto,内容超出时会展示滚动条,滚动条的出现,使得页面不得不给滚动条让出一定的宽度,这个切换的场景中,由于不得不给滚动条让位置,最外层的元素发生了元素宽度变化,产生了重排的效果,有没有可能避免这一次不必要的重排呢?答案是有的。
大家一定还记得 vue 的指令中有两个很相像的指令 v-if 和 v-show, 他们俩的原理和区别是什么?分别用在什么情景下?提醒到这,是不是有思路了?如果还没有,那也没关系,再提示一点点,既然要避免多余的一次重排,而滑动加载又不可避免,如果我一开始就预留好滚动条的位置,只是你看不见,到了滚动条应该出场的时候再让你看见,是不是就能避免不必要的重排了呢?现在再想想,这是不是就是 v-show 指令的设计原理?
CSS 中有一个 scrollbar-gutter 属性,当它的值设置为 stable 时,就能够实现上述的这种功能,代码如下:
1 | .box { |
举例:
内容较短时预留滚动条空间,内容超出时显示滚动条;
技能十三:图片最大宽度
背景:当给固定宽高容器设置背景图时,如果背景图尺寸超过容器宽高,图片会溢出,因此,最好在项目的 resetCss 中按照以下属性属性初始化:
1 | img { |
实例:
技能十四:粘性定位
说明:position 的粘性定位指的是通过用户的滚动,元素的 position 属性在 position:relative 与 position:fixed 定位之间切换;这对于需要使用滚动吸顶的场景非常方便;是典型的依据业务场景推动 CSS 技术发展的典例;
技能十五:浏览器兼容性 CSS 请勿批量处理
说明:根据 W3C 标准,批量分组选择选择器,如果分组中,其中一个无效,那么整个选择器都将会失效。因此,在遇到浏览器兼容属性时,切勿批量组合书写;
实例:
如果是如下书写方式,则该选择器没有任何问题,因为该分组选择器的所有选择器都有效:
1 | h1, |
此时,它的作用等同于:
1 | h1 { |
但如果,是下面这种情况就不同了:
1 | input::-webkit-input-placeholder, |
该选择器使用了分组选择器,在确定的某一个浏览器中,该分组中只有一个选择器有效,而其他选择都是失效状态,根据规则,整个分组选择器都将会失效,因此,正确的做法应该是分开写,代码如下:
1 | input::-webkit-input-placeholder { |
此时,其效果才是符合预期的。
四、结语
通常一个项目的稳定性指的都是逻辑层稳定和服务层稳定,CSS 是极其容易被忽视的一层;当项目发生线上故障时,逻辑层和服务器可以通过日志查询、抓包等手段定位,而 CSS 问题则只能凭借经验和项目所运行环境进行大致推断,极难快速准确定位问题。在稳定性建设时,CSS 的书写应该遵循“瞻前顾后”的防御性写法,尽可能的避免意外的边界情况,这才是防御性 CSS 的真实价值。