YuyingWu

YuyingWu 查看完整档案

杭州编辑华南理工大学  |  软件工程 编辑阿里巴巴  |  前端开发 编辑 wuyuying.com 编辑
编辑

前端爱好者 / 鼓励师

个人动态

YuyingWu 回答了问题 · 2018-08-11

解决云凤蝶 这种鼠标经过显示二维码 是如何实现的

这样的交互做法有很多。

类似案例的那种,hover上去,原本隐藏的子元素qrcode-div的opacity从0到1,就展现了。
建议楼主可以直接打开控制台,看看人家是怎么做的哈,跟踪一下相关元素的样式就好了。

其他能实现的方式:

一、默认渲染二维码的div

  1. 默认样式opacity:0,hover的样式 opacity:1
  2. 默认样式display:none,hover的样式 display: block

二、方块的div的内容,默认展示原来的内容(案例图片+描述),hover时把div的内容替换成二维码的模板(不过这种方式会反复修改dom节点)

关注 3 回答 2

YuyingWu 赞了文章 · 2018-08-03

GMTC 大前端时代前端监控的最佳实践

摘要: 今天我分享的内容分成三个部分: 第一部分是“大前端时代前端监控新的变化”, 讲述这些年来,前端监控一些新的视角以及最前沿的一些思考。 第二部分"前端监控的最佳实践", 从使用的角度出发,介绍前端监控系统的各种使用姿势 最后是“阿里云ARMS前端监控系统架构”, 简单地剖析下,阿里云前端监控系统是怎么实现的。

本文来自阿里云前端监控团队,转载请注明出处

本文为2018年6月21日,在北京举办的GMTC(全球大前端技术大会),下午性能与监控专场,由阿里云前端监控团队前端技术专家彭伟春带来的演讲稿,现场反馈效果非常好,地上都坐了三圈,很多人反馈根本无法挤进去。先上现场照。

图片描述

正文从这里开始~

图片描述

大家下午好,今天我给大家带来的主题是《大前端时代前端监控的最佳实践》。

图片描述

先做一个自我介绍,我叫彭伟春,英文名是Holden, 阿里花名是六猴, 大家都叫我猴哥。是阿里开源同构框架beidou的作者,目前是阿里云前端系统技术负责人。

图片描述

今天我分享的内容分成三个部分:

  • 第一部分是“大前端时代前端监控新的变化”, 讲述这些年来,前端监控一些新的视角以及最前沿的一些思考。
  • 第二部分"前端监控的最佳实践", 从使用的角度出发,介绍前端监控系统的各种使用姿势。
  • 最后是“阿里云ARMS前端监控系统架构”, 简单地剖析下,阿里云前端监控系统是怎么实现的。

图片描述

图片描述

先进入我们第一个环节 大前端时代前端监控新的变化。
要了解前端监控新的变化,还得先看看前端这些年发生了哪些变化:

  • 首先是Gmail的横空出世,开启了SPA的时代
  • Backbone/Angular等框架带来了MVVM模式的同时,也把JS从脚本语言提升到了工程语言
  • React Native/Weex把移动端开发从Hybrid模式进化到了跨端开发模式
  • Node.js问世为前端带来了更多的可能性

图片描述

前端这些年发生了翻天覆地的变化,又会给监控带来什么呢?让我们思考下以下几个问题:

  • 传统监控模式能否适用于新的技术?比如PV统计
  • SPA模式下首屏如何计算?
  • 跨端开发给监控带来什么什么挑战?
  • 前端监控的上报模式在Node.js端是否合理?

接下来我和大家一起探讨其中的一两项

图片描述

早些年,SPA如此盛行,我们也在业务中做了尝试,体验是大幅提升了,可业务方却吐槽PV下降了。

图片描述

那到底是什么导致了PV下降了呢?在后端直出时代,我们每一次的交互,都是向后端请求一个新的页面,PV自然就高,改成SPA模式之后,大量的页面请求变成了页内路由,或者说是页内转场。那如何解呢?这难不倒我们,大部分框架路由都是基于哈希实现的,我们只要侦听hash改变,每次改变上报一次PV就好了。也有少量的路由并不是基于哈希实现的,比如angular, 这时候就需要轻量级地hack pushState和replaceState。

图片描述

这样就完美了吗?

图片描述

我们再思考下以下几个案例

  • 某新闻类的网站,每次看完之后,都会下拉刷新,加载新的内容,这个时候是算一次PV还是多次?
  • 天猫商品列表页,看完一屏之后,向上滚动会再加载新的一屏,PV该算一次还是多次?
  • 阿里云邮后台一直开着,每周上百次查看,是算一个PV还是每次查看都计算一次?
  • 未关闭的浏览器tab几小时之后再次浏览,该不该再计一次PV?
  • 查找信息时,浏览器Tab之间快速切换,切换过程中要不要计一次PV?

其实还有很多其它层出不穷的场景,具体该如何去统计PV留给大家去思考, 不再展开。

图片描述

接下来我们探讨一个大家最感兴趣的话题: 性能。先看一组我们的统计数据,淘宝旺铺页面点击率随加载时间变长从85%的点击率逐步降低到了82%,别小看这3%,在阿里这么大的体量下,3%意味着巨大的商业价值,那站在前端监控的角度,首屏是如何统计出来的呢?

图片描述

回到那个刀耕火种的年代,那时候要什么没什么,都是自己动手丰衣足食。这就是手动打点阶段: 手动打点,分别在页头和首屏dom节点处new Date()打点,计算差值,作为首屏时间,再加上setTimeout(new Date(), 0)标记首屏可交互时间。

图片描述

随着前端的飞速发展,手工打点的模式早已满足不了需求了。为了帮助开发人员更好地衡量和改进web性能,W3C性能小组引入了 Navigation Timing API 帮我们自动,精准的实现了性能测试的打点问题,大致地过一下,性能API里面包含了【卸载上一个页面】【重定向】【应用缓存】【DNS域名解析】【TCP连接】【请求页面】【响应】【页面处理】最后触发load事件,通常我们把domContentLoaded作为首屏时间。Chrome最早支持,IE跟进。

图片描述

在很长一段时间里,我们都享受着performance API带来的便利, 但随着SPA模式的盛行,我们再回过头来看看W3C标准是否足够了。先来看一个案例,这是阿里云某产品的管理后台。整个加载过程分成三个部分,1. 加载初始的空壳页面 2.加载JS资源并异步请求数据 3. 前端渲染中间的主体部分。按照W3C标准取值首屏时间应该是1106ms, 而实际的首屏在1976ms,也就是完成异步取数据后渲染完页面的时间点。为什么会相差如此大呢?实际上SPA的盛行让W3C标准失去了原来的意义。

图片描述

针对这种情况Google lighthouse提出了FMP的概念,first meaning paint, 也就是主要内容可见时间,那什么是主要内容? 每个人得出的结论可能会不一样。

图片描述

先做一个猜想:主要内容 = 页面渲染过中元素增量最大的点。

图片描述

先通过飞猪案例做一次验证。

图片描述

猜想成立。

图片描述

再通过手淘案例做一次验证。

图片描述

猜想不成立。

图片描述

那到底是什么原因导致我们的猜想不成立?

  • 首先是元素是否可见, 不可见的元素对用户的影响基本为0。
  • 其次是每个元素对页面的影响是否等效?由此引出权重,不同的元素采用不同的权重计算影响。阿里云前端监控

图片描述

根据上面的修正因子。我们重新设计了一遍算法, 计算每次变化的得分,一起来看看,算法是如何实现的?
如图所示分为三个步骤

  • 侦听页面元素的变化;
  • 遍历每次新增的元素,并计算这些元素的得分总;
  • 如果元素可见,得分为 1 * weight(权重), 如果元素不可见,得分为0;

如果每次都去遍历新增元素并计算是否可见是非常消耗性能的。实际上采用的是深度优先算法,如果子元素可见,那父元素可见,不再计算。 同样的,如果最后一个元素可见,那前面的兄弟元素也可见。通过深度优先算法,性能有了大幅的提升。

图片描述

再拿之前的手淘案例来验证一遍。

图片描述

经过改良之后,第三屏主要内容的得分是最高的,符合预期。

图片描述

那么接下来首屏统计又会发生什么样的变化呢?其实统计首屏时间本身就是浏览器的职责,交由浏览器来处理是最好的。目前W3C关于首屏统计已经进入了提议阶段,坐等W3C再次标准化。大家可以在github上看到最新进。

限于篇幅,前端监控其它新的变化不再展开。讲了这么多前端监控的新变化,那什么才是打开前端监控最最正确地姿势呢?

图片描述

由此进入我们的第二个环节,“前端监控的最佳实践”。

图片描述

我用一个表达式“要是什么什么就好了”来总结。我经常会想【要是天上能掉钱就好了】,【要是有个机器人帮我写代码就好了】。同样的,每次发版之后都是提醒吊胆的,不知道用户到底能不能正常使用。(这时候你就会想)要是能有双眼睛帮我盯着系统就好了;每次出错,都是用户投诉反馈问题,实际等到用户主动反馈问题,影响面已经非常大了: (这时候你就会想)要是能在第一时间发现错误就好了;

图片描述

还真有这样的案例,前年双十一凌晨值班,突然收到邮件和短信告警,于是点开了详情。

图片描述

发现在接口成功率趋势图中,接口请求量大幅上升,伴随着成功率急剧下降,再查看错误信息聚合模块,发现频率最高的错误信息是【交易规则冲突】,深度排查之后,最终找出了原因,是运营配置的双十一优惠规则和平时优惠规则产生了冲突,导致下单失败。最后凌晨4点申请了紧急发布修复了冲突,解除告警。

图片描述

由此可以得出最佳实践之一:主动监控。当然主动监控的内容不仅局限于API成功率,也包括JS错误率等。稍微总结下流程:先是配置告警规则; 然后就可以放心大胆地睡觉了,如有任何风吹草动,系统马上会通知到我们,再通过错误聚类模块,精准地定位问题。再手起刀落,bug修复完成。

图片描述

再回到我们的【要是什么什么就好了】,在做性能优化的时候,有时候明明整体性能已经不错了,可偏偏有少量用户觉得很慢:(这时候你就会想)要是能知道慢速用户发生了什么就好了。

图片描述

这时候我们就需要用到【性能样本分布】,打开页面性能页面,查看0 -60秒之间每个区间的性能样本分布情况,从分布图中可以看出来大部分用户加载时间都在2秒以内,极少数部分用户的页面时间在10秒左右的。

拖动下面的滑块,缩小时间范围到10S左右,这时候系统就会筛选出10秒左右的慢会话。

图片描述

点击展开某次慢会话,不仅可以看到这次慢会话的基本信息,比如网络类型等,还可以看到完整的资源加载瀑布图,可以清晰地看出来,具体是什么资源导致整个会话变慢。由此我们又可以得出最佳实践之二:慢会话追踪

图片描述

再回到我们的【要是什么什么就好了】,有时候用户提交了一条反馈,某某功能出错用不了,这时候你又不知道用户端到底报了什么错,是不是又得打电话给用户,还得手把手教用户如何通过浏览器开发者工具把错误截图下来发你。我哩个去,用户这个时候很可能因为系统太烂了,已经不堪其辱,早就把页面关了并且发誓再也不用这破系统。(这时候你就会想)要是能知道用户报了什么错就好了。

图片描述

别怕,打开阿里云前端监控的【访问明细】搜索用户ID,直接可以看到该用户在访问过程中,到底报了什么错。

图片描述

有时候拿到了用户报错时的基本信息,也知道用户报了什么错,但是在自己电脑上调试的时候,无论如何也复现不了,这个时候是不是又得去和用户沟通,让用户描述重现路径,实际上用户可能自己都忘了具体怎么做才能重现错误。(这时候我们就会想)要是能重现用户行为就好了。

图片描述

【视频演示】我们现场来模拟一次用户出错还原,左边是用户实际操作的屏幕,为了更好地展示效果,我把用户行为实时地展示在右边的屏幕上:

第一步: 模拟用户在淘宝页面上做出了一系列的操作, 鼠标移动、滚动页面,搜索等;
第二步:假设突然出现了某某错误,这时系统会把记录的用户行为存储到服务端;
第三步: 开发人员通过会话ID查询到出错行为,最终进行还原。大家可以看到左边屏幕不再操作,右边屏幕还原出了之前出错的所有行为。

图片描述

大家一定在想这么炫酷的能力是如何实现的呢?接下来就为大家揭秘阿里云前端监控系统背后的技术架构。

图片描述

就从大家最感兴趣的错误还原讲起,大家可能在猜测,是不是把整个页面录制成视频了。其实不是这样的,视频太大了,不可能出错了把一个视频发到服务端,这样是对用户资源的严重浪费。先看示意图(跟着箭头从左到右):

  • 首先,每一次会话都有一个唯一的session ID,这是串联起所有行为的纽带。
  • 其次,用户行为又分成两个部分,其一是用户的操作,比如鼠标滑动,点击,页面滚动等,其二是页面的变化。这两者我们都统称为用户行为,记录在同一个队列中。
  • 一开始的时候,系统会记录下初始的页面作为第一帧,这是唯一的一次完整页面记录。
  • 针对用户操作,我们会记录事件的类型,鼠标位置等关键信息,保存到队列中。
  • 针对页面变动,我们会起一个mutationObserve侦听页面的改动,每次只记录改动的部分,保存到队列中。
  • 无论是事件还是页面改动,都是对等的一帧,每一帧都会有当前时间,与上一帧间隔时间等基本信息用户还原。
  • 一旦出错,SDK就把队列发送到监控系统,并清空当前队列。
  • 还原端根据记录的行为队列,根据时间逐一播放出来。最终形成一个类似于视频的效果。

