现代 CSS 新增了很多强大且易用的 CSS 特性,但在实际开发过程中,大部分 Web 开发者都惧怕在生产环境中使用一些新技术,其中之一就是担心其兼容性以及不知道何时使用这些新特性。今天,我以以往的真实项目为例,来向大家分享一下,我是如何将新的 CSS 技术应用于实际项目中。
SVG运用和优势
先来说图标(Icon)的使用。整套视觉下来,页面上的图标(或类似图标)的场景还是蛮多的,比如:
仔细分析一下,有些图标是具有共性的,或者说只有一点点差异:
别的图标暂且不表,有两个地方是有共性的:
-
箭头图标是相同的,如果说差异,就是箭头有没有带容器和箭头容器的颜色有差异
-
微标(票房状态的图标,比如“平局”、“获胜”等),在不同的地方仅仅是颜色和大小的差异
自有Web技术到现在,Web上使用图标的技术也随着时代的变迁在改变:
-
<img>引用独立的Icon图片文件 -
background-image替代<img>标签,引用独立的Icon图片文件 -
将众多Icon图片合并在一起(俗称Sprites),使用
background-image和background-position来控制Icon图标的显示和位置 -
Icon Font的使用,比如阿里IconFont和Font Awesome
-
内联SVG和SVG Sprites
在这次的项目中,我采用了SVG Sprites相关的技术。在聊为什么采用SVG Sprites技术之前,先来对上面提到的两个典型的图标做一个简单的分析。
直接用下图来描述箭头图标的构造(一图胜过千言万语):
从上图中我们可以看出来,最共用的是箭头部分对应的SVG代码:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1568275614301" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2246" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M448 672c-6.4 0-19.2 0-25.6-6.4-12.8-12.8-12.8-32 0-44.8L531.2 512 422.4 409.6c-12.8-12.8-12.8-32 0-44.8s32-12.8 44.8 0l128 128c12.8 12.8 12.8 32 0 44.8l-128 128C467.2 672 454.4 672 448 672z" p-id="2247"></path>
</svg>
你可能发现了,在上图中箭头有不同的颜色,比如#FF1545和#fff,可以借助SVG的fill或strock属性,不管SVG标签行内添加样式,还是在CSS样式表中添加样式,可以将fill和strock设置为currentColor。
currentColor是CSS中第一个变量属性值,最大的特性就是可以根据当前color来决定值。
箭头容器就很好控制了,使用 CSS 即可。
该方案最大的优势,我们只需要一个箭头的矢量图(SVG的path就可以很好解决)。第二种思路要比上面这种略差一点,因为需要两个SVG的代码(两个不同的.svg)文件。一个是像上面所示的代码,不带容器;另外一个就是像下面这样的SVG代码,带圆形容器:
代码如下,也是用SVG的path绘制出来,同样的fill或strock设置为currentColor:
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 1024 1024">
<path d="M512 64C264.96 64 64 264.96 64 512s200.96 448 448 448 448-200.96 448-448S759.04 64 512 64zm149.76 471.968L501.504 694.464c-6.24 6.144-14.368 9.248-22.496 9.248-8.256 0-16.512-3.168-22.752-9.504-12.416-12.576-12.32-32.8.256-45.248L593.92 513.056 457.632 376.384c-12.48-12.512-12.448-32.768.064-45.248 12.512-12.512 32.768-12.448 45.248.064l158.912 159.36c.032.032.032.064.064.096s.064.032.096.064c2.944 2.976 5.056 6.432 6.592 10.048.064.128.224.256.256.384 4.736 11.616 2.368 25.44-7.104 34.816z"/>
</svg>
不管采用哪种方式,都可以达到我们内联使用SVG。只不过上面这种方式是属于人肉方式。这种方式除了人肉处理之外,还有另一个弊端:在不同的地方引用同一个箭头(SVG代码)会生具有同样的代码,让你的代码变得冗余。
随着工程化越来越强大,我们很多重复性的事情可以交给工程来处理。比如说,在工程中配置相关的事项,工程会帮我们处理SVG的代码。比如,将项目中的特定文件夹(比如 /icons/ )下的所有 .svg 文件合并在一起,并以SVG Sprites的方式将SVG代码内联到 index.html 中:
这样做的最大优势是“开发者无需关注内联的SVG Sprites代码是如何生成的,只需要将被使用的 .svg 文件存入到指定的文件目录中”。然后在调用的时候通过SVG的<use href="#id">调用SVG Sprites中<symbol id>即可。在React或Vue这样的JavaScript框架中,我们还可以创建一个独立的组件,比如Icon组件:
interface IconProps {
type: string;
width?: string;
height?: string;
className?: string;
styleName?: string;
}
const defaultProps: IconProps = {
type: '',
width: '1em',
height: '1em',
};
function Icon(props: IconProps) {
const { type, width, height, ...rest } = {
...defaultProps,
...props,
};
return (
<svg
width={width}
height={height}
fill="currentColor"
{...rest}
xmlns="http://www.w3.org/2000/svg"
dangerouslySetInnerHTML={{
__html: <use xlink:href="#icon-${type}" href="#icon-${type}"></use>,
}}>
{/* <use href={#icon-${type}}></use> */}
</svg>
);
}
export default Icon;
如此一来,使用的时候也会变得简单:
<Icon type="rightarrow" />
就是这么的简单。对于在工程中如何配置自动生成SVG Sprites,可以查阅读:
这次在使用SVG Sprites踩了一个坑,那就是使用<use href="#idname">调用SVG的<symbol id="name">时在部分移动终端不能正常的渲染SVG图标,后来查阅相关文档发现是JSX引起的。所以在构建Icon组件的时候使用:
dangerouslySetInnerHTML={{__html: <use xlink:href="#icon-${type}" href="#icon-${type}"></use> }}
来替代
<use href={#icon-${type}}></use>
接下来再来说“徽标”的使用:
正如上图所示,他们之间有很多共性,不同是“颜色”和“文本内容”。用内联的SVG非常的有益,或者说,我们可以构建一个独立的组件,比如IconBadge:
interface IconBadgeProps {
text?: string;
}
const IconBadge = (props: IconBadgeProps) => {
const { text } = props;
return (
<svg xmlns="https://www.w3.org/2000/svg" width="70" height="70" viewBox="0 0 70 70">
<g fill="none" fillRule="evenodd">
<circle cx="35" cy="35" r="34.3236486" stroke="currentColor" strokeWidth="1.3527027" />
<circle cx="35" cy="35" r="31.739062" stroke="currentColor" strokeWidth="0.52187604" />
<g fill="currentColor">
<path d="M35.18046574 13.08946812l-1.9177418 1.00821657.3662561-2.13543695-1.5514858-1.51232485 2.1441006-.31155606.9588709-1.94288472.958871 1.94288472 2.1441005.31155606-1.5514857 1.51232485.3662561 2.13543695zM47.1029719 16.08946832l-1.9177418 1.0082165.3662561-2.1354369-1.5514858-1.5123249 2.1441006-.311556.9588709-1.94288476.958871 1.94288476 2.1441006.311556-1.5514858 1.5123249.3662561 2.1354369zM23.10297162 16.08946814l-1.91774187 1.00821657.3662561-2.13543695-1.55148576-1.51232485 2.14410059-.31155606.95887094-1.94288472.95887093 1.94288472 2.14410059.31155606-1.55148576 1.51232485.36625611 2.13543695z" />
</g>
<g fill="currentColor">
<path d="M35.56785426 58.10022588l1.9177418-1.00821657-.3662561 2.13543695 1.5514858 1.51232485-2.1441006.31155606-.9588709 1.94288472-.958871-1.94288472-2.1441005-.31155606 1.5514857-1.51232485-.3662561-2.13543695zM23.6453481 55.10022568l1.9177418-1.0082165-.3662561 2.1354369 1.5514858 1.5123249-2.1441006.311556-.9588709 1.94288476-.958871-1.94288476-2.1441006-.311556 1.5514858-1.5123249-.3662561-2.1354369zM47.64534838 55.10022586l1.91774187-1.00821657-.3662561 2.13543695 1.55148576 1.51232485-2.14410059.31155606-.95887094 1.94288472-.95887093-1.94288472-2.14410059-.31155606 1.55148576-1.51232485-.36625611-2.13543695z" />
</g>
<text
fill="currentColor"
fontSize="48"
fontWeight="500"
letterSpacing=".50666669"
textAnchor="middle">
<tspan x="10" y="44">
{text}
</tspan>
</text>
</g>
</svg>
);
};
export default IconBadge;
调用的时候,只要给text传递不同的值,比如:
<IconBadge text="获胜" />
颜色的控件和前面所说的一样,通过fill设置currentColor来控制即可。
像上面这样使用,你可能会有一种顾虑,这说多的SVG代码,<path>中那么复杂,怎么撸出来。事实上没有那么复杂,时至今日,我们借助一些矢量图的编辑软件可以很容易的获取这些SVG代码。
只不过导出来的代码有些冗余,但我们在将导出来的SVG代码用到项目中之前,可以先使用在线工具svgomg 对SVG进行优化:
如果你的开发体系是React,还可以借助在线工具svg2jsx 直接将SVG代码转成JSX代码,然后放到你的项目中,避免不少的麻烦:
看上去很好,可事实却又是残酷的。最终因为“文字数量”的不一致以及不同平台对文本渲染的差异(特别是在安卓终端设备),渲染出来的效果无法让人接受。正因为如此,对于项目中徽标的使用,还是采用了<img>的方式。
回过头来,为什么不使用IconFont呢?有关于这方面的争论,社区一直没有停止过,这里简单罗列一下他们之间的差异:
| 字体图标 | SVG图标 | |
|---|---|---|
| 图标是矢量 | 浏览器会以字体解析它,所以浏览器会以文字的方式来对图标做抗锯齿处理,这可以导致字体图标没有期待中的那么锐利 | SVG是XML文件,浏览器直接解析XML文件,直接就是矢量图形,图标锐利,体积也小 |
| 可控制性 | 可以通过font-size、color、text-shadow 等CSS来控制图标 | 除了字体图标一样的CSS控制方法之外,还可以单独控制一个复合SVG图标中的某一部分,也可以给图标描边 |
| 控制图标位置 | 图标位置会受到line-height、vertical-align、letter-spacing等属性影响 | SVG图标的大小就是很精确的SVG图形的大小 |
| 图标加载 | 跨域时没有合理的CORS头部、字体文件未加载、@font-face 在Chrome中的bug和不支持 @font-face 的浏览器等,这些原因都会造成字体图标渲染失败 | SVG图标就是文档本身,只要支持SVG的浏览器,都能正常的渲染 |
| 语义化,易访问性 | 为了更好的显示图标,通常使用伪元素或伪类来做,这样做语义化较差 | SVG图标就是一个小图片。SVG的语义就是”我是一张图片“,感觉可能更好 |
| 易用性 | 使用一个已造好的字体图标集从来都不有效,因为有太多的图标未使用。而创建一个你自己的字体图标集也不是轻松的事情,需要懂得相关的编辑工具或应用软件 | SVG图标会简单一些,因为你可以自己手动地操作,如果需要的话,你可以使用相关的编辑工具 |
| 浏览器支持度 | 得到非常好的支持性,可以一直支持到IE6,在Opera mini,Android 2.1,Windows Phone 7.5-7.8得到支持 | 浏览器支持性一般,IE8和Android 2.1以及其以下浏览器不支持。不支持可以采用降级处理,但不并完美 |
在2019年的React Conf大会上,Facebook工程师分享的话题中就有一个有关于IconFont图标和SVG图标的。
Facebook 团队通过优化,将图标大小从 4046.05KB 降低到了 132.95kb,体积减少了惊人的 96.7% ,减少体积占总包体积的 19.6% !
这个优化是非常可人的,如果用这个来算钱的话,你懂的。其实现方式并不我们想象的那么复杂,很简单。下面是原始图标使用的代码:
<FontAwesomeIcon icon="coffee" />
<Icon icon={["fab", "twitter"]} />
<Button leftIcon="user" />
<FeatureGroup.Item icon="info" />
<FeatureGroup.Item icon={["fail", "info"]} />
在编译期间通过 AST 分析,将所有字符串引用换成了图标实例的引用,利用 Webpack 的 Tree-Shaking 功能实现按需加载,从而删除了没有使用到的图标。
import {faCoffee,faInfo,faUser} from "@fontawesome/free-solid-svg-icons"
import {faTwitter} from '@fontawesome/free-brands-svg-icons'
import {faInfo as faInfoFal} from '@fontawesome/pro-light-svg-icons'
<FontAwesomeIcon icon={faCoffee} />
<Icon icon={faTwitter} />
<Button leftIcon={faUser} />
<FeatureGroup.Item icon={faInfo} />
<FeatureGroup.Item icon={faInfoFal} />
有关于这方面更详细的介绍,可以阅读《font-awesome-codemod》或者 《Creating a custom transform for jscodeshift》。
除此之外,SVG Icon还有很多自身的优势,比如说你的Icon是一个多颜色的,那么使用SVG自身元素以及属性可以很好的控制图标的局部颜色:
另外,给图标添加动效,SVG也非常的灵活。特别是你希望给自己的产品在交互的时候带有一些微动效元素,那么SVG将是一个不错的选择,比如:
Demo 地址:codepen.io/Ilyatsuprun…
有关于SVG Animation这方面更详细的介绍,请移步阅读:
注意,SVG 动画只是 Web 动画中的一部分,如果你对 Web 动画感兴趣,或者想更深入的了解和掌握 Web 动画相关的技巧,请移步阅读我的小册《Web 动画之旅》!
CSS伪元素是把利器
CSS伪元素中的::before和::after对于CSSer来说是把利器,这两个伪元素配合W3C的另外一个规范 CSS Generated Content Module Level 3 中的content可以创建出两个伪元素。这样一来,一个HTML元素就具备多个盒模型,即有多个背景和边框等,正如下图所示:
借助该特性,在实际开发中能帮我们解决很多问题。
就拿这个项目来说吧,使用CSS伪元素生成内容的地方也很多,比如分隔线以及相同图形镜像等:
绘制图形的边角料,比如Tooltips、丝带边角(一般三角形为多):
伪元素除了在UI上还原可以帮助我们做很多意想不到的事情(效果),在UX上也是有帮助的。就拿元素或控件的可点击区域为例吧。不管是在PC端还是在移动端,我们都会涉及到可点击区域的体验。作为开发者,除了精确的还原视觉UI效果之外,还应该考虑用户体验的问题:
如果不考虑可点击区域,就会造成用户难于点击(不管是PC端上使用鼠标,还是在移动端用手指触控)。在这次互动的项目中同样有这样的需求。比如点击某个图标(实际UI尺寸大小小于控件设计规范),提示框(Tooltips)显示出来:
为了改变这一现象,不少同学会考虑在Icon图标容器上添加click事件,从而扩大可点区域;也有同学会考虑在Icon外额外添加一个容器,来扩大可点区域。事实上,我们还可以通过伪元素来扩大可点区域。比如下图这样的一个效果,不仅在一个button上可点击,而是借助伪元素让整个卡片都是可点击区域:
上面列出的仅是这次互动项目中有关于“伪元素”使用的场景,其实伪元素可用的场景还有很多,如果你对这方面感兴趣的话,可以阅读《伪元素能帮助我们做些什么》一文。
CSS选择器还能这样用
随着CSS不断的发展,这些年来她的变化也非常的大。其中选择器模块(Selectors Level 4)就给我们带来很大的便利。特别是在处理一些随着状态会变会UI的场景。比如,随着状态不同,按钮有不同的个数:
可能很多同学选择的方案是会给不同的状态定义不同的类名来控制布局的差异性。时至今日,我们可以不再依赖于脚本代码来帮我们做判断,完全可以把这种简单性的判断交给客户端去处理。
.modal__footer > div:last-child {
margin-right: 0;
}
.modal__footer > div:first-child:last-child {
margin: 0;
min-width: 400px;
}
还可以让组合选择器的条件更多一些,比如使用:not():
如上图所示,可以很好的控制:last-child的margin、padding或border之类的控制。甚至还可以将多个:not()和其他的伪类选择器结合,比如隐藏元素的样式处理:
.sr-only:not(:focus):not(:active) {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
white-space: nowrap;
clip: rect(0 0 0 0);
clip-path: inset(100%);
}
换个姿势使用盒阴影
说到盒阴影,大家首先想到的是box-shadow属性。虽然box-shadow能帮助我们实现大部分的效果,但是在面对一些场景,他是心有余,而力不足。比如:
对于不规则的图形添加阴影,以往是找设计师的同学在图片上做好阴影,但现在开始,我们可以使用代码来更灵活的应对视觉稿风格的调整。比如上图中“盾牌”的阴影,就是使用CSS filter的drop-shadow()来完成的:
.game__badge img {
filter: drop-shadow(0 4px 4px rgba(0, 0, 0, .3));
}
事情就是这么的简单,而且filter中的drop-shadow()还可以做很多有意思的东西。如果你对这方面知识感兴趣的话,这里推荐你花点时间阅读《Web 中阴影》一文。
说到filter的话加点额外的料,CSS的filter还可以做很多效果,除了让我们能通过样式代码处理图片效果之外,还可以给一些元素做特殊效果,比如下面这样的两个场景切换:
我最开始使用的就是filter: grayscale(1);。
CSS的
filter还有一个孪生兄弟,CSS混合模式(background-blend-mode和mix-blend-mode)。
继续回到盒阴影上面来吧。box-shadow经常用来实现 3D按钮 或者用来实现较有质感的效果。但在性能上也受诸多的挑战,特别多级阴影的使用以及和动态改变阴影效果场景中。虽然先天有一定的缺陷,但我们可以后天来弥补。比如说,借助前面说到的CSS伪元素::before和::after来实现这种3D按钮更有优势。
Demo 地址:codepen.io/airen/full/…
将box-shadow转换为伪元素实现阴影效果,对于性能是较大帮助的。特别是有动效的地方,可以让阴影效果更为平滑:
当你悬停在左边的卡片(在box-shadow上应用动画)与悬浮在右边的卡片(对其伪元素的opacity应用动画)进行相比时,你会很明显的发现有更多的重绘。
CSS Masking和Clipping
说到绘制图形,很多同学首先想到的是SVG或Canvas,其实CSS的一些特性也可以帮助我们绘制图形的,她也是非常强在的,强大到什么程度呢?给大家上一个堪称CSS绘制的艺术作品的截图:
上图是 @Diana Smith最新作品 ,另外在社区中有一个网站名叫 CSS ART ,收集了很多使用CSS绘制的作品。
看到上面的这些作品,只能感叹“没有做不到的,只有想不到的”!话又说回来,这东东并没有实际价值,但很多时候使用CSS绘制图形,还是能帮助我们减少应用的资源加载。而且也有很多CSS绘制的图形是有实用场景的。比如社区中非常有名的网站**A Single Div**:
URL: a.singlediv.com/
是不是觉得不可思议,要是你也想尝试着使用一个div绘制出一个图形(你喜欢的图形),其思路和具体操作步骤可以参阅读@Lynn Fisher和@Robert Nyman一起写的一篇教程《Single Div Drawings with CSS》。
咱们回到项目中的来,在这次项目中使用CSS绘制图形(取代图片)场景并不很多。这里拿下图来举例:
我们以三角形的绘制来说吧。
在CSS中绘制三角形,首先想到的会是border属性,因为该方案大家最早接触,也是最熟悉的方案之一:
正如《提示框组件的实现给我带来的思考和探索》一文中和大家聊的一样,绘制三角形的方案有很多种,特别是随着CSS Masking和Clipping越来越成熟之后,我们可以使用clip-path来绘制三角形(或别的你想要的图形 ):
类似上图所示,clip-path轻易的帮助我们绘制了直角三角形,代码也非常的简单:
.report__results::before {
clip-path: polygon(100% 0, 100% 100%, 0 100%);
}
.report__results::after {
clip-path: polygon(0 0, 100% 100%, 0 100%);
}
对于Tooltips上面的三角形,同样可以类似下图所示的一样,使用clip-path绘制出等边三角形或等腰三角形:
这个项目中的Tooltips是个半透明的效果,如果将三角形和容器分离开,在衔接处总是会有一定的瑕疵,如果采用《 提示框组件的实现给我带来的思考和探索 》中提到的绘制Tooltips的方案来绘制的话,就可以完美的解决!感兴趣的同学,可以在你的下一个项目中尝试使用
clip-path来帮助你完成。
踩中了100vh的坑
vw和vh是CSS的视窗单位,现在我们项目中针对于各种终端的适配方案,采用的都是vw。其中100vh对于处理全屏高度的布局非常的有效。针对这样的布局,我们在项目中都会使用:
html, body {
min-height: 100vh;
}
但这次我们在分享回流的全屏页面中踩中了一个100vh的坑:
在iOS系统下的带有刘海和安全区域的设备,比如iPhone X、XS、XR等都会有白边的现象。
当看到这个问题的时候,我自己也一脸朦逼,一直以来都这么用的,从未碰到类似的现象。后来在排查该问题的时候,发现其核心问题是移动端浏览器(Chrome和Safari)有一个功能,地址栏有时可见,有时会隐藏,从而改变了视窗的可见区域大小(在手淘的WebView容器也有类似的功能,顶部状态栏的隐藏)。这些浏览器没有将100vh的高度调整视窗高度为变化时屏幕的可视区域,而是将100vh设置为隐藏地址栏的浏览器高度。
为了解决这个白边现象,只好通过别的Hack形式来绕开。Hack思路很简单,100vh应该和我们屏幕的高度(screen.height)是相等的。然后再借助CSS自定义属性,在HTML的根元素<html>上显式声明一个自定义属性--vh。代码大致如下:
try {
var vh = screen.height * 0.01;
document.documentElement.style.setProperty('--vh', vh + 'px');
window.addEventListener('resize', function () {
var vh = screen.height * 0.01;
document.documentElement.style.setProperty('--vh', vh + 'px');
});
} catch (error) {}
有了这个--vh的值之后,CSS就可以很好的控制了:
.page__container {
min-height: 100vh;
}
@supports (padding-top: constant(safe-area-inset-top)) {
html,
body,
.page__container {
min-height: calc(var(--vh, 1vh) * 100) !important;
}
.page__container {
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
}
}
@supports (padding-top: env(safe-area-inset-top)) {
html,
body,
.page__container {
min-height: calc(var(--vh, 1vh) * 100) !important;
}
.page__container {
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
}
}
最终的效果完美的。
当然,随着更多的视窗单位出现,我们可以使用 dvh 来替代 vh ,如此一来,则不需要任何 Hack 手段即可避免 100vh 的天坑:
overflow触发Flexbox的边缘情况
业务中很多场景会有类似下图这样的需求:
虚线框中内容是带有滚动的。因为容器的高度是固定的,内容很有可能会超过容器的高度。
在目前Flexbox布局为主流布局的场景下,很有可能会在滚动容器上显式设置flex:1:
了解Flexbox的同学都或多或少的知道,Flexbox中的flex计算是件复杂的事情,正因为计算的较为复杂,在滚动容器中很有可能显式设置overflow-y为scroll时,滚动并没有生效。
这不是Flexbox的Bug,是特性!
在部分浏览器直接会把布局整乱:
事实上,这真不是Bug,在结果上和样式的正确使用,就不会出现这样的现象。比如:
对应的HTML结构也很简单:
<div class="main-container"> // flex-direction: column;
<div class="fixed-container">Fixed Container</div> // height: 100px;
<div class="content-wrapper"> // min-height: 0;
<div class="overflow-container">
<div class="overflow-content">
Overflow Content
</div>
</div>
</div>
</div>
关键样式代码:
.main-container {
display: flex;
flex-direction: column;
}
.fixed-container {
height: 100px;
}
.content-wrapper {
display: flex;
flex: 1;
min-height: 0; /* 这个很重要*/
}
.overflow-container {
flex: 1;
overflow-y: auto;
}
Demo 地址:codepen.io/airen/pen/K…
关键部分是 设置了flex:1的Flex项目需要显式的设置min-height:0,即滚动容器的父元素。这其实是Flexbox布局的一个边缘情况。最后再说一下,Flexbox布局中的flex是一个很有意思的东东,不花点时间是没法彻底搞清楚他的。如果你对这方面的感兴趣,可以阅读:
注意,这里既然说到了overflow,那么在CSS的世界中,overflow有很多边缘情况。如果你对overflow方面的感兴趣的话,可以阅读《溢出常见问题与排查》一文。
CSS条件特性
随着移动终端的品牌越来越多,我们前端在适配上面临的挑战也越来越大,和当年适配PC端各种浏览器已无较大的差异,甚至要比PC时代更难以应付。比如说,屏幕垂直方向的适配。由于不同的终端,其屏幕的高度也有差异化,对于全屏布局,要在不同高度的终端实现完美的适配,可以说是件痛苦的事情:
前端很多时候都是在适配中纠结着活着,这一点不假。
在这次项目开发中,同样面临着这样的问题。比如说浮层,设计师要求的是这样(设计师想得非常的周到,还给我们提供了详细的适配示意图):
对于这样的差异化布局,要实现起来还好,并不复杂。借助CSS的条件特性就可以,比如我们熟悉的媒体查询:
.bottomsheet__dialog {
position: absolute;
top: 400px;
right: 0;
bottom: 0;
left: 0;
transform-origin: center bottom;
border-radius: 40px 40px 0 0;
background: #fff;
padding-top: 122px;
}
@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3){
.bottomsheet__dialog {
top: 500px;
}
}
@media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3){
.bottomsheet__dialog {
top: 500px;
}
}
这是比较粗暴一点的做法,只考虑了两种情形。如果你要再细化一点也可以按上面的思路来处理,只不过工作量就上去了。这需要我们去平衡。
上面这样的场景似乎好处理,但对于下面这样的场景,那就不是件易事了:
设计师和业务方都希望所有的设备都在不出现滚动条,不被截取主要元素的条件下完全的全屏显示。
作为有追求的偶,我还是很想满足设计师和业务的需求,可是,我心中早就万马奔腾:
后来他说告诉我,这样的效果也是能接受的:
既然如此,那还不如让类似iPhoneXS、iPhone8的设备全屏幕显示,类似iPhone4的设备出现滚动条(毕竟这个量越来越少)。
行了行了,就这样撸吧:
.share__body {
padding-top: 0;
}
@supports (padding-top: constant(safe-area-inset-top)) {
.share__back {
top: calc(var(--safe-area-inset-top) + 12px);
}
}
@supports (padding-top: env(safe-area-inset-top)) {
.share__back {
top: calc(var(--safe-area-inset-top) + 12px);
}
}
@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3){
.share__body {
padding-top: 106px;
}
}
@media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {
.share__body {
padding-top: 106px;
}
}
活好不好,就看这下了。
CSS的条件特性还有另一个作用,可以用来检测浏览器是否支持CSS的属性。比如我们要检测浏览器是否支持display: grid,就可以使用@supports()来做检测。在这次项目中,我们也有使用这方面的特性。比如下面这样的一个效果:
注意 input 框中的光标,它是红色的,是红色的,是红色的。重要的事情说三遍。
在CSS中,我们现在可以使用caret-color属性来控制光标颜色了,只不过该属性有些浏览器是不支持了。面对这样的场景,我们就可以使用@supports函数来做判断。在支持的浏览器中使用caret-color属性,在不支持的浏览器上,我们则使用text-shadow来模拟:
.modal__control {
appearance: none;
text-shadow: 0 0 0 #666; /* 文本颜色 * /
color: #ff0036; /* 光标颜色 */
-webkit-text-fill-color: transparent;
}
@supports (caret-color: red) {
.modal__control {
color: #666; /* 文本颜色 * /
caret-color: #ff0036; /* 光标颜色 */
}
}
如果你对 CSS 条件样式相关内容感兴趣,可以移步阅读:
如何让图片更好的适配于不同的容器
先来上一个需求的截图:
对于上图示意的需求,用户头像效果都是类似的,不同的是在不同宽度容器中,头像容器大小有所不同。前端拿到的数据是相同的(用户头像和头像框是固定的一个尺寸)。
不希望在不同的容器下获取不一样的尺寸
在这个前置条件下,我们前端怎么通过CSS让图像能在不同容器中等比缩放呢?
有些同学可能会说,background-size就可以,只不过background-size是用来控制背景图片的,对于img的话不太适用。这里要说的是,我们不应该把object-fit遗忘了,因为object-fit就可以很好的帮助我们处理图片根据容器进行缩放:
比如:
.media__object img {
object-fit: contain;
object-position: center;
}
如何将艺术字带到Web中
像下图这样,用到艺术字体的场景,以往都是依赖于图片来处理:
使用图片除了可扩展性不好之外,还额外增加资源的加载,应用运营变更也不方便。虽然说CSS的 @font-face可以加载字体实现,但有一个致命的问题是,中文字体包太大:
就比如上图,为了那么几个艺术字,要加载近3M的字体文件,得不尝失。如果说有方案可以根据我们需要加载的文本而降低字体文件包的大小,那么是不是一个不错的选择。在社区中有很多类似的工具可以帮助我们按需加载需要的文案。比如 字蛛就是一个很不错的中文字体压缩器。
使用字蛛非常的容易,执行下面的命令全局安装字蛛:
npm install font-spider -g
你可以在你的本地创建一个项目(任何一个项目,只要你喜欢)。并且把你需要字体文件放到该目录下同时创建一个index.html文件。并且将下面的样式添加到该文件中:
<style>
@font-face {
font-family: 'fzzyk';
src: url('./FZZYK.eot');
src:
url('./FZZYK.eot?#font-spider') format('embedded-opentype'),
url('./FZZYK.woff') format('woff'),
url('./FZZYK.TTF') format('truetype'),
url('./FZZYK.svg') format('svg');
font-weight: normal;
font-style: normal;
}
h1{
font-family: fzzyk;
}
</style>
同时在<h1>标签中输入你将会用到的文字。
保存好之后,在你的命令终端执行类似下面这样的命令:
font-spider *.html
这样可以根据你在<h1>输入的文字生成新的字体包,从而减少原始字体包的大小,而且也不会影响具体的使用:
从上图中你可以看到压缩后的字体文件大小。这个时候你只需要在使用到艺术字体的地方,将font-family设置成@font-face中显式声明的字体,比如此例中是 fzzyk。 设置该字体的方案就是设计师所需要的艺术字体:
你可以运用于font相关属性,达到你自己想要的效果,比如改变颜色使用color、改变大小使用font-size,粗线可以使用font-weight等。
如果你在项目中采用该方案,那么记得使用font-display属性,你可以选择swap。它不仅提供了自定义字体和内容的可访问性之间的最佳平衡,它还提供了和使用JavaScript脚本相同的字体加载行为。如果你在页面上有想要加载的字体,但是最终也可以不加载,这时你就可以考虑使用fallback或者optional作为font-display的值。
除了上面这种方案之外,我们还可以将需要使用到的文字由设计师设计成矢量图。简单地说,将每个文字设计成一个独立的矢量图,以SVG的方式将每个文字导出成独立的 .svg 文件,然后使用前面提到的SVG Sprites技术,通过 <use> 来调用。虽然这是一个可行性方案,但成本相对来说是较高的。不太建议使用。
小结
最后非常感谢大家花时间阅读这篇文章,希望大家能有所收获。

如果你觉得该教程对你有所帮助,请给我点个赞。要是你喜欢 CSS ,或者想进一步了解和掌握 CSS 相关的知识,请关注我的专栏,或者移步阅读下面这些系列教程: