vw 适配方案
严格来说,使用 rem 进行页面适配其实是一种 hack 手段,rem 单位的初衷本身并不是用来进行移动端页面宽度适配的。
到了今天,有了一种更好的替代方案,使用 vw 进行适配 。
百分比适配方案的核心需要一个全局通用的基准单位,rem 是不错,但是需要借助 Javascript 进行动态修改根元素的 font-size,而 vw/vh(vmax/vmin) 的出现则很好弥补 rem 需要 JS 辅助的缺点。
根据 CSS Values and Units Module Level 4:vw等于初始包含块(html元素)宽度的1%,也就是
- 1vw 等于 window.innerWidth 的数值的 1%
- 1vh 等于window.innerHeight 的数值的 1%
再以上面设计稿图的元素为例,那么,
- 元素的宽度为:209/375 = 55.73% = 55.73vw
- 元素的高度为:80/375 = 21.33% = 21.33vw
- 元素的上左右边距依次计算...
根据相关的测试,可以使用 vw 进行长度单位的有:
- 容器大小适配,可以使用 vw
- 文本大小的适配,可以使用 vw
- 大于 1px 的边框、圆角、阴影都可以使用 vw
- 内距和外距,可以使用 vw
简单的一个页面,看看效果,完全是等比例缩放的效果:
CodePen Demo(移动端打开):使用 vw 进行页面适配
自动转换插件
当我们使用 rem 作为长度单位的时,通常会有借助 Sass/Less 实现一个转换函数,像是这样:
// 假设设计稿的宽度是 375px,假设取设计稿宽度下 1rem = 100px $baseFontSize: 100; @function px2rem($px) { @return $px / $baseFontSize * 1rem; }
同理,在 vw 方案下,我们只需要去改写这个方法:
// 假设设计稿的宽度是 375px @function px2rem($px) { @return $px / 375 * 100vw; }
当然,我们还可以借助一些插件包去实现这个自动转换,提高效率,譬如 postcss-px-to-viewport
vw polyfill
vw 现在毕竟还是存在兼容问题的,看看兼容性:
其实已经覆盖了绝大部分设备,那么如果业务使用了且又真的出现了兼容问题,应该怎么处理呢?有两种方式可以进行降级处理:
- CSS Houdini:通过CSS Houdini针对vw做处理,调用CSS Typed OM Level1 提供的 CSSUnitValue API。
- CSS Polyfill:通过相应的Polyfill做相应的处理,目前针对于 vw 单位的 Polyfill 主要有:vminpoly、Viewport Units Buggyfill、vunits.js和 Modernizr
对于 vw 方案的一些总结
vw 确实看上去很不错,但是也是存在它的一些问题:
- 也没能很好的解决 1px 边框在高清屏下的显示问题,需要自行处理
- 由于 vw 方案是完全的等比缩放,在完全等比还原设计稿的同时带来的一个问题是无法很好的限定一个最大最小宽度值,由于 rem 方案是借助 Javascript 的,所以这一点 rem 比 vw 会更加的灵活
当然,两个方案现阶段其实都可以使用甚至一起搭配使用,更多详情可以读读:
1px线
上面说到使用 vw 适配屏幕大小方案,其中有一个缺点就是在 Retina 屏下,无法很好的展示真正的 1px 物理像素线条。
设计师想要的 retina 下 border: 1px,其实是 1 物理像素宽,而不是 1 CSS 像素宽度,对于 CSS 而言:
- 在 dpr = 1 时,此时 1 物理像素等于 1 CSS 像素宽度;
- 在 dpr = 2 时,此时 1 物理像素等于 0.5 CSS 宽度像素,可以认为 border-width: 1px 这里的 1px 其实是 1 CSS像素宽度,等于 2 像素物理宽度,设计师其实想要的是 border-width: 0.5px;
- 在 dpr = 3 时,此时 1 物理像素等于 0.33 CSS 宽度像素,设计师其实想要的是 border: 0.333px
然而,并不是所有手机浏览器都能识别 border-width: 0.5px,在 iOS7 以下,Android 等其他系统里,小于 1px 的单位会被当成为 0px 处理,那么如何实现这 0.5px、0.33px 呢?
这里介绍几种方法:
- 渐变实现
- 使用缩放实现
- 使用图片实现(base64)
- 使用SVG实现(嵌入 background url)
图片适配及优化
图像通常占据了网页上下载资源绝的大部分。优化图像通常可以最大限度地减少从网站下载的字节数以及提高网站性能。
通常可以,有一些通用的优化手段:
- 消除多余的图像资源
- 尽可能利用 CSS3\SVG 矢量图像替代某些光栅图像
- 谨慎使用字体图标,使用网页字体取代在图像中进行文本编码
- 选择正确的图片格式
- 为不同 DPR 屏幕提供最适合的图片尺寸
本文重点关注如何在不同的 dpr 屏幕下,让图片看起来都不失真。
首先就是上述的第二点,尽可能利用 CSS3\SVG 矢量图像替代某些光栅图像。某些简单的几何图标,可以用 CSS3 快速实现的图形,都应该尽量避免使用光栅图像。这样能够保证它们在任何尺寸下都不会失真。
其次,实在到了必须使用光栅图像的地步,也是有许多方式能保证图像在各种场景下都不失真。
无脑多倍图
在移动端假设我们需要一张 CSS 像素为 300 x 200 的图像,考虑到现在已经有了 dpr = 3 的设备,那么要保证图片在 dpr = 3 的设备下也正常高清展示,我们最大可能需要一张 900 x 600 的原图。
这样,不管设备的 dpr 是否为 3,我们统一都使用 3 倍图。这样即使在 dpr = 1,dpr = 2 的设备上,也能非常好的展示图片。
当然这样并不可取,会造成大量带宽的浪费。现代浏览器,提供了更好的方式,让我们能够根据设备 dpr 的不同,提供不同尺寸的图片。
srcset 配合 1x 2x 像素密度描述符
简单来说,srcset 可以根据不同的 dpr 拉取对应尺寸的图片:
<div class='illustration'> <img src='illustration-small.png' srcset='images/illustration-small.png 1x, images/illustration-big.png 2x' style='max-width: 500px'/> </div>
上面 srcset 里的 1x,2x 表示 像素密度描述符,表示
- 当屏幕的 dpr = 1 时,使用 images/illustration-small.png 这张图
- 当屏幕的 dpr = 2 时,使用 images/illustration-big.png 这张图
srcset 属性配合 sizes 属性 w 宽度描述符
上面 1x,2x 的写法比较容易接受易于理解。
除此之外,srcset属性还有一个 w 宽度描述符,配合 sizes 属性一起使用,可以覆盖更多的面。
以下面这段代码为例子:
<img sizes = “(min-width: 600px) 600px, 300px" src = "photo.png" srcset = “photo@1x.png 300w, photo@2x.png 600w, photo@3x.png 1200w, >
解析一下:
sizes = “(min-width: 600px) 600px, 300px" 的意思是,如果屏幕当前的 CSS 像素宽度大于或者等于 600px,则图片的 CSS 宽度为 600px,反之,则图片的 CSS 宽度为 300px。
也就是 sizes 属性声明了在不同宽度下图片的 CSS 宽度表现。这里可以理解为,大屏幕下图片宽度为 600px,小屏幕下图片宽度为 300px。(具体的媒体查询代码由 CSS 实现)
这里的 sizes 属性只是声明了在不同宽度下图片的 CSS 宽度表现,而具体使图片在大于600px的屏幕上展示为600px宽度的代码需要另外由 CSS 或者 JS 实现,有点绕。
srcset = “photo@1x.png 300w, photo@2x.png 600w, photo@3x.png 1200w 里面的 300w,600w,900w 叫宽度描述符。怎么确定当前场景会选取哪张图片呢?
1. 当前屏幕 dpr = 2 ,CSS 宽度为 375px。
当前屏幕 CSS 宽度为 375px,则图片 CSS 宽度为 300px。分别用上述 3 个宽度描述符的数值除以 300。
- 300 / 300 = 1
- 600 / 300 = 2
- 1200 / 300 = 4
上面计算得到的 1、 2、 4 即是算出的有效的像素密度,换算成和 x 描述符等价的值 。这里 600w 算出的 2 即满足 dpr = 2 的情况,选择此张图。
2. 当前屏幕 dpr = 3 ,CSS 宽度为 414px。
当前屏幕 CSS 宽度为 414px,则图片 CSS 宽度仍为 300px。再计算一次:
- 300 / 300 = 1
- 600 / 300 = 2
- 1200 / 300 = 4
因为 dpr = 3,2 已经不满足了,则此时会选择 1200w 这张图。
3. 当前屏幕 dpr = 1 ,CSS 宽度为 1920px。
当前屏幕 CSS 宽度为 1920px,则图片 CSS 宽度变为了 600px。再计算一次:
- 300 / 600 = .5
- 600 / 600 = 1
- 1200 / 600 = 2
因为 dpr = 1,所以此时会选择 600w 对应的图片。
具体的可以试下这个 Demo:CodePen Demo -- srcset属性配合w宽度描述符配合sizes属性
此方案的意义在于考虑到了响应性布局的复杂性与屏幕的多样性,利用上述规则,可以一次适配 PC 端大屏幕和移动端高清屏,一箭多雕。
了解更多细节,推荐看看:
字体适配方案
字体大小
字体是很多前端开发同学容易忽略的一个点,但是其中也是有很多小知识点。
首先要知道,浏览器有最小字体限制:
- PC上最小 font-size=12px
- 手机上最小 font-size=8px
如果小于最小字体,那么字体默认就是最小字体。
其次,很多早期的文章规范都建议不要使用奇数级单位来定义字体大小(如 13px,15px...),容易在一些低端设备上造成字体模糊,出现锯齿。
字体的选择展示
在字体适配上面,我们需要从性能和展示效果两个维度去考虑。
完整的一个字体资源实在太大了,所以我们应该尽可能的使用用户设备上已有的字体,而不是额外去下载字体资源,从而使加载时间明显加快。
而从展示效果层面来说,使用系统字体能更好的与当前操作系统使用的相匹配,得到最佳的展示效果。所以我们在字体使用方面,有一个应该尽量去遵循的原则,也是现在大部分网站在字体适配上使用的策略:
使用各个支持平台上的默认系统字体。
兼顾各个操作系统
常见的操作系统有 Windows、Windows Phone、Mac OS X、iPhone、Android Phone、Linux。当然对于普通用户而言,无须关注 Linux 系统。
下面就以 CSS-Trick 网站最新的 font-family 为例,看看他们是如何在字体选择上做到适配各个操作系统的
{ font-family: system-ui,-apple-system,BlinkMacSystemFont,segoe ui,Roboto, Helvetica,Arial, sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol; }
font-family 关键字
对于 CSS 中的 font-family 而言,它有两类取值。
- 一类是类似这样的具体的字体族名定义:font-family: Arial 这里定义了一个具体的字体样式,字体族名为 Arial;
- 一类是通用字体族名,它是一种备选机制,用于在指定的字体不可用时给出较好的字体,类似这样:font-family: sans-serif 。
其中,sans-serif 表无衬线字体族,例如, "Open Sans", "Arial" "微软雅黑" 等等。
关于通用字体族名,在 CSS Fonts Module Level 3 -- Basic Font Properties 中,定义了 5 个,也就是我们熟知的几个通用字体族名:
- serif 衬线字体族
- sans-serif 非衬线字体族
- monospace 等宽字体,即字体中每个字宽度相同
- cursive 草书字体
- fantasy 主要是那些具有特殊艺术效果的字体
新增通用字体族关键字
而在 CSS Fonts Module Level 4 -- Generic font families 中,新增了几个关键字:
- system-ui 系统默认字体
- emoji 用于兼容 emoji 表情符号字符
- math 适用于数学表达式
- fangsong 此字体系列用于中文的(仿宋)字体。
我们看看用的最多的 system-ui。
system-ui
简单而言,font-family: system-ui 的目的就是在不同的操作系统的 Web 页面下,自动选择本操作系统下的默认系统字体。
默认使用特定操作系统的系统字体可以提高性能,因为浏览器或者 webview 不必去下载任何字体文件,而是使用已有的字体文件。 font-family: system-ui 字体设置的优势之处在于它与当前操作系统使用的字体相匹配,对于文本内容而言,它可以得到最恰当的展示。
San Francisco Fonts
OK,简单了解了 system-ui 字体族。但是像 -apple-system、BlinkMacSystemFont 没有在最新的标准里出现。它们又代表什么意思呢?
在此之前,先了解下 San Francisco Fonts 。
San Francisco Fonts 又叫旧金山字体,是一款西文字体。随着 iOS 9 更新面世,在 WatchOS 中随 Apple Watch 一起悄然发售,并且还将在 Apple TV 上的新 tvOS 中使用。
San Francisco Fonts 在 iOS 系统上用于替代升级另外一款西文字体 Helvetica Neue。Apple 做了一些重要的改变,使其成为平台上更好的, 甚至是完美的西文字体。
-apple-system/BlinkMacSystemFont
话说回来。正如每个前端开发人员都知道的那样,将一个功能纳入规范是一回事,将其纳入浏览器又是另一回事。
幸运的是,system-ui 的普及很快。 Chrome 和 Safari 都可以在各种平台上完全支持它。只有 Mozilla 和 Windows 相对落后。
看看 system-ui 的兼容性,Can i Use -- system-ui(图片截取日 2019-08-13):
仔细看上图的最后两行:
- Supported as the -apple-system value (only on macOS and iOS)
- Supported as the BlinkMacSystemFont value (only on macOS)
考虑到不同平台及向后兼容,在 macOS 和 iOS 上,我们需要使用 -apple-system 及 BlinkMacSystemFont 来兼容适配 system-ui 标准。
Segoe UI
Segoe UI 是 Windows 从 Vista 开始的默认西文字体族,只有西文,不支持汉字,属于无衬线体。
它也表示一个系列而不是某一款单一字体。使用 font-family: Segoe UI 可以在 Windows 平台及 Windows Phone 上选取最佳的西文字体展示。
Roboto
Roboto 是为 Android 操作系统设计的一个无衬线字体家族。Google 描述该字体为“现代的、但平易近人”和“有感情”的。
这个字体家族包含Thin、Light、Regular、Medium、Bold、Black六种粗细及相配的斜体。
总结一下
到此,我们可以总结一下了。以 CSS-Tricks 网站的 font-family 定义为例子:
{ font-family: system-ui,-apple-system,BlinkMacSystemFont,segoe ui,Roboto, Helvetica,Arial, sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol; }
- system-ui,使用各个支持平台上的默认系统字体
- -apple-system, 在一些稍低版本 Mac OS X 和 iOS 上,它针对旧版上的 Neue Helvetica 和 Lucida Grande 字体,升级使用更为合适的 San Francisco Fonts
- BlinkMacSystemFont,针对一些 Mac OS X 上的 Chrome 浏览器,使用系统默认字体
- segoe ui,在 Windows 及 Windows Phone 上选取系统默认字体
- Roboto,面向 Android 和一些新版的的 Chrome OS
- Helvetica,Arial,在针对不同操作系统不同平台设定采用默认系统字体后,针对一些低版本浏览器的降级方案
- sans-serif,兜底方案,保证字体风格统一,至少也得是无衬线字体
上述 5 个字体族定义,优先级由高到底,可以看到,它们 5 个都并非某个特定字体,基本的核心思想都是选择对应平台上的默认系统字体。
涵盖了 iOS、MAC OS X、Android、Windows、Windows Phone 基本所有用户经常使用的主流操作系统。
使用系统默认字体的主要原因是性能。字体通常是网站上加载的最大/最重的资源之一。如果我们可以使用用户机器上已有的字体,我们就完全不需要再去获取字体资源,从而使加载时间明显加快。
并且系统字体的优点在于它与当前操作系统使用的相匹配,因此它的文本展示必然也是一个让人舒适展示效果。
当然,上述 font-family 的定义不一定是最佳的。譬如天猫移动端在 font-family 最前面添加了 "PingFang SC",miui,..必定也有他们的业务上的考虑。但是一些 fallback 方案向后兼容的思想都是一致的,值得参考学习。
更多的关于字体方面的细节知识,可以看看这几篇文章:
- 你该知道的字体 -- font-family
- Web 字体 font-family 再探秘
- Using UI System Fonts In Web Design: A Quick Practical Guide
- System Fonts in CSS
- Apple’s San Francisco Font
前端布局的兼容适配
前端工程师的一大工作内容就是页面布局。无论在PC端还是移动端,页面布局的兼容适配都是重中之重。在整个前端发展的历程中,布局的方法也在不断的推陈出新。
布局发展历程
简单来说,前端的布局发展历程经历了下面几个过程:
表格布局 --> 定位布局 --> 浮动布局 --> flexbox布局 --> gridbox布局
每一种布局在特定时期都发挥了重要的作用,而每一种新的布局方式的出现,往往都是因为现有的布局方式已经在该时期已经无法很好的满足开发者的需求,无法满足越来越潮流的页面布局的方式。
以 Flexbox 的出现为例子,在 Flexbox 被大家广为接受使用之前。我们一直在使用定位+浮动的布局方式。像下面这个布局:
容器宽度不定,内部三个元素,均分排列且占满整个空间,并且垂直居中。如果使用定位+浮动的布局方式,你无法很快想到最佳的解决方式。三个元素并排那么必然需要浮动或者绝对定位,容器宽度不定且中间元素始终居中,需要顾虑的方面就很多了。也许使用 text-align: justufy 可以 hack 实现,等等等等。
然而,使用 flexbox 布局的话,只需要:
.container { display: flex; justify-content: space-between; align-items: center; }
flexbox 的出现,一次性解决了流动布局,弹性布局,排列方式等多个问题。并且它是简洁的,可控的。
再来看一个例子,水平垂直居中一个元素。使用 flexbox 也许是最便捷的:
.container { display: flex; } .item { margin: auto; }
CSS Grid Layout
OK,flexbox 已经足够优秀了,为什么 gird 网格布局的出现又是为什么?它解决了什么 flex 布局无法很好解决的问题?
看看下面这张图:
flexbox 是一维布局,他只能在一条直线上放置你的内容区块;而grid是一个二维布局。它除了可以灵活的控制水平方向之外,还能轻易的控制垂直方向的布局模式。对于上图那样的九宫格布局,它就可以轻而易举的完成。
一图以蔽之,flexbox:
图片截取自陈慧晶老师在 2019 第五届 CSS 大会上的分享 -- 新时代CSS布局
在现阶段,移动端布局应当更多使用 flexbox 去完成(相对那些还在使用 float 布局的),而考虑到未来页面布局的推陈出新。对于 Grid 布局我们应当像前几年对待 flexbox 一样,重视起来,随着兼容性的普及,Grid 布局也会慢慢成为主流。
最后
好了,本文到此结束,希望对你有帮助 :)
更多精彩技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。
如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。
Update commentCancel