图片描述

大家可能觉得还不过瘾,接下来为大家讲一下阿里云ARMS前端监控系统的整体架构。
首先从左到右分成三个域。分别是日志采集域,日志分析域和监控告警域。在日志采集域,客户端通过SDK将信息上报到Nginx服务器, 日志服务SLS在Nginx服务器上起一个agent,去把日志信息同步过去,日志到了SLS就相当于到了一个大的蓄水池。再通过实时计算引擎的计算,结果部分存储到HBase,另一部分结果回存到SLS日志服务用于搜索。
最终通过restful API向前端提供数据,前端渲染出数据dashboard.
是不是感觉很简单地样子,有句话叫做【看山跑死马】,山看起来就在眼前, 可就算骑马过去马都会累死。那就让我们一起来揭开它的神秘面纱吧。

图片描述

接下来重点介绍跟前端同学工作密切相关的日志采集域,相比业界,我们的日志采集还是有很多可圈可点之处的。比如说:

  • 静默采集: 只需要一行代码接入SDK就行了,所有的API请求、资源加载、JS错误、性能等都自动监控起来了。省去了繁琐的配置。
  • 单元测试 +
    自动化测试:前端监控的目的就是去监控前端的异常情况,不给页面带来新的异常这是我们的底线,对此,我们有完善的单元测试和自动化测试去保障SDK本身的质量。
  • (SDK出错隔离):但实际上任何系统都不能保证自己不会出错,那么万一SDK本身报错了,我们还有异常隔离机制,确保出错也不会影响业务的运行。

图片描述

这些内容我都不详细展开,那接下来我重点讲一下,阿里云前端监控是如何突破局限优雅地上报日志

大家都知道,http徵求意見稿rfc2616规定浏览器对于一个域名,同时只能有 2 个连接。而PV、UV、ajax请求、JS逻辑错误、页面资源加载等等都会触发上报,同时2个连接明显不够用,可能会造成网络阻塞,上报延迟

后来在修正稿rfc7230中去掉了这个限制, 只规定了限制数量,但并未指定具体数字,浏览器也实际放宽了限制。比如Chrome是同时6个连接。

然而,一个请求独占一个连接,有时候6个连接也是不够用的
大家可能会想, 那既然规范都没有指定要限制多少条,那浏览器为什么还要限制6条呢?其实也是出于公平和安全考虑,如果不限制数量,理论上一个客户端就能占用大量服务器资源,甚至压垮服务器。

图片描述

那如何突破限制呢?有一个绝招:就是升级到http2, 利用h2的多路复用特性。
一个连接上打开多个流,还可以双向数据传输,轻松突破6路并行限制。

思考一下:在http1时代的把资源散列在不同域名下还有效吗?实际上非但不能提升性能,反而会新增连接开销。

图片描述

突破6路限制就够了吗?我们再来看看另一个很容易被忽略的部分:http头部损耗。

http请求中,每次请求都会包含一系列的请求头来描述请求的资源和特性等。而头部没经过任何压缩,每次请求都要占用200-800个字节,如果带上一个比较大的cookie,甚至会超过1K;
而我们实际的日志数据大小仅仅只有10 - 50字节,头部消耗占了90%以上;
另外,据Htpp Archive统计数据,平均每个页面上百个请求,越来越多的流量消耗在头部;
最致命的是,UserAgent等信息不会频繁变动,每次请求都传输是一种严重的浪费。

图片描述

再次利用h2头部压缩。先来看看采用h1和h2的效果对比。
h1下请求大小295 字节, 而h2仅仅只有18 字节,大小只有区区16分之一,请求时间也从6ms降低到了4毫秒。

图片描述

太神奇了,来快速地过一下http2头部压缩是如何实现的:

首先协议里预设了一个静态字典,用来表示常用的头部字段,比如图中,2就是 method get. 以前需要把完整的key-value对发过去,现在只需要把一个数字发过去,大小大幅缩小。

其次,客户端和服务端会共同维护一个动态表,动态表用来干啥呢?举个例子,比如useragent, 每个用户的useragent值是不一样的,没法放到静态表中去约定。但是对于同一个用户会话,useragent是不会改变,这样的值,就由客户端和服务端协商决定存入动态表,这样第一次传输过去之后,以后就只需要传入动态表中的一个编码就行了,图中的62和63就是这样的情况。连接中发送的请求越多,就越能丰富动态表中的值,越到后面,请求性能越好(佐证了域名散列的方式不可取)。

还有一类情况,值总是变来变去,也没法保存到动态表中。这时候,只能直接压缩了。在h2中采用的是Huffman压缩算法,能把数字或字符最短压缩到5个字节,最大压缩率是37.5%。

图片描述

其实除了头部压缩外,还有很多办法减少体积,比如

采用http 204返回无响应体的response;
采用post请求合并多条日志,共用请求头;
错误调用堆栈中经常会出现很多的文件url,占了不少空间,可以考虑将他们抽取成一个变量;

时间关系,日志采集部分就到此为止。

图片描述

接下来我们来看看一个监控系统最核心的部分:实时计算。

实时计算采用的是业界已经非常成熟的流计算,简单地过一下概念。
这是一张表示流计算的经典结构图,有两种组件,水龙头是spout,代表数据源, 闪电是bolt, 代表处理逻辑。这里面有两个很重要的特征。

其一是计算能力弹性,如果有更大的日志量流入,能够动态调度更多的算力来保障计算的实时性;

其二是反压。每个计算节点都可以根据自己的负载情况反压上一级的计算节点,从而实现计算任务的更合理地分配。

图片描述

思考一下:如何在海量日志中实时取到限定条件的聚合数据?如图所示,我想实时拿到【模拟页面】在【广东省】【最近24小时】【访问速度】走势。

分析一下,如果需要画出这样的走势图,每个小时画一个点,需要取24个点的值,每个节点写个SQL把符合条件的数据求平均,如果数据量很小的时候,取24次数据勉强性能上勉强可以忍受。

但是如果作为一个SASS系统,监控系统会接入非常多的项目,每时每刻都有大量的数据上报。系统也会积累海量的数据。取一个节点需要多少时间呢?参考离线计算大概要15分钟, 24个节点,预估需要6个小时。这明显是不可接受的。那阿里云前端监控是如何做到实时拿数据的呢?

图片描述

这就需要用到我们的大数据处理神器dataCube(数据立方),我们来剖析下数据立方是如何解决实时性的问题的。

如图所示: 拿浏览器、设备、地理区域三个维度为例,组成一个三维的数据立方。立方中的每个小格子代表一个聚合数据。

请看图中数字3所在的格子,3代表三维,也就是Vivo设备、chrome浏览器在北京地区的聚合量。
再看一个黄色切面上的数字2,黄色切面代表浏览器维度的聚合,也就是上海地区Vivo设备的聚合量,包括所有的浏览器。

再看最右下角的数字0代表0维,也就是所有的聚合量,包括所有的浏览器、所有的设备、所有的地区。

数据立方的秘密就是把所有格子的值都预先计算出来,下次要取值,直接取数据立方的某个值就好了,本质上是一种空间换时间的思路。

图片描述

看一个我们实际的处理场景,元数据经过流计算之后,每个每分钟、每小时、每天都会产生一个数据立方。而这个数据立方多达90多维。回到之前的案例,如果我想限定若干个条件拿到24小时趋势图,我只需要24个数据立方中把指定位置的小格子取出来就行了。计算时间就能大幅压缩到秒级别。

原文链接

查看原文

赞 7 收藏 5 评论 1

YuyingWu 回答了问题 · 2018-07-31

有哪些类似codesandbox.io可以在线编辑html代码并发布到线上的平台?

codepen用得比较多,上面还会有各种demo的collection,有的是实验性的demo,更有些youtube上的教程的实例代码。

譬如我的CSS variables的笔记,也会记录在上面,非常好用!

关注 3 回答 3

YuyingWu 发布了文章 · 2018-07-31

CSS自定义属性 —— 别说你懂CSS相对单位

图片描述

前段时间试译了Keith J.Grant的CSS好书《CSS in Depth》,其中的第二章《Working with relative units》,书中对relative units的讲解和举例可以说相当全面,看完之后发现自己并不太懂CSS相对单位,也希望分享给大家,所以有了这个译文系列。(若有勘误或翻译建议,欢迎 Github PR^_^

《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用
  4. 无单位数字和行高
  5. CSS自定义属性 [本文]

本文对应的章节目录:

  • 2.6 自定义属性(也叫“CSS变量”)

    • 2.6.1 动态改变自定义属性的值
    • 2.6.2 通过JavaScript改变自定义属性的值
    • 2.6.3 初探自定义属性
  • 总结
译者说
 
在今年年初,我也写过一篇《CSS Variables学习笔记》,里面有更多的CSS Variables的语法解释和实例demo,感兴趣的朋友可以看看 :)
 
本文也是《别说你懂CSS相对单位》系列的最后一篇,感谢小伙伴们的支持和建议,enjoy 🎉🎉🎉 意犹未尽的朋友们,可以去看看Keith J.Grant的《CSS in Depth》。
 

2.6 自定义属性(也叫“CSS变量”)

在2015年,一个大家期待已久的名为“用作层叠式变量的自定义属性”(Custom Properties for Cascading Variables)的CSS规范终于发布为“候选推荐标准”(Candidate Recommendation)。这套规范引入了CSS中“变量”的概念,支持一种新的基于上下文的动态样式定义方式。你可以声明一个变量,再给它赋值,然后就可以在样式表的任何地方引用它。你可以通过这样的方式,减少样式表中的重复代码,以及后续你会看到的一些有用的应用场景。

在写这本书的时候,自定义属性已经被大多数主流浏览器支持了,除了IE。查看最新的浏览器支持情况,可以查看Can I Use的http://caniuse.com/#feat=css-variables

笔记

如果你刚好在用支持自定义变量的CSS预处理器,如Sass(syntactically awesome stylesheets)或Less,你可能会下意识拒绝CSS变量。千万别这么做。因为原生的CSS变量比任何一个预处理器能实现的功能都要强大和灵活。为了强调它们之间(原生CSS变量和预处理器自定义变量)的差异,我会把它叫作“自定义属性”,而不用“CSS变量”。

声明一个自定义属性,跟声明其他属性类似。代码片段2.23是自定义属性声明的例子。新建一个页面和样式表吧,然后添加以下的CSS代码。

 
[ 代码片段 2.23 声明一个自定义属性 ]

:root {
  --main-font: Helvetica, Arial, sans-serif;
}

代码片段中,定义了一个名叫--main-font的变量,然后把它的值设定为普通的字体sans-serif。为了和其他属性区分开,命名的前缀必须是两道横杠(--),然后写上你想要的名字。

变量一定要声明在一个声明区块内。在这里,我使用了:root选择器,那么这个变量就可以在整个页面的样式里使用 —— 后面我会简单解释这个问题。

变量的声明,就它本身而言,不会做任何事情,直到我们在代码里引用它。我们在一个段落中使用它吧,做成像图2.13那样的效果。

 
[ 图 2.13 对一个简单段落使用用变量声明的字体sans-serif ]

"图 2.13"

我们可以用一个叫作var()的函数去引用自定义属性的值。现在,你可以利用这个函数去引用我们刚才声明的变量--main-font。把下面展示的代码片段添加到你的样式表中吧,把变量用起来。

 
[ 代码片段 2.24 使用一个自定义属性 ]

:root {
  --main-font: Helvetica, Arial, sans-serif;
}

p {                                    1
  font-family: var(--main-font);       1
}   
  • 1 把段落的字体定义为 Helvetica, Arial, sans-serif

 
自定义属性可以让你在一个地方声明它的值,作为一个“单一数据源”(single source of truth),然后在样式表的任意一个地方引用。这一点对一些反复出现的值特别有用,譬如颜色。下一个代码片段添加了一个名叫brand-color的自定义属性。你可以在样式表中多次使用这个变量,但假如你需要(全局)修改它的值,只需要在一行代码中编辑它的值就可以了。

 
[ 代码片段 2.25 对color使用自定义属性 ]

:root {
  --main-font: Helvetica, Arial, sans-serif;
  --brand-color: #369;                           1
}

p {
  font-family: var(--main-font);
  color: var(--brand-color);
}
  • 1 声明一个蓝色的brand-color变量

 
var()函数支持第二个参数,代表一个默认值。假如一个变量被声明的时候,第一个参数没有被声明,那么第二个参数值就会被引用。

[ 代码片段 2.26 提供回退默认值 ]

:root {
  --main-font: Helvetica, Arial, sans-serif;
  --brand-color: #369;
}

p {
  font-family: var(--main-font, sans-serif);         1
  color: var(--secondary-color, blue);               2
}
  • 1 声明一个默认值 sans-serif
  • 2 变量 secondary-color 没有被声明,于是默认值 blue 会被使用

 
这段代码在两个不同的声明中,定义了默认值。第一个声明里,--main-font被声明,值为Helvetica, Arial,sans-serif,于是这个值就会被用到了。第二个声明里,--secondary-color是一个没有声明过的变量,所以默认值 blue 被用到了。

 

笔记
如果var()被定义为一个无效值,这个属性会被定义为它的初始值。举个例子,如果在padding: var(--brand-color)中,变量是一个色号,那对于padding来说这就是一个无效值。在这个情况下,padding的值会被定义为0。

 

2.6.1 动态改变自定义属性的值

从这些例子可以看到,自定义属性只是更方便了一点,也可以帮助你减少很多的重复代码。但让自定义属性更有意思的是,自定义属性的声明是可以层叠和继承的。你可以在多个选择器中声明同一个变量,这些变量在页面的不同部分可以有着不一样的值。

你可以声明一个变量是黑色的,举个例子,然后在一个特定的容器里把它重新定义为白色的。于是,在这个容器以外的所有依赖这个变量的颜色是黑色,而在容器内的就是白色。通过这样的方式,我们来实现一个像图2.14这样的效果。

 
[ 图 2.14 自定义属性基于不同域下的值,生成两个颜色不一样的面板 ]

"图 2.14"

这个面板类似你之前看到的那个(图2.7),HTML在代码片段2.27。这个面板有两个实例,一个在body下,另一个在一个深色的区块。来,更新下你的代码。

 
[ 代码片段 2.27 页面上不同上下文的两个面板 ]

<body>
  <div class="panel">                               1
    <h2>Single-origin</h2>
    <div class="body">
      We have built partnerships with small farms
      around the world to hand-select beans at the
      peak of season. We then careful roast in
      small batches to maximize their potential.
    </div>
  </div>

  <aside class="dark">                              2
    <div class="panel">                             2
      <h2>Single-origin</h2>
      <div class="body">
        We have built partnerships with small farms
        around the world to hand-select beans at the
        peak of season. We then careful roast in
        small batches to maximize their potential.
      </div>
    </div>
  </aside>
</body>
  • 1 页面上一个普通的面板
  • 2 第二个面板在深色容器里

 
我们用变量重新改写一下面板中的文字和背景颜色。把下面的代码片段加进你的样式表。这里把背景颜色设成白色,文字颜色设成黑色。在你添加深色主题之前,我会解释这段代码的工作原理。

 
[ 代码片段 2.28 利用变量定义面板的颜色 ]

:root {
  --main-bg: #fff;                       1
  --main-color: #000;                    1
}

.panel {
  font-size: 1rem;
  padding: 1em;
  border: 1px solid #999;
  border-radius: 0.5em;
  background-color: var(--main-bg);      2
  color: var(--main-color);              2
}

.panel > h2 {
  margin-top: 0;
  font-size: 0.8em;
  font-weight: bold;
  text-transform: uppercase;
}
  • 1 分别把背景色和文字颜色定义为白色和黑色
  • 2 在面板样式中使用变量

 
你再一次把变量声明在:root选择器里。很明显,这样的话我们就可以在根元素(整个页面)下的任何元素中引用这个变量了。当根元素下的子元素使用这些变量时,它们就能拿到这些变量对应的值。

你有两个面板,不过它们仍然看起来是一样的。现在,再一次定义这些变量,但这次是在一个不同的选择器中。下一个代码片段是深色容器的,它有深灰色的背景色,以及小小的padding和margin。同时,它也重写了两个变量。添加到你的样式表吧。

 
[ 代码片段 2.29 设置深色容器的样式 ]

.dark {
  margin-top: 2em;                   1
  padding: 1em;
  background-color: #999;            2
  --main-bg: #333;                   3
  --main-color: #fff;                3
}
  • 1 在深色容器和上一个容器间设定一个margin
  • 2 给深色容器设定深灰色的背景色
  • 3 在当前容器的作用域下,重新定义--main-bg 和 --main-color的值

 
刷新页面,第二个面板就会有深色背景和白色文字。这是因为当这个面板去调用这些变量时,拿到的是深色容器作用域下的值,而不是根元素域下的值。注意,你并不需要修改这个容器里的样式或者添加额外的类名。

在这个例子里,你两次定义了自定义属性,第一次在根元素作用域上(--main-color是黑色的),第二次在深色容器作用域(--main-color是白色的)。自定义属性表现得像作用域变量,因为值会被后代元素继承。在深色容器中,--main-color是白色的,而在页面的其他位置,它是黑色的。

2.6.2 通过JavaScript改变自定义属性的值

在浏览器中,自定义属性还可以被JavaScript访问和动态地修改。毕竟这不是一本讲JavaScript的书,我会告诉你足够多的基本概念,然后你再把这些融入到自己的JavaScript项目中。

 
[ 代码片段 2.30 在JavaScript里访问一个自定义变量 ]

<script type="text/javascript">
  var rootElement = document.documentElement;
  var styles = getComputedStyle(rootElement);                 1
  var mainColor = styles.getPropertyValue('--main-bg');       2
  console.log(String(mainColor).trim());                      3
</script>
  • 1 获取元素的样式对象(style object)
  • 2 从样式对象中获得 --main-bg 的值
  • 3 确认 mainColor 是一个字符串以及把空格去掉,输出“#fff”

 
因为你可以随手修改自定义属性的值,你可以用JavaScript给--main-bg动态地定义一个新的值。如果你把它定义为浅蓝色,它就是展示成这样(图2.15)。

 
[ 图 2.15 JavaScript可以通过改变变量--main-bg的值改变面板的背景色 ]

"图 2.15"

下面的代码片段,会在根元素下给--main-bg定义一个新的值,在<script>标签的最下面,加上这些的代码。

 
[ 代码片段 2.31 在JavaScript定义一个自定义变量的值 ]

var rootElement = document.documentElement;
rootElement.style.setProperty('--main-bg', '#cdf');            1
  • 1 把根元素下的 --main-bg 定义为浅蓝色

 
如果你执行这段代码,任何继承了--main-bg属性的元素都会发生改变,对应的值会变成新的。在你的页面上,这会把第一个面板的背景色变成浅蓝色。第二个面板保持不变,因为它继承的还是在深色容器里定义的值。

利用这项技术,你可以在浏览器里用JavaScript给你的站点换主题。或者你可以高亮页面上的某些部分,又或者随手就可以做一些改变。只需要少量几行JavaScript代码,你做的改变就可以影响到页面上大量的元素。

2.6.3 初探自定义属性

自定义属性是一个全新的CSS领域,开发者才刚刚开始探索。因为目前浏览器的支持比较有限,所以还没有到使用它的“黄金时间”。我相信,一段时间之后,你会看到很多关于自定义属性的最佳实践和新颖的玩法。这是你需要留意的。尝试使用自定义属性,看看你可以做出些什么吧。

需要关注的一点,如果你使用var()声明,低版本浏览器不能识别就会忽略它。如果可以的话,给那些浏览器提供一个回退(fallback)方案。

 
[ 代码片段(没有编号) ]

color: black;
color: var(--main-color);

自定义属性原生的动态特性,并不是总是可以使用的,可以关注它的浏览器支持情况http://caniuse.com

总结

  • 拥抱和使用相对单位,让页面的结构去定义样式代码的含义
  • 个人喜欢对字号大小使用rem,选择性地对页面组件的一些简单缩放效果使用em
  • 你可以让整个页面实现响应式缩放,而不需要任何的媒体查询
  • 在声明行高时,使用不带单位的数值
  • 开始了解和使用CSS最新的特性之一——自定义属性吧!

《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用
  4. 无单位数字和行高
  5. CSS自定义属性 [本文]

章节:

  • 2.1 相对单位值的魔力

    • 2.1.1 完美像素设计(pixel-perfect design)的挣扎
    • 2.1.2 完美像素网页的终结
    • 像素(pixel)、点(point)和pc(pica)
  • 2.2 em和rem

    • 2.2.1 对font-size使用em

      • 当我们在一个元素内用em同时声明font-size和其他属性
      • 字号收缩问题
    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.2 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小
  • 2.4 视口相关单位(viewport-relative units)

    • CSS3
    • 2.4.1 在font-size上使用vw
    • 2.4.2 在font-size上使用calc()
  • 2.5 不带单位的数字(unitless number)和行高(line-height)
  • 2.6 自定义属性(也叫“CSS变量”)

    • 2.6.1 动态改变自定义属性的值
    • 2.6.2 通过JavaScript改变自定义属性的值
    • 2.6.3 初探自定义属性
  • 总结
原著版权信息:

作者:Keith J.Grant
书籍:CSS in Depth
章节:Working with relative units


笔者 @Yuying Wu,前端爱好者 / 鼓励师 / 新西兰打工度假 / 铲屎官。目前就职于某大型电商的B2B前端团队。

感谢你读到这里,对上文若有任何疑问或建议,欢迎留言。

如果你和我一样喜欢前端,喜欢捣腾独立博客或者前沿技术,或者有什么职业疑问,欢迎关注我以及各种交流哈。

独立博客:wuyuying.com
知乎ID:@Yuying Wu
Github:Yuying Wu

查看原文

赞 56 收藏 44 评论 0

YuyingWu 评论了文章 · 2018-07-31

如何更愉快地使用em —— 别说你懂CSS相对单位

图片描述

前段时间试译了Keith J.Grant的CSS好书《CSS in Depth》,其中的第二章《Working with relative units》,书中对relative units的讲解和举例可以说相当全面,看完之后发现自己并不太懂CSS相对单位,也希望分享给大家,所以有了这个译文系列。(若有勘误或翻译建议,欢迎 Github PR ^_^)

《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em [本文]
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用
  4. 无单位数字和行高
  5. CSS自定义属性

本文对应的章节目录:

  • 2.1 相对单位值的魔力

    • 2.1.1 完美像素设计(pixel-perfect design)的挣扎
    • 2.1.2 完美像素网页的终结
    • 像素(pixel)、点(point)和pc(pica)
  • 2.2 em和rem

    • 2.2.1 对font-size使用em

      • 当我们在一个元素内用em同时声明font-size和其他属性
      • 字号收缩问题

CSS提供了很多种方式去定义一个值。大家最熟悉的可能也是最容易使用的就是像素(pixel),这被称做“绝对单位”。也就是说,5px在不同的场景下是一样的值。而其他的单位,如em和rem,不是绝对的而是相对的。相对单位的值会根据外部影响因素的变化而变化。例如,2em的值取决于你在哪个元素使用它(有时甚至是哪个属性)。很自然,相对单位使用起来会比较困难。

开发人员,甚至有经验的CSS开发人员,往往不喜欢跟相对单位打交道,其中包括臭名昭著的em。em的值可以被改变的方式似乎难以预测,没有px那么清晰。在本章中,我将揭开相对单位的神秘面纱。首先,我会解释它们为CSS带来的独特价值,然后我会帮助你更好地理解它们。我会解释它们的工作原理,也会告诉你怎么征服它们那看似不可预测的特性。你可以让相对单位为你所用,正确地运行,它们将让你的代码变得更加简单、灵活和容易使用。

2.1 相对单位值的魔力

CSS是通过迟邦定(late-binding)的方式把样式渲染到web页面上的:内容和它的样式会在各自的渲染完成之后再合并到一起。比起其他类型的图形设计,这给设计过程添加了它们没有的复杂程度,同时也赋予CSS更强大的能力 —— 一个样式表可供成百上千个页面使用。此外,用户可以直接改变页面的最终呈现方式。举个例子,用户可以更改默认字号大小或者调整浏览器窗口的大小。

在早期的计算机应用程序开发以及传统出版行业中,开发人员或出版商清楚知道所在的媒介存在哪些限制。对于一个特定的应用程序,窗口可能是400px宽,300px高,或者一个版面可能是4英寸宽,6½英寸高。因此,当开发人员布局应用程序的按钮和文本时,他们很清楚这些元素可以做成什么尺寸,以及在屏幕上还有多少空间可以留给他们用来处理其他元素。然而在网页上,情况却不是这样的。

2.1.1 完美像素设计(pixel-perfect design)的挣扎

在web环境下,用户可以将浏览器窗口设置为任意大小,且CSS需要去适应它。另外,用户可以在一个页面打开后,再调整它的大小,CSS也需要去适应这些新的约束条件。这说明了在你创建页面时样式还没有被调用,而是当页面在屏幕上渲染时,浏览器才会去计算样式的规则。

这给CSS增加了一层抽象的概念。我们不应该根据理想的情境来设计元素,而是应该声明一些样式规则,可以让该元素在任何场景下都能跑通。对于现在的互联网,你的页面可能要在一个4英寸的手机屏幕上展示,也可能在一个30英寸的大屏幕上。

长久以来,设计师大量使用“完美像素”设计,缓解了这个问题带来的复杂性。他们会创建一个有着严格定义的容器,通常是一个大约800px宽的居中的纵向列。然后在这些限制下他们再进行设计,这跟他们的前辈在原生应用程序或印刷出版物中做的设计或多或少有点类似。

2.1.2 完美像素网页的终结

随着技术的进步和制造商推出更高分辨率的显示器,像素完美的设计方式慢慢开始崩溃。在21世纪初期,把页面设计成1024px宽还是800px宽,哪个是更保险的展示策略?开发者针对这个问题讨论得很多。然后,我们又针对能否改成1280px宽有类似的讨论。是时候做个决定了。把我们网站的内容宽度做得宽一点(相对于落伍的小电脑屏幕),还是做得窄一点(相对于新出的大屏幕),哪个选择更好呢?

当智能手机出现的时候,开发人员终于要(被迫)要停止假装每个人都可以在他们的网站上获得相同的体验了。不管我们喜不喜欢,我们都得放弃已知的多栏定宽(px)布局,并开始考虑响应式设计。我们再也不能逃避CSS所带来的抽象概念(abstraction),相反,我们要去拥抱这项特性。

响应式 —— 在CSS中,这指的针对不同大小的浏览器窗口,用不同的方式响应更新页面的样式。我们要对不同尺寸的手机、平板电脑或桌面显示器多花心思了。我们将在第8章中详细介绍响应式设计,但在本章中,我会先给大家介绍一些重要的基础概念。

增加的抽象概念意味着额外的复杂性。如果我设定一个宽度为800px的元素,那么它在一个更小的窗口中会怎么显示呢?如果一个横向菜单不能全部在一行展示完,它又会怎么展示?在编写CSS时,你需要能够同时考虑具体情况以及普适性的问题。如果针对一个特定的问题,你有多种方式可以解决,那么你应该选那个在多种不同场景下更通用的解决方案。

在抽象概念这个问题上,相对单位是CSS提供的工具之一。与其把字号大小设置为14px,你可以把它设置为与窗口大小成比例缩放。或者,你可以设置页面上所有元素是依赖基础字号大小的变化而变化的,然后用一行代码就可以达到调整整个页面的目的。接下来,我们来看看CSS提供了哪些方式来实现以上的效果。

像素(pixel)、点(point)和pc(pica)

CSS支持一些绝对长度单位,其中最常见也最基本的是像素(px)。较不常见的绝对单位有毫米(mm,millimeter)、厘米(cm,centimeter)、英寸(in.,inch)、点(pt,point,印刷术语,长度为1/72 inch)以及pc(pica,印刷术语,长度为12 points)。如果你想了解其中的计算方式,以上的长度单位都可以直接转换成另一个单位:1 inch = 25.4 mm = 2.54 cm = 6 pc = 72 pt = 96 px。因此,16px与12pt(16/96×72)是等价的。设计师通常更熟悉点(point)的使用,而开发人员更习惯于像素,因此在和设计师沟通时,你可能需要在两者之间做一些计算工作。
 
像素这个名字有点误导性 —— 1 CSS像素并不严格等同于显示器的1像素,在高分辨率显示器(如“Retina显示屏”)上尤其明显。尽管根据浏览器、操作系统和硬件的不同,CSS的测量值可能会有细微的差别,但96px总是会大致等于屏幕上的物理1英寸。(尽管有可能会因某些设备或用户设置而异。)

2.2 em和rem

em是最常见的相对长度单位,这是排版中使用的一种度量方式,基准值是当前元素的字号大小。 在CSS中,1em表示当前元素的字号大小,实际值取决于在哪个元素上应用。图2.1展示了一个padding为1em的div

[ 图 2.1:padding为1em的元素(添加虚线是为了让padding更明显)]

"图 2.1"

模板代码片段如下。这套样式规则定义字号为16px,也就是元素本身1em代表的值,然后再使用em来声明元素的padding。 把这段代码添加到一个新的样式表里,在<div class ="padded">下随手写些文字,然后到浏览器看看效果吧。

代码片段 2.1:在padding上使用em

.padded {
  font-size: 16px;
  padding: 1em;          1
}
  • 1 把各个方向的padding的值设置为字号大小

padding赋值为1em,乘以字号,得到一个值为16px的padding渲染值。重点来了,使用相对单位声明的值会由浏览器转化为一个绝对值,我们称之为计算值。

在这个例子里,将padding改为2em会生成一个32px的计算值。如果同一个元素的另一个选择器,用一个不一样的字号值去覆盖它,这会改变em在这个域下的基准值,那么padding的计算值也会相应变化。

在设置padding、height、width或border-radius等属性时,使用em可能会很方便,因为如果它们继承了不同的字号大小,或者用户更改了字体设置,这些属性会均匀地缩放。

图2.2展示了两个不同大小的盒子。盒子内的font-sizepaddingborder-radius各不相同。

[ 图 2.2:有相对大小的padding和border-radius的元素 ]

"图 2.2"

你可以通过用em声明paddingborder-radius来给这些盒子声明样式规则。首先给每个元素设定paddingborder-radius为1em,然后给每个盒子指定不同的字号,那么其他属性会跟着字号缩放。

在你的HTML代码里,创建如下的两个盒子,类名分别是box-smallbox-large,代表两个字号修饰符。

[ 代码片段 2.2:在不同元素上使用em(HTML)]

<span class="box box-small">Small</span>
<span class="box box-large">Large</span>

现在,添加下面的样式到你的样式表。这里使用了em声明了一个盒子。还定义了小字号和大字号的修饰符,指定不同的字号大小。

[ 代码片段 2.3:在不同元素上使用em(CSS)]

.box {
  padding: 1em;
  border-radius: 1em;
  background-color: lightgray;
}

.box-small {
  font-size: 12px;              1
}

.box-large {
  font-size: 18px;              1
}
  • 1 不同的字号大小,这会改变元素的em实际值的大小。

这是em一个强大的功能。你可以定义一个元素的字号大小,然后使用一句简单的声明,就可以通过改变字号大小从而控制整个元素大小缩放。你后面将会创建另外一个这样的例子,不过首先,我们来讨论一下em和字号大小。

2.2.1 对font-size使用em

font-size使用em作为单位时,它的表现会有点不一样。我之前说过,em是以当前元素的字号大小作为基准值的。但是,如果你把一个元素的字号设为1.2em的时候,这是什么意思呢?一个元素的字号大小是不能等于它自己的1.2倍的。相反,在font-size上的em会先从继承到的字号大小衍生出来。

举个简单的例子,见图2.3。以下展示了一些不同字号大小的文字。在代码片段2.4,你会用到em来实现。

[ 图 2.3 以em为单位的两种不同的字号大小 ]

"图 2.3"

在你的页面添加以下代码片段。第一行文字,在<body>标签里面,它会按body的字号大小渲染。第二部分,口号(slogan),继承父元素的字号大小。

[ 代码片段 2.4 相对font-size的模板 ]

<body>
  We love coffee
  <p class="slogan">We love coffee</p>          1
</body>
  • 1 slogan从<body>继承了字号大小。

代码片段中,CSS代码片段声明了body的字号大小。为了更加清晰,在这里我用了px来声明。下一步,你可以用em来放大slogan的字号大小。

[ 代码片段 2.5:在font-size上使用em ]

body {
  font-size: 16px;
}

.slogan {                  1
  font-size: 1.2em;        1
}                          1
  • 1 计算:这个元素继承到字号,乘以1.2

slogan声明的字号大小是1.2em,为了计算转换成像素值,你需要引用继承的字号16px,16 * 1.2 = 19.2,所以计算字号值是19.2px。

提示

如果你已经知道以px为单位的基础字号大小,但希望把它改用em声明,下面有个简单的计算公式:目标em值 = 目标像素值 / 父元素(被继承元素)像素值。举个例子,如果你想要一个10px的字号大小,父元素的字号是12px,10 / 12 = 0.8333em。如果你想要16px的字号大小,父元素字号是12px,那么 16 / 12 = 1.3333em。我们会在这章里多次用到这个计算公式。

有一点对你很有帮助,对于大多数浏览器,默认字号大小是16px。技术上,关键字medium会被计算转换为16px。

当我们在一个元素内用em同时声明font-size和其他属性

你已经使用过em声明font-size了(基于一个继承的字号大小值)。以及,你也曾经使用em声明其他属性,如paddingborder-radius(基于当前元素的字号大小值)。当你针对同一个元素使用em声明font-size和其他属性的时候,em会变得很神奇。此时浏览器必须先计算font-size,然后基于这个值再去计算其他值。这些属性声明的时候使用的是相同的em值,但很可能它们会有不同的计算值。

在之前的例子里,我们计算到字号大小是19.2px(继承的16px乘以1.2em)。图2.4是相同的slogan元素,但有额外的1.2em padding以及为了让padding大小更加明显的灰色背景。可以看出,paddingfont-size稍微大一些,尽管它俩声明的时候em值是一样的。

[ 图2.4 一个font-size为1.2em以及padding为1.2em的元素 ]

"图 2.4"

现在的情况是,这个段落从body继承了16px的字号大小,通过计算得到值为19.2px的字号计算值。这意味着,19.2px是1em在当前域的基础值,而这个值会被用作计算padding的值。对应的CSS代码在下面,更新你的样式表并查看你的测试页面吧。

[ 代码片段 2.6 在font-size和padding上使用em ]

body {
  font-size: 16px;
}

.slogan {
  font-size: 1.2em;             1
  padding: 1.2em;               2
  background-color: #ccc;
}
  • 1 赋值为 19.2 px
  • 2 赋值为 23.04 px

在这个例子里,padding的声明值为1.2em,乘以19.2px(当前元素的字号大小),计算出23.04px。我们可以看到,尽管font-sizepadding声明时em值是一样的,但它们的计算值是不一样的。

字号收缩问题

当你使用em声明多层嵌套的元素字号时,会产生意想不到的效果。要弄清楚每个元素的实际值,首先你需要知道它继承的父元素的字号大小,如果父元素的字号也是用em声明的,那么你需要知道它的父元素的字号大小,在dom树往上查,以此类推。

当你使用em声明列表的字号大小,列表嵌套了好几层,效果就更明显了。大多数web开发者会发现在他们的职业生涯里面,图2.5的列表嵌套形式有点眼熟。文字在逐步缩小!正是因为em带来的这一类烦人的问题,开发者才对em避而远之。

[ 图 2.5 嵌套列表中的字号缩小现象 ]

"图 2.5"

当你多层嵌套列表,而每一层声明的字号大小以em为单位,字号收缩现象就会发生。在代码片段2.7和2.8的例子里,无序列表的字号是0.8em。这个选择器对页面上所有的ul有效,所以当一个列表从另外一个列表继承到字号大小的时候,em就产生复合效果。

[ 代码片段 2.7 在列表上使用em ]

body {
  font-size: 16px;
}

ul {
  font-size: .8em;
}

[ 代码片段 2.8 多层嵌套的列表 ]

<ul>
  <li>Top level
    <ul>                                    1
      <li>Second level                      1
        <ul>                                2
          <li>Third level                   2
            <ul>                            3
              <li>Fourth level              3
                <ul>
                  <li>Fifth level</li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>
  • 1 这个列表嵌套在第一个列表里,继承了它的字号大小
  • 2 然后这个列表又嵌套在另一个列表里,继承了第二个列表的字号大小
  • 3 ……以此类推

每一个列表的字号大小是0.8乘以父元素的字号大小。这代表第一个列表的字号大小是12.8px,嵌套的子列表字号大小是10.24px(12.8px * 0.8),第三层列表的是8.192px,如此类推。同样地,如果你给字号大小的赋值大于1em,相反,文字的字号会一层层变大。我们想要的效果是像图2.6一样,在顶层声明字号的大小,但下面嵌套层级的列表字号保持不变。

[ 图 2.6 字号正常的多层嵌套列表 ]

"图 2.6"

其中一种实现的方式可以看看代码片段2.9。第一个列表的字号大小还是0.8 em(见示例2.7),第二个选择器对嵌套在无序列表的无序列表有效 —— 也就是除了第一个无序列表以外的所有无序列表。现在嵌套的列表设定了跟父元素一致的字号大小,正如图2.6一样。

[ 代码片段 2.9 字号收缩现象的纠正 ]

ul {
  font-size: .8em;
}

ul ul {                1
  font-size: 1em;      1
}  
  • 1 嵌套在列表里面的列表,应该把字号大小设定为跟父元素一样

这可以解决问题,但不是最优方案。因为你设置了一个字号值,马上用另一个选择器重写了这个规则。如果你可以使用针对嵌套的列表声明一个特定的选择器,避免互相覆盖,会是一个更好的方案。

到现在我们清楚了,如果你不是一个比较小心的人,你应该远离em。使用em作为paddingmargin和元素缩放效果的单位挺好的,但当em遇上font-size时,事情可以变得很复杂。感谢上天,我们有个更好的选择 —— rem。

(未完待续,请期待下一篇《如何更愉快地使用rem》)


《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em [本文]
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用
  4. 无单位数字和行高
  5. CSS自定义属性

章节:

  • 2.1 相对单位值的魔力

    • 2.1.1 完美像素设计(pixel-perfect design)的挣扎
    • 2.1.2 完美像素网页的终结
    • 像素(pixel)、点(point)和pc(pica)
  • 2.2 em和rem

    • 2.2.1 对font-size使用em

      • 当我们在一个元素内用em同时声明font-size和其他属性
      • 字号收缩问题
    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.3 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小
  • 2.4 视口相关单位(viewport-relative units)

    • CSS3
    • 2.4.1 在font-size上使用vw
    • 2.4.2 在font-size上使用calc()
  • 2.5 不带单位的数字(unitless number)和行高(line-height)
  • 2.6 自定义属性(也叫“CSS变量”)

    • 2.6.1 动态改变自定义属性的值
    • 2.6.2 通过JavaScript改变自定义属性的值
    • 2.6.3 初探自定义属性
  • 总结
原著版权信息:
 
作者:Keith J.Grant
书籍:CSS in Depth
章节:Working with relative units

笔者 @Yuying Wu,前端爱好者 / 鼓励师 / 新西兰打工度假 / 铲屎官。目前就职于某大型电商的B2B前端团队。

感谢你读到这里,对上文若有任何疑问或建议,欢迎留言。

如果你和我一样喜欢前端,喜欢捣腾独立博客或者前沿技术,或者有什么职业疑问,欢迎关注我以及各种交流哈。

独立博客:wuyuying.com
知乎ID:@Yuying Wu
Github:Yuying Wu

查看原文

YuyingWu 评论了文章 · 2018-07-26

无单位数字和行高 —— 别说你懂CSS相对单位

前段时间试译了Keith J.Grant的CSS好书《CSS in Depth》,其中的第二章《Working with relative units》,书中对relative units的讲解和举例可以说相当全面,看完之后发现自己并不太懂CSS相对单位,也希望分享给大家,所以有了这个译文系列。(若有勘误或翻译建议,欢迎 Github PR^_^

《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用
  4. 无单位数字和行高 [本文]
  5. CSS自定义属性

本文对应的章节目录:

  • 2.5 不带单位的数字(unitless number)和行高(line-height)
译者说:本节的内容比较短,但是内容相对独立,故没有跟其他小节合并。

2.5 无单位数字(unitless number)和行高(line-height)

有一些属性可以接收不带单位的数值(意思就是一个不带长度单位的数字),如line-heightz-indexfont-weight(700等于bold,400等于normal,如此类推)。你也可以在需要长度单位的地方(如px、em、rem)使用一个不带单位的0,因为长度已经是0了,带不带单位也无所谓了 —— 0px 等于 0% 等于 0em。
 
 

警告

不带单位的0只可以表示长度单位和百分比的值,譬如paddingborderwidth。而对于一些特殊的情况,如度数(degrees)或者像秒这样基于时间的值(time-based values),是不可以使用不带单位的0的。

 
line-height属性最特别的地方,在于同时支持带单位和不带单位的值。你应该保持使用不带单位的数值,因为这样就可以从父元素继承。我们在页面上写点文字,看看它是怎么表现的吧。把下面代码添加到你的样式表。

[ 代码片段 2.20 继承line-height的模板 ]

<body>
  <p class="about-us">
    We have built partnerships with small farms around the world to
    hand-select beans at the peak of season. We then carefully roast in
    small batches to maximize their potential.
  </p>
</body>

 
body声明一个line-height,然后文档的其他元素会从这里继承。页面的展示符合预期,不管你对页面的其他元素的字号大小做了什么改变。
 
 

[ 图 2.11 不带单位的行高,会在每个后代元素下重新计算出实际值 ]

"图 2.11"

把代码片段2.21的内容添加到你的样式表。段落(<p>)继承了1.2的行高。因为字号是32px(2em 16px,浏览器默认字号大小),所以本地的行高计算值是38.4px(32px 1.2)。这会给段落的行间距留下比较合适的空间。

 

[ 代码片段 2.21 对line-height使用不带单位的值 ]

body {
  line-height: 1.2;               1
}

.about-us {
  font-size: 2em;
}
  • 1 后代元素继承不带单位的值

 
如果你给行高设定了一个带单位的值,你可能会得到意想不到的结果,如图2.12那样,行间文字互相重叠了,代码片段2.22则是造成这个结果的CSS代码。

[ 图 2.12 继承行高造成的行间互叠 ]
"图 2.12"
 
[ 代码片段 2.22 带单位的行高值造成意外的结果 ]

body {
  line-height: 1.2em;             1
}

.about-us {
  font-size: 2em;                 2
}
  • 1 后代元素继承了计算值(19.2px)
  • 2 等于32px

 
这样的结果源于一次奇怪的继承:当一个元素是用带单位的值声明的,那么它的后代元素会继承计算结果值。当行高属性是用类似em来声明时,它的值会先被计算,然后计算后的值会传到任何继承它的后代元素。对于line-height这个属性来说,如果子元素有跟父元素不一样字号大小的情况,就会导致意想不到的结果,譬如文字间的遮挡。

 

长度 —— CSS中用来描述距离测量的正式用语。它是一个带单位的数字,如5px。长度有两种类型:绝对的和相对的。百分比跟长度很类似,但严格来说,百分比不能叫长度。

 
当你(对某个属性)使用不带单位的数字,声明的值会被继承,也就是说这个值会在子元素中用来重新计算子元素域下的值,而这个通常是你想要的效果。使用不带单位的数字,可以让你在body上设定一个行高,然后什么都不用管,页面上其他元素会默认继承,除非在某个特定的地方你想要做一个额外的样式。

(未完待续,还有最后一篇《CSS Variables》)


《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用
  4. 无单位数字和行高 [本文]
  5. CSS自定义属性

章节:

  • 2.1 相对单位值的魔力

    • 2.1.1 完美像素设计(pixel-perfect design)的挣扎
    • 2.1.2 完美像素网页的终结
    • 像素(pixel)、点(point)和pc(pica)
  • 2.2 em和rem

    • 2.2.1 对font-size使用em

      • 当我们在一个元素内用em同时声明font-size和其他属性
      • 字号收缩问题
    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.2 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小
  • 2.4 视口相关单位(viewport-relative units)

    • CSS3
    • 2.4.1 在font-size上使用vw
    • 2.4.2 在font-size上使用calc()
  • 2.5 不带单位的数字(unitless number)和行高(line-height)
  • 2.6 自定义属性(也叫“CSS变量”)

    • 2.6.1 动态改变自定义属性的值
    • 2.6.2 通过JavaScript改变自定义属性的值
    • 2.6.3 初探自定义属性
  • 总结
原著版权信息:

作者:Keith J.Grant
书籍:CSS in Depth
章节:Working with relative units


笔者 @Yuying Wu,前端爱好者 / 鼓励师 / 新西兰打工度假 / 铲屎官。目前就职于某大型电商的B2B前端团队。

感谢你读到这里,对上文若有任何疑问或建议,欢迎留言。

如果你和我一样喜欢前端,喜欢捣腾独立博客或者前沿技术,或者有什么职业疑问,欢迎关注我以及各种交流哈。

独立博客:wuyuying.com
知乎ID:@Yuying Wu
Github:Yuying Wu

查看原文

YuyingWu 评论了文章 · 2018-07-24

如何更愉快地使用rem —— 别说你懂CSS相对单位

图片描述

前段时间试译了Keith J.Grant的CSS好书《CSS in Depth》,其中的第二章《Working with relative units》,书中对relative units的讲解和举例可以说相当全面,看完之后发现自己并不太懂CSS相对单位,也希望分享给大家,所以有了这个译文系列。(若有勘误或翻译建议,欢迎 Github PR^_^

《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem [本文]
  3. 视口相关单位的应用
  4. 无单位数字和行高
  5. CSS自定义属性

本文对应的章节目录:

  • 2.2 em和rem

    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.3 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小

2.2 em和rem

2.2.2 对font-size使用rem

当浏览器解析HTML文档时,创建了一个用来代表页面元素的集合,叫做DOM(文档对象模型,Document Object Model)。树状结构,每一个节点代表一个元素。<html>就是顶层节点(根节点),在下面的是它的子节点<head><body>,再往下就是它们的子节点,还有后代节点,如此类推。

根节点是文档里所有其他元素的祖先。它有一个特别的伪类(pseudo-class)选择器(:root),在样式表里可以用这个选择器表示。使用带类名的类型选择器html,或者直接用标签选择器,效果是一样的。

rem是根em(root em)的缩写。rem是和根元素关联的,不依赖当前元素。不管你在文档中的什么地方使用这个单位,1.2rem的计算值是相等的,等于1.2倍的根元素的字号大小。下面的示例代码中,声明了根元素的字号大小,并在嵌套的无序列表中使用rem声明字号大小。

[ 代码片段 2.10 使用rem声明字号大小 ]

:root {                    1
  font-size: 1em;          2
}

ul {
  font-size: .8rem;
}
  • 1 伪类 :root 等价于 html 选择器
  • 2 使用浏览器的默认字号大小(16px)

在这个示例里,根字号大小是浏览器的默认大小16px(根元素的1em等于浏览器的默认字号大小)。无序列表的字号大小为0.8rem,计算结果是12.8px。因为这只跟根元素相关,尽管你在列表里嵌套了列表,嵌套子列表的字号仍然保持不变。

可用性:对font-size使用相对长度单位

一些浏览器会提供给用户2种方式定制文字的大小:缩放和设置一个默认的字号大小。通过按Ctrl+或者Ctrl-,用户可以对页面进行缩放。这在视觉上会把整个页面的文字或图片(其实是所有元素)都放大或缩小了。在一些浏览器,这个改变只针对当前的标签页且是临时的,不会影响到新开的标签页。

设置默认字号大小,会有点不一样。不仅仅是设置的入口比较难找(一般在浏览器的设置页),而且这个设置是永久的,直到用户把默认值还原。值得注意的是,这个设置对使用px或其他绝对单位定义的字号大小无效。因为默认字号大小对一些用户是必要的,尤其是弱视的群体,你应该用相对单位或百分比来定义字号的大小。

rem简化了很多em带来的复杂度。事实上,rem提供了一个在px和em间的相对单位折中解决方案,而且更易于使用。那么,是不是意味着你应该在对所有元素都使用rem,去掉其他长度单位呢?当然不是。

在CSS的世界里,这个答案通常是,看情况。rem只是你的工具箱中的其中一个。掌握CSS很重要的一点,就是学会分辨在什么场景下该使用什么工具。我的选择是,对font-size使用rem,对border使用px,对其他的度量方式如paddingmarginborder-radius等使用em。然而在必要时,需要声明容器的宽度的话,我更喜欢使用百分比。

这样,字号大小就变得可预测,而当其他因素影响到元素的字号大小时,你也可以借助em去缩放元素的padding和margin。在border上使用像素是很合适的,尤其当你想要一根漂亮的线的时候。以上就是我对不同属性使用不同单位的理想方案,不过我要再次声明,这些都是工具,在某些特定场景下,利用不同的工具可能取到更好的效果。

提示

当你不确定的时候,对font-size使用rem,对border使用px,以及对其他大多数属性使用em。

2.3 停止使用像素思维去思考

把页面的根元素字号大小定义为0.625em或者62.5%,在最近几年来,这样的用法很常见,这是一种模式,或者更贴切地说,这是一种反模式。

[ 代码片段 2.11 反模式:全局地把font-size定义为10px ]

html {
  font-size: .625em;
}

我并不推荐这种用法。这个用法把浏览器默认的字号大小16px缩小到10px。这样做的好处是简化了计算,如果设计师告诉你字号大小应该是14px,那你可以很轻易地计算出1.4rem,毕竟我们还是在使用相对单位。

一开始,这看起来很方便,但事实上这样的实现方式有两个问题。第一,强制你写了很多重复的样式代码。10px对于大多数文本来说太小了,你需要在整个页面中,来来回回地覆盖它。你会发现,自己把一段段落(<p>)的字号大小声明为1.4rem,然后又把导航(<nav>)的链接字号大小声明为1.4rem,样式代码中还有很多这样的用法。这样引入了更高的错误风险,当你需要修改时发现代码耦合程度比较高,同时也会让样式文件变大。

第二个问题是,你这么做的时候,其实你还是在用像素的思维在思考。虽然在代码里写的是1.4rem,但是在你的脑子里,其实还是想的是14px。在响应式网页开发中,你应该学会适应那些“模糊”的值。1.2em实际等于多少像素,并不重要,你只需要知道这是比继承的字号大一点点,那就足够了。而且,如果在屏幕上这不是你想要的效果,那就改吧。这是需要时间实验和试错的,但事实上,使用px的时候我们也需要这样做。(在第13章,我们会有更具体的方式来优化这个实现方式。)

当使用em时,我们很容易陷入纠结,这个值转化成像素值会是多少呢?尤其对于字号大小。你一直在乘和除以em值,这样你很快就会疯掉了。相反,我希望你可以接受一项挑战,尝试培养先开始使用em的习惯。如果你习惯使用像素,那转成em是需要一定时间和练习的,但相信我,这很值得。

这不是在说你再也不使用像素了。如果你跟一个设计师合作,你可能需要用更精确的像素值去沟通,这没问题的。在项目的开始,你需要声明一个基础的字号大小(通常是对标题或者标注的常用字号)。使用绝对值去描述大小,往往会更加容易。

转换成rem会有计算环节,那就让计算器去忙吧(通常我会在Mac电脑上按cmd+空格,在Spotlight里计算)。首先在根元素上声明根字号大小,从那开始,使用像素应该是例外的情况,而不是常态。

在这章内容里,我还是会持续地聊起像素。这会有助于我解释相对单位的工作原理,同时也能帮助你培养计算em值的习惯。在这章之后,我基本会使用相对单位来讨论字号的大小。

2.3.1 设置一个合理的字号默认值

先假设你想把默认字号设定为14px。把10px设定为基准值,再在页面中去覆盖它的写法,我们不推荐这种写法,相反,你应该在根元素上直接声明一个值。
在这个代码片段里,目标字号值是继承的,浏览器的默认值16px,那么14/16 = 0.875。

把下面的代码添加到一个新的样式表的最上面,我们会在这上面添加其他代码。这里设定根元素(<html>)的默认字号大小。

[ 代码片段 2.12 设定正确的默认字号大小 ]

:root {                    1
  font-size: 0.875em;      2
}
  • 1 或者使用 HTML 选择器
  • 2 14/16(期望值px / 继承值px)等于0.875

现在,你的期望基准字号14px对整个页面的元素有效,你不需要在其他地方重新声明了。你只需要在设计不一样的地方修改成新的字号,譬如标题。

我们一起来创建图2.7那样的面板吧!你创建的这个面板,基于14px字号,使用相对单位。

[ 图 2.7 使用相对单位和继承字号的面板 ]

"图 2.7"

下面是模板,加到你的页面吧。

[ 代码片段 2.13 面板的模板 ]

<div class="panel">
  <h2>Single-origin</h2>
  <div class="panel-body">
    We have built partnerships with small farms around the world to
    hand-select beans at the peak of season. We then carefully roast
    in <a href="/batch-size">small batches</a> to maximize their
    potential.
  </div>
</div>

下一段代码是样式的。你会在paddingborder-radius使用em,标题的字号使用rem,以及border使用px。把下面代码添加到你的样式表吧。

[ 代码片段 2.14 使用相对单位的面板 ]

.panel {
  padding: 1em;                    1
  border-radius: 0.5em;            1
  border: 1px solid #999;          2
}

.panel > h2 {
  margin-top: 0;                   3
  font-size: 0.8rem;               4
  font-weight: bold;               4
  text-transform: uppercase;       4
}
  • 1 对padding和border-radius使用em
  • 2 用1px定义细边框
  • 3 把面板上面多余的空间去掉,更多解释看第3章
  • 4 用rem控制标题的字号大小

这段代码给面板添加了一个细边框以及定义了标题的样式。我希望标题的字号小一点,但要加粗和全是大写。(你可以根据自己的设计,把字号改大点或者使用不同的排版方式)

第二个选择器>是一个直接后代组合选择符(direct descendant combinator),它代表的是.panel下的子元素h2。更完整的选择器和组合选择符的索引可以看附录A。

在代码片段2.13中,为了更清晰看到效果,我给body添加了一个类panel-body,不过你会发现,在你自己的代码里是不需要的。因为这个元素从根元素上继承了字号大小,它已经是你想要看到的那样。

2.3.3 让这个面板变得“响应式”

我们再更深入地看看这个问题。你可以根据屏幕尺寸的变化,添加媒体查询来改变基础字号大小,这可以令面板在不同尺寸的屏幕下,有不同的大小变化。(见图2.8)

[ 图 2.8 在不同屏幕大小的响应式面板:300px(左上角),800px(右上角),1440px(下面) ]

"图 2.8"

媒体查询(media query) —— 通过@media规则来声明样式,在不同的屏幕尺寸或者媒体类型(如打印机或显示器)下,触发对应的样式控制。这是响应式设计的关键要素。详情看代码片段2.15的这个例子,我在第8章会更深入的讨论媒体查询这个话题。

为了实现上面说的效果,把你的样式代码改成这样。

[ 代码片段 2.15 ]

:root {                            1
  font-size: 0.75em;               1
}                                  1

@media (min-width: 800px) {        2
  :root {                          2
    font-size: 0.875em;            2
  }                                2
}                                  2

@media (min-width: 1200px) {       3
  :root {                          3
    font-size: 1em;                3
  }                                3
}   
  • 1 针对所有屏幕,但是在更大的屏幕会被覆盖
  • 2 针对比800px更宽的屏幕,覆盖默认样式代码
  • 3 针对比1200px更宽的屏幕,覆盖以上两套样式代码

第一套样式规则,声明了小屏幕中的默认字号大小,这是我们想要在较小的屏幕上看到的字号大小。然后使用媒体查询,把800px和1200px分别作为两个分水岭逐级增加字号的大小,覆盖掉默认的代码。

针对页面的根元素使用这些字号大小,响应式地重新定义em和rem对应的值,从而达到响应改变整个页面的效果。尽管你没有直接对这个面板做任何的修改,它现在是响应式的。在小屏幕上,譬如一台手机,字号大小会被渲染成更小的(12px)。然后,在更大的屏幕上,宽大于800px和大于1200px的,组件的字号会分别放大到14px和16px。改变你的浏览器窗口,看看组件是怎么变化的吧。

如果你在整个页面中像这样严格使用相对单位,整个页面会随着视窗大小放大和缩小。这会是你的响应式策略里很重要的一部分。上面的2套媒体查询声明代码,可以帮助你节省在页面的其他部分使用媒体查询的额外代码。不过,如果你在元素中声明的字号大小是以像素为单位的,那就不会产生任何效果了。

类似地,如果你的老板或者客户觉得现在网站的字号太小或者太大,你随时可以做到通过修改一行代码影响到全局的元素,这项改变会影响到页面上的其他元素,不费吹灰之力。

2.3.3 调整单个组件的大小

你也可以通过使用em缩放页面上的一个独立组件。有时,你可能会需要界面上的某些组件可以有个大号的版本。在我们的面板上这么做吧,首先你需要给面板添加一个类名large<div class="panel large">

在图2.9,我们看到了面板的普通版和大号版的比较。效果类似响应式面板,但是两种尺寸是可以同时在同一个页面中使用的。

[ 图 2.9 在一个页面上的普通尺寸面板和大号面板 ]

"图 2.9"

我们来对面板的字号声明方式做一些小的修改。你还是在使用相对单位,但需要调整它们的基准值。第一点,给每个面板的父元素字号大小的定义font-size: 1rem。这里指的是,不管在什么地方使用这个面板,每个面板的字号大小是一个确定值。

第二点,使用em重新声明标题的字号大小,而不使用rem,这样标题就可以和刚才声明的父元素字号1rem关联起来。下面是对应的代码,更新下你的样式表代码吧。

[ 代码片段 2.16 创建一个面板的大号版本 ]

.panel {
  font-size: 1rem;               1
  padding: 1em;
  border: 1px solid #999;
  border-radius: 0.5em;
}

.panel > h2 {
  margin-top: 0;
  font-size: 0.8em;              2
  font-weight: bold;
  text-transform: uppercase;
}
  • 1 给组件声明确定的字号大小
  • 2 其他元素的字号大小用em和父元素字号关联

这些修改看起来并没有影响面板的样式,但是现在你已经准备好了,做一个大号的面板只需要修改一小行代码。你需要做的,就是把父元素字号大小改写成1rem以外的一个值。因为其他元素的计算方式都依赖父元素的字号大小,只要修改它,整个面板的相关尺寸都会发生改变。添加下一个CSS代码片段到你的样式表,定义一个大号面板吧。

[ 代码片段 2.17 利用一行代码放大整个面板 ]

.panel.large {               1
  font-size: 1.2rem;
}
  • 1 组合选择器指向同时有panel类和large类的元素

现在,你可以给普通面板添加class="panel"和给大号面板添加class="panel large"。类似地,你也可以定义一个小号版本,只需要把父元素的字号设得比1rem小。如果这个面板是一个更复杂的组件,包含多种字号大小或padding,也只需要一个声明就可以重置大小,只要所有的子元素都是使用em声明的。


《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem [本文]
  3. 视口相关单位的应用
  4. 无单位数字和行高
  5. CSS自定义属性

章节:

  • 2.1 相对单位值的魔力

    • 2.1.1 完美像素设计(pixel-perfect design)的挣扎
    • 2.1.2 完美像素网页的终结
    • 像素(pixel)、点(point)和pc(pica)
  • 2.2 em和rem

    • 2.2.1 对font-size使用em

      • 当我们在一个元素内用em同时声明font-size和其他属性
      • 字号收缩问题
    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.2 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小
  • 2.4 视口相关单位(viewport-relative units)

    • CSS3
    • 2.4.1 在font-size上使用vw
    • 2.4.2 在font-size上使用calc()
  • 2.5 不带单位的数字(unitless number)和行高(line-height)
  • 2.6 自定义属性(也叫“CSS变量”)

    • 2.6.1 动态改变自定义属性的值
    • 2.6.2 通过JavaScript改变自定义属性的值
    • 2.6.3 初探自定义属性
  • 总结
原著版权信息:

作者:Keith J.Grant
书籍:CSS in Depth
章节:Working with relative units


笔者 @Yuying Wu,前端爱好者 / 鼓励师 / 新西兰打工度假 / 铲屎官。目前就职于某大型电商的B2B前端团队。

感谢你读到这里,对上文若有任何疑问或建议,欢迎留言。

如果你和我一样喜欢前端,喜欢捣腾独立博客或者前沿技术,或者有什么职业疑问,欢迎关注我以及各种交流哈。

独立博客:wuyuying.com
知乎ID:@Yuying Wu
Github:Yuying Wu

查看原文

YuyingWu 发布了文章 · 2018-07-23

无单位数字和行高 —— 别说你懂CSS相对单位

前段时间试译了Keith J.Grant的CSS好书《CSS in Depth》,其中的第二章《Working with relative units》,书中对relative units的讲解和举例可以说相当全面,看完之后发现自己并不太懂CSS相对单位,也希望分享给大家,所以有了这个译文系列。(若有勘误或翻译建议,欢迎 Github PR^_^

《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用
  4. 无单位数字和行高 [本文]
  5. CSS自定义属性

本文对应的章节目录:

  • 2.5 不带单位的数字(unitless number)和行高(line-height)
译者说:本节的内容比较短,但是内容相对独立,故没有跟其他小节合并。

2.5 无单位数字(unitless number)和行高(line-height)

有一些属性可以接收不带单位的数值(意思就是一个不带长度单位的数字),如line-heightz-indexfont-weight(700等于bold,400等于normal,如此类推)。你也可以在需要长度单位的地方(如px、em、rem)使用一个不带单位的0,因为长度已经是0了,带不带单位也无所谓了 —— 0px 等于 0% 等于 0em。
 
 

警告

不带单位的0只可以表示长度单位和百分比的值,譬如paddingborderwidth。而对于一些特殊的情况,如度数(degrees)或者像秒这样基于时间的值(time-based values),是不可以使用不带单位的0的。

 
line-height属性最特别的地方,在于同时支持带单位和不带单位的值。你应该保持使用不带单位的数值,因为这样就可以从父元素继承。我们在页面上写点文字,看看它是怎么表现的吧。把下面代码添加到你的样式表。

[ 代码片段 2.20 继承line-height的模板 ]

<body>
  <p class="about-us">
    We have built partnerships with small farms around the world to
    hand-select beans at the peak of season. We then carefully roast in
    small batches to maximize their potential.
  </p>
</body>

 
body声明一个line-height,然后文档的其他元素会从这里继承。页面的展示符合预期,不管你对页面的其他元素的字号大小做了什么改变。
 
 

[ 图 2.11 不带单位的行高,会在每个后代元素下重新计算出实际值 ]

"图 2.11"

把代码片段2.21的内容添加到你的样式表。段落(<p>)继承了1.2的行高。因为字号是32px(2em 16px,浏览器默认字号大小),所以本地的行高计算值是38.4px(32px 1.2)。这会给段落的行间距留下比较合适的空间。

 

[ 代码片段 2.21 对line-height使用不带单位的值 ]

body {
  line-height: 1.2;               1
}

.about-us {
  font-size: 2em;
}
  • 1 后代元素继承不带单位的值

 
如果你给行高设定了一个带单位的值,你可能会得到意想不到的结果,如图2.12那样,行间文字互相重叠了,代码片段2.22则是造成这个结果的CSS代码。

[ 图 2.12 继承行高造成的行间互叠 ]
"图 2.12"
 
[ 代码片段 2.22 带单位的行高值造成意外的结果 ]

body {
  line-height: 1.2em;             1
}

.about-us {
  font-size: 2em;                 2
}
  • 1 后代元素继承了计算值(19.2px)
  • 2 等于32px

 
这样的结果源于一次奇怪的继承:当一个元素是用带单位的值声明的,那么它的后代元素会继承计算结果值。当行高属性是用类似em来声明时,它的值会先被计算,然后计算后的值会传到任何继承它的后代元素。对于line-height这个属性来说,如果子元素有跟父元素不一样字号大小的情况,就会导致意想不到的结果,譬如文字间的遮挡。

 

长度 —— CSS中用来描述距离测量的正式用语。它是一个带单位的数字,如5px。长度有两种类型:绝对的和相对的。百分比跟长度很类似,但严格来说,百分比不能叫长度。

 
当你(对某个属性)使用不带单位的数字,声明的值会被继承,也就是说这个值会在子元素中用来重新计算子元素域下的值,而这个通常是你想要的效果。使用不带单位的数字,可以让你在body上设定一个行高,然后什么都不用管,页面上其他元素会默认继承,除非在某个特定的地方你想要做一个额外的样式。

(未完待续,还有最后一篇《CSS Variables》)


《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用
  4. 无单位数字和行高 [本文]
  5. CSS自定义属性

章节:

  • 2.1 相对单位值的魔力

    • 2.1.1 完美像素设计(pixel-perfect design)的挣扎
    • 2.1.2 完美像素网页的终结
    • 像素(pixel)、点(point)和pc(pica)
  • 2.2 em和rem

    • 2.2.1 对font-size使用em

      • 当我们在一个元素内用em同时声明font-size和其他属性
      • 字号收缩问题
    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.2 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小
  • 2.4 视口相关单位(viewport-relative units)

    • CSS3
    • 2.4.1 在font-size上使用vw
    • 2.4.2 在font-size上使用calc()
  • 2.5 不带单位的数字(unitless number)和行高(line-height)
  • 2.6 自定义属性(也叫“CSS变量”)

    • 2.6.1 动态改变自定义属性的值
    • 2.6.2 通过JavaScript改变自定义属性的值
    • 2.6.3 初探自定义属性
  • 总结
原著版权信息:

作者:Keith J.Grant
书籍:CSS in Depth
章节:Working with relative units


笔者 @Yuying Wu,前端爱好者 / 鼓励师 / 新西兰打工度假 / 铲屎官。目前就职于某大型电商的B2B前端团队。

感谢你读到这里,对上文若有任何疑问或建议,欢迎留言。

如果你和我一样喜欢前端,喜欢捣腾独立博客或者前沿技术,或者有什么职业疑问,欢迎关注我以及各种交流哈。

独立博客:wuyuying.com
知乎ID:@Yuying Wu
Github:Yuying Wu

查看原文

赞 29 收藏 22 评论 2

YuyingWu 发布了文章 · 2018-07-23

视口相关单位的应用 —— 别说你懂CSS相对单位

前段时间试译了Keith J.Grant的CSS好书《CSS in Depth》,其中的第二章《Working with relative units》,书中对relative units的讲解和举例可以说相当全面,看完之后发现自己并不太懂CSS相对单位,也希望分享给大家,所以有了这个译文系列。(若有勘误或翻译建议,欢迎 Github PR ^_^)

《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用 [本文]
  4. 无单位数字和行高
  5. CSS自定义属性

本文对应的章节目录:

  • 2.4 视口相关单位(viewport-relative units)

    • CSS3
    • 2.4.1 在font-size上使用vw
    • 2.4.2 在font-size上使用calc()

2.4 视口相关单位(viewport-relative units)

你已经学完em和rem了,它们都是以font-size为基准值的,但相对单位不只它们。还有视口相关单位,依赖浏览器的视口大小来定义长度的。

视口(viewport) —— 在浏览器窗口中用来渲染页面的可视区域,这不包括浏览器的地址栏、工具栏、状态栏等(如果有的话)。

如果你不熟悉视口相关单位的话,在这里简单介绍一下。

  • vh —— 视口高度的1/100
  • vw —— 视口宽度的1/100
  • vmin —— 视区宽度或高度较小值的1/100(IE9支持的是vm)
  • vmax —— 视区宽度或高度较大值的1/100(在写本书时,IE或者Edge都不支持)

举个例子,50vw等于视口宽度的一半,而25vh等于视口高度的25%。vmin依赖两者(宽或高)的较小值,如果我们需要确保一个元素不管在横屏还是竖屏下适应屏幕展示的话,这个属性会很有帮助:如果是横屏,vmin的基准值是屏幕的高度,如果是竖屏,它的基准值是屏幕的宽度。

图2.10展示了一个正方形的元素在不同屏幕尺寸下的视口的情况。宽和高的值都声明为90vmin,也就是宽高较小值的90%。边长的值等于,横屏情况下高度的90%,或者竖屏情况下宽度的90%。

[ 图 2.10 如果把一个元素的宽高定义成90vmin,它总会展示成一个正方形,边长稍小于屏幕的视口,不管它的尺寸或方向怎样。 ]

"图 2.10"

在代码片段2.18可以看到这个元素的样式,渲染了一个适应屏幕尺寸的大正方形,不管浏览器的尺寸是多少。你可以通过添加<div class="square">,再看看页面效果。

[ 代码片段 2.18 边长使用vmin的正方形元素 ]

.square {
  width: 90vmin;
  height: 90vmin;
  background-color: #369;
}

如果想做一个大英雄图片充满屏幕的效果,视口相关长度最合适不过。你的图片可以在一个长条形的容器内,把图片的高度设为100vh,那它的高度就会跟视口高度一样。

笔记

视口相关单位对于大多数浏览器还是一项比较新的特性,所以当你试图把这个特性和其他样式混搭时,有可能会有一些很奇怪的bug。详情参考http://caniuse.com/#feat=viewport-units列表中的“已知问题(Known Issues)”

CSS3

本章提到的很多单位类型,其实并不在早期的CSS版本里(尤其是rem和视口相关单位)。在这门语言一系列的更新迭代过程中,它们慢慢被加进来,我们将最新的版本称作CSS3。
 
在二十世纪末二十一世纪初,在CSS规范的初版发布之后的很长一段时间,只有很小的改动。在1998年5月份,W3C(World Wide Web Consortium)发布了CSS 2规范。不久之后,修正版本2中问题和缺陷的2.1版本开始了,CSS 2.1的工作持续了好多年,但并没有添加什么令人印象深刻的特性。直到2011年4月份,版本2.1终于被确认为“提议推荐标准”(Proposed Recommendation)。到这个时候,大多数浏览器对CSS 2.1的特性已经支持得很好了,在此基础上,浏览器又努力地添加一些新的特性。这就是新规范CSS 3。
 
“3”是个非正式的版本号,实际上并没有CSS3规范。相反,这个规范被拆成多个独立的模块,分别有独立的版本号。background和border的规范已经从盒模型(box model)以及层叠与继承(cascading and inheritance)规范中独立出来。通过这种方式,W3C就可以针对CSS某个模块进行更新迭代,而不用同时更新其他不相关的模块的内容。在这些模块规范里,很多还停留在第3版本(现在叫“第3级”),但有的模块(比如选择器规范(selectors specification))已经到第4级,而其他的模块(比如flexbox)还在第1级。
 
这些新特性进入了人们的视野。我们可以看到在2009到2013年间,大量新的CSS特性涌进浏览器中。其中有现在比较出名的rem和视口相关单位,以及新的选择器(new selectors)、媒体查询(media queries)、网页文字(web fonts)、圆形边框(rounded borders)、动画(animation)、变换(transitions)、变形(transformations)以及定义颜色的不同方式。然而,每年的新特性的数量还在逐年稳定增长。
 
这意味着,我们不再只跟一个特定版本的CSS规范打交道。这是一套有生命的标准,每个浏览器也在持续地支持新的特性,开发者会开始使用然后渐渐习惯它们。CSS4大概不会出现了,如果有的话,应该也只是个用于市场营销的名词。尽管这本书也会提到CSS3的新特性,但没有必要把它们都搬出来,就整个网页而言,这些都是CSS。

2.4.1 在font-size上使用vw

一个应用如果使用了视口相关单位,可能效果最不明显的地方就是用在字号大小上。事实上,我发现把vh和vw用在字号上比元素的宽或者高更实用。

试想一下,如果把元素的font-size声明为2vm,会怎么样?台式机屏幕宽是1200px,2vm等于24px(1200 2%)。而平板电脑的屏幕宽768px,2vm约等于15px(768 2%)。很棒的是,元素在两个尺寸下缩放自如。这意味着这里并没有一个突然的断点,元素会随着视口尺寸的增大而平滑增大。

不幸的是,24px对于大屏幕来说有点太大了。而更糟糕的是,在iPhone6它直接缩小到7.5px。好消息是缩放生效了,而坏消息是极限情况的处理有点差。你可以通过CSS的方法calc()解决这个问题。

2.4.2 在font-size上使用calc()

calc()函数支持对2个或者更多个数值进行基础的运算。这个函数对不同类型的单位间的运算尤其有用。这个函数支持加(+)、减(-)、乘(*)和除以(/)。其中,加和减运算符左右必须留有空格,所以我建议我们来培养一个习惯,总是在四个运算符的两侧都写上空格,譬如calc(1em + 10px)

你会在下一个代码片段中,使用calc()来计算vh单位和em单位的值。把你的样式表中的上一段设定基础字号大小的代码去掉(及相关的媒体查询代码),添加以下代码。

[ 代码片段 2.19 在font-size中使用em和vh单位进行calc()运算 ]

:root {
  font-size: calc(0.5em + 1vw);
}

现在,打开页面,缓慢地改变浏览器的大小。你会发现,字号改变的过渡很平滑。0.5em在这里代表的是字号的最小值,1vm则代表着响应式地往上累加。这样基础字号大小就在iPhone6的11.75px和1200px宽浏览器窗口的20px间缩放。你可以根据自己的喜好改变这些值。

你现在可以实现响应式策略的核心逻辑而不需要添加一行媒体查询的代码。页面上的所有元素可以根据视口大小平滑缩放,不再需要3或4个硬编码的断点。

(后一篇《无单位数字和行高》已同步发布,了解一下 ^_^


《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem
  3. 视口相关单位的应用 [本文]
  4. 无单位数字和行高
  5. CSS自定义属性

章节:

  • 2.1 相对单位值的魔力

    • 2.1.1 完美像素设计(pixel-perfect design)的挣扎
    • 2.1.2 完美像素网页的终结
    • 像素(pixel)、点(point)和pc(pica)
  • 2.2 em和rem

    • 2.2.1 对font-size使用em

      • 当我们在一个元素内用em同时声明font-size和其他属性
      • 字号收缩问题
    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.2 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小
  • 2.4 视口相关单位(viewport-relative units)

    • CSS3
    • 2.4.1 在font-size上使用vw
    • 2.4.2 在font-size上使用calc()
  • 2.5 不带单位的数字(unitless number)和行高(line-height)
  • 2.6 自定义属性(也叫“CSS变量”)

    • 2.6.1 动态改变自定义属性的值
    • 2.6.2 通过JavaScript改变自定义属性的值
    • 2.6.3 初探自定义属性
  • 总结
原著版权信息:

作者:Keith J.Grant
书籍:CSS in Depth
章节:Working with relative units


笔者 @Yuying Wu,前端爱好者 / 鼓励师 / 新西兰打工度假 / 铲屎官。目前就职于某大型电商的B2B前端团队。

感谢你读到这里,对上文若有任何疑问或建议,欢迎留言。

如果你和我一样喜欢前端,喜欢捣腾独立博客或者前沿技术,或者有什么职业疑问,欢迎关注我以及各种交流哈。

独立博客:wuyuying.com
知乎ID:@Yuying Wu
Github:Yuying Wu

查看原文

赞 22 收藏 18 评论 1

YuyingWu 发布了文章 · 2018-07-16

如何更愉快地使用rem —— 别说你懂CSS相对单位

图片描述

前段时间试译了Keith J.Grant的CSS好书《CSS in Depth》,其中的第二章《Working with relative units》,书中对relative units的讲解和举例可以说相当全面,看完之后发现自己并不太懂CSS相对单位,也希望分享给大家,所以有了这个译文系列。(若有勘误或翻译建议,欢迎 Github PR^_^

《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem [本文]
  3. 视口相关单位的应用
  4. 无单位数字和行高
  5. CSS自定义属性

本文对应的章节目录:

  • 2.2 em和rem

    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.3 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小

2.2 em和rem

2.2.2 对font-size使用rem

当浏览器解析HTML文档时,创建了一个用来代表页面元素的集合,叫做DOM(文档对象模型,Document Object Model)。树状结构,每一个节点代表一个元素。<html>就是顶层节点(根节点),在下面的是它的子节点<head><body>,再往下就是它们的子节点,还有后代节点,如此类推。

根节点是文档里所有其他元素的祖先。它有一个特别的伪类(pseudo-class)选择器(:root),在样式表里可以用这个选择器表示。使用带类名的类型选择器html,或者直接用标签选择器,效果是一样的。

rem是根em(root em)的缩写。rem是和根元素关联的,不依赖当前元素。不管你在文档中的什么地方使用这个单位,1.2rem的计算值是相等的,等于1.2倍的根元素的字号大小。下面的示例代码中,声明了根元素的字号大小,并在嵌套的无序列表中使用rem声明字号大小。

[ 代码片段 2.10 使用rem声明字号大小 ]

:root {                    1
  font-size: 1em;          2
}

ul {
  font-size: .8rem;
}
  • 1 伪类 :root 等价于 html 选择器
  • 2 使用浏览器的默认字号大小(16px)

在这个示例里,根字号大小是浏览器的默认大小16px(根元素的1em等于浏览器的默认字号大小)。无序列表的字号大小为0.8rem,计算结果是12.8px。因为这只跟根元素相关,尽管你在列表里嵌套了列表,嵌套子列表的字号仍然保持不变。

可用性:对font-size使用相对长度单位

一些浏览器会提供给用户2种方式定制文字的大小:缩放和设置一个默认的字号大小。通过按Ctrl+或者Ctrl-,用户可以对页面进行缩放。这在视觉上会把整个页面的文字或图片(其实是所有元素)都放大或缩小了。在一些浏览器,这个改变只针对当前的标签页且是临时的,不会影响到新开的标签页。

设置默认字号大小,会有点不一样。不仅仅是设置的入口比较难找(一般在浏览器的设置页),而且这个设置是永久的,直到用户把默认值还原。值得注意的是,这个设置对使用px或其他绝对单位定义的字号大小无效。因为默认字号大小对一些用户是必要的,尤其是弱视的群体,你应该用相对单位或百分比来定义字号的大小。

rem简化了很多em带来的复杂度。事实上,rem提供了一个在px和em间的相对单位折中解决方案,而且更易于使用。那么,是不是意味着你应该在对所有元素都使用rem,去掉其他长度单位呢?当然不是。

在CSS的世界里,这个答案通常是,看情况。rem只是你的工具箱中的其中一个。掌握CSS很重要的一点,就是学会分辨在什么场景下该使用什么工具。我的选择是,对font-size使用rem,对border使用px,对其他的度量方式如paddingmarginborder-radius等使用em。然而在必要时,需要声明容器的宽度的话,我更喜欢使用百分比。

这样,字号大小就变得可预测,而当其他因素影响到元素的字号大小时,你也可以借助em去缩放元素的padding和margin。在border上使用像素是很合适的,尤其当你想要一根漂亮的线的时候。以上就是我对不同属性使用不同单位的理想方案,不过我要再次声明,这些都是工具,在某些特定场景下,利用不同的工具可能取到更好的效果。

提示

当你不确定的时候,对font-size使用rem,对border使用px,以及对其他大多数属性使用em。

2.3 停止使用像素思维去思考

把页面的根元素字号大小定义为0.625em或者62.5%,在最近几年来,这样的用法很常见,这是一种模式,或者更贴切地说,这是一种反模式。

[ 代码片段 2.11 反模式:全局地把font-size定义为10px ]

html {
  font-size: .625em;
}

我并不推荐这种用法。这个用法把浏览器默认的字号大小16px缩小到10px。这样做的好处是简化了计算,如果设计师告诉你字号大小应该是14px,那你可以很轻易地计算出1.4rem,毕竟我们还是在使用相对单位。

一开始,这看起来很方便,但事实上这样的实现方式有两个问题。第一,强制你写了很多重复的样式代码。10px对于大多数文本来说太小了,你需要在整个页面中,来来回回地覆盖它。你会发现,自己把一段段落(<p>)的字号大小声明为1.4rem,然后又把导航(<nav>)的链接字号大小声明为1.4rem,样式代码中还有很多这样的用法。这样引入了更高的错误风险,当你需要修改时发现代码耦合程度比较高,同时也会让样式文件变大。

第二个问题是,你这么做的时候,其实你还是在用像素的思维在思考。虽然在代码里写的是1.4rem,但是在你的脑子里,其实还是想的是14px。在响应式网页开发中,你应该学会适应那些“模糊”的值。1.2em实际等于多少像素,并不重要,你只需要知道这是比继承的字号大一点点,那就足够了。而且,如果在屏幕上这不是你想要的效果,那就改吧。这是需要时间实验和试错的,但事实上,使用px的时候我们也需要这样做。(在第13章,我们会有更具体的方式来优化这个实现方式。)

当使用em时,我们很容易陷入纠结,这个值转化成像素值会是多少呢?尤其对于字号大小。你一直在乘和除以em值,这样你很快就会疯掉了。相反,我希望你可以接受一项挑战,尝试培养先开始使用em的习惯。如果你习惯使用像素,那转成em是需要一定时间和练习的,但相信我,这很值得。

这不是在说你再也不使用像素了。如果你跟一个设计师合作,你可能需要用更精确的像素值去沟通,这没问题的。在项目的开始,你需要声明一个基础的字号大小(通常是对标题或者标注的常用字号)。使用绝对值去描述大小,往往会更加容易。

转换成rem会有计算环节,那就让计算器去忙吧(通常我会在Mac电脑上按cmd+空格,在Spotlight里计算)。首先在根元素上声明根字号大小,从那开始,使用像素应该是例外的情况,而不是常态。

在这章内容里,我还是会持续地聊起像素。这会有助于我解释相对单位的工作原理,同时也能帮助你培养计算em值的习惯。在这章之后,我基本会使用相对单位来讨论字号的大小。

2.3.1 设置一个合理的字号默认值

先假设你想把默认字号设定为14px。把10px设定为基准值,再在页面中去覆盖它的写法,我们不推荐这种写法,相反,你应该在根元素上直接声明一个值。
在这个代码片段里,目标字号值是继承的,浏览器的默认值16px,那么14/16 = 0.875。

把下面的代码添加到一个新的样式表的最上面,我们会在这上面添加其他代码。这里设定根元素(<html>)的默认字号大小。

[ 代码片段 2.12 设定正确的默认字号大小 ]

:root {                    1
  font-size: 0.875em;      2
}
  • 1 或者使用 HTML 选择器
  • 2 14/16(期望值px / 继承值px)等于0.875

现在,你的期望基准字号14px对整个页面的元素有效,你不需要在其他地方重新声明了。你只需要在设计不一样的地方修改成新的字号,譬如标题。

我们一起来创建图2.7那样的面板吧!你创建的这个面板,基于14px字号,使用相对单位。

[ 图 2.7 使用相对单位和继承字号的面板 ]

"图 2.7"

下面是模板,加到你的页面吧。

[ 代码片段 2.13 面板的模板 ]

<div class="panel">
  <h2>Single-origin</h2>
  <div class="panel-body">
    We have built partnerships with small farms around the world to
    hand-select beans at the peak of season. We then carefully roast
    in <a href="/batch-size">small batches</a> to maximize their
    potential.
  </div>
</div>

下一段代码是样式的。你会在paddingborder-radius使用em,标题的字号使用rem,以及border使用px。把下面代码添加到你的样式表吧。

[ 代码片段 2.14 使用相对单位的面板 ]

.panel {
  padding: 1em;                    1
  border-radius: 0.5em;            1
  border: 1px solid #999;          2
}

.panel > h2 {
  margin-top: 0;                   3
  font-size: 0.8rem;               4
  font-weight: bold;               4
  text-transform: uppercase;       4
}
  • 1 对padding和border-radius使用em
  • 2 用1px定义细边框
  • 3 把面板上面多余的空间去掉,更多解释看第3章
  • 4 用rem控制标题的字号大小

这段代码给面板添加了一个细边框以及定义了标题的样式。我希望标题的字号小一点,但要加粗和全是大写。(你可以根据自己的设计,把字号改大点或者使用不同的排版方式)

第二个选择器>是一个直接后代组合选择符(direct descendant combinator),它代表的是.panel下的子元素h2。更完整的选择器和组合选择符的索引可以看附录A。

在代码片段2.13中,为了更清晰看到效果,我给body添加了一个类panel-body,不过你会发现,在你自己的代码里是不需要的。因为这个元素从根元素上继承了字号大小,它已经是你想要看到的那样。

2.3.3 让这个面板变得“响应式”

我们再更深入地看看这个问题。你可以根据屏幕尺寸的变化,添加媒体查询来改变基础字号大小,这可以令面板在不同尺寸的屏幕下,有不同的大小变化。(见图2.8)

[ 图 2.8 在不同屏幕大小的响应式面板:300px(左上角),800px(右上角),1440px(下面) ]

"图 2.8"

媒体查询(media query) —— 通过@media规则来声明样式,在不同的屏幕尺寸或者媒体类型(如打印机或显示器)下,触发对应的样式控制。这是响应式设计的关键要素。详情看代码片段2.15的这个例子,我在第8章会更深入的讨论媒体查询这个话题。

为了实现上面说的效果,把你的样式代码改成这样。

[ 代码片段 2.15 ]

:root {                            1
  font-size: 0.75em;               1
}                                  1

@media (min-width: 800px) {        2
  :root {                          2
    font-size: 0.875em;            2
  }                                2
}                                  2

@media (min-width: 1200px) {       3
  :root {                          3
    font-size: 1em;                3
  }                                3
}   
  • 1 针对所有屏幕,但是在更大的屏幕会被覆盖
  • 2 针对比800px更宽的屏幕,覆盖默认样式代码
  • 3 针对比1200px更宽的屏幕,覆盖以上两套样式代码

第一套样式规则,声明了小屏幕中的默认字号大小,这是我们想要在较小的屏幕上看到的字号大小。然后使用媒体查询,把800px和1200px分别作为两个分水岭逐级增加字号的大小,覆盖掉默认的代码。

针对页面的根元素使用这些字号大小,响应式地重新定义em和rem对应的值,从而达到响应改变整个页面的效果。尽管你没有直接对这个面板做任何的修改,它现在是响应式的。在小屏幕上,譬如一台手机,字号大小会被渲染成更小的(12px)。然后,在更大的屏幕上,宽大于800px和大于1200px的,组件的字号会分别放大到14px和16px。改变你的浏览器窗口,看看组件是怎么变化的吧。

如果你在整个页面中像这样严格使用相对单位,整个页面会随着视窗大小放大和缩小。这会是你的响应式策略里很重要的一部分。上面的2套媒体查询声明代码,可以帮助你节省在页面的其他部分使用媒体查询的额外代码。不过,如果你在元素中声明的字号大小是以像素为单位的,那就不会产生任何效果了。

类似地,如果你的老板或者客户觉得现在网站的字号太小或者太大,你随时可以做到通过修改一行代码影响到全局的元素,这项改变会影响到页面上的其他元素,不费吹灰之力。

2.3.3 调整单个组件的大小

你也可以通过使用em缩放页面上的一个独立组件。有时,你可能会需要界面上的某些组件可以有个大号的版本。在我们的面板上这么做吧,首先你需要给面板添加一个类名large<div class="panel large">

在图2.9,我们看到了面板的普通版和大号版的比较。效果类似响应式面板,但是两种尺寸是可以同时在同一个页面中使用的。

[ 图 2.9 在一个页面上的普通尺寸面板和大号面板 ]

"图 2.9"

我们来对面板的字号声明方式做一些小的修改。你还是在使用相对单位,但需要调整它们的基准值。第一点,给每个面板的父元素字号大小的定义font-size: 1rem。这里指的是,不管在什么地方使用这个面板,每个面板的字号大小是一个确定值。

第二点,使用em重新声明标题的字号大小,而不使用rem,这样标题就可以和刚才声明的父元素字号1rem关联起来。下面是对应的代码,更新下你的样式表代码吧。

[ 代码片段 2.16 创建一个面板的大号版本 ]

.panel {
  font-size: 1rem;               1
  padding: 1em;
  border: 1px solid #999;
  border-radius: 0.5em;
}

.panel > h2 {
  margin-top: 0;
  font-size: 0.8em;              2
  font-weight: bold;
  text-transform: uppercase;
}
  • 1 给组件声明确定的字号大小
  • 2 其他元素的字号大小用em和父元素字号关联

这些修改看起来并没有影响面板的样式,但是现在你已经准备好了,做一个大号的面板只需要修改一小行代码。你需要做的,就是把父元素字号大小改写成1rem以外的一个值。因为其他元素的计算方式都依赖父元素的字号大小,只要修改它,整个面板的相关尺寸都会发生改变。添加下一个CSS代码片段到你的样式表,定义一个大号面板吧。

[ 代码片段 2.17 利用一行代码放大整个面板 ]

.panel.large {               1
  font-size: 1.2rem;
}
  • 1 组合选择器指向同时有panel类和large类的元素

现在,你可以给普通面板添加class="panel"和给大号面板添加class="panel large"。类似地,你也可以定义一个小号版本,只需要把父元素的字号设得比1rem小。如果这个面板是一个更复杂的组件,包含多种字号大小或padding,也只需要一个声明就可以重置大小,只要所有的子元素都是使用em声明的。


《别说你懂CSS相对单位》系列译文:

  1. 如何更愉快地使用em
  2. 如何更愉快地使用rem [本文]
  3. 视口相关单位的应用
  4. 无单位数字和行高
  5. CSS自定义属性

章节:

  • 2.1 相对单位值的魔力

    • 2.1.1 完美像素设计(pixel-perfect design)的挣扎
    • 2.1.2 完美像素网页的终结
    • 像素(pixel)、点(point)和pc(pica)
  • 2.2 em和rem

    • 2.2.1 对font-size使用em

      • 当我们在一个元素内用em同时声明font-size和其他属性
      • 字号收缩问题
    • 2.2.2 对font-size使用rem

      • 可用性:对font-size使用相对长度单位
  • 2.3 停止使用像素思维去思考

    • 2.3.1 设置一个合理的字号默认值
    • 2.3.2 让这个面板变得“响应式”
    • 2.3.3 调整单个组件的大小
  • 2.4 视口相关单位(viewport-relative units)

    • CSS3
    • 2.4.1 在font-size上使用vw
    • 2.4.2 在font-size上使用calc()
  • 2.5 不带单位的数字(unitless number)和行高(line-height)
  • 2.6 自定义属性(也叫“CSS变量”)

    • 2.6.1 动态改变自定义属性的值
    • 2.6.2 通过JavaScript改变自定义属性的值
    • 2.6.3 初探自定义属性
  • 总结
原著版权信息:

作者:Keith J.Grant
书籍:CSS in Depth
章节:Working with relative units


笔者 @Yuying Wu,前端爱好者 / 鼓励师 / 新西兰打工度假 / 铲屎官。目前就职于某大型电商的B2B前端团队。

感谢你读到这里,对上文若有任何疑问或建议,欢迎留言。

如果你和我一样喜欢前端,喜欢捣腾独立博客或者前沿技术,或者有什么职业疑问,欢迎关注我以及各种交流哈。

独立博客:wuyuying.com
知乎ID:@Yuying Wu
Github:Yuying Wu

查看原文

赞 43 收藏 36 评论 3

认证与成就

  • 获得 339 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2017-01-17
个人主页被 1.6k 人浏览