├── .nojekyll
├── 03~性能优化
├── 内存优化
│ ├── README.md
│ ├── 内存使用分析.md
│ └── 内存泄露优化.md
├── 资源加载优化
│ ├── README.md
│ ├── 字体优化.md
│ ├── 预加载.md
│ ├── 设计优化.md
│ ├── 懒加载.md
│ └── 关键渲染路径.md
├── 渲染执行优化
│ ├── 渲染策略.md
│ ├── 布局与渲染策略.md
│ ├── 交互与动画.md
│ ├── README.md
│ ├── 脚本解析与执行.md
│ └── 资源请求与缓存.md
└── 图片优化
│ ├── Webp.md
│ ├── README.md
│ ├── 图片压缩.md
│ ├── 视频优化.md
│ ├── 图片格式与转换.md
│ └── 图片优化.md
├── 04~移动端与响应式
├── H5 优化
│ ├── README.md
│ ├── 用户体验.md
│ ├── 触摸滚动.md
│ └── 像素适配.md
├── 响应式适配
│ ├── README.md
│ ├── 响应式布局
│ │ ├── 长宽比.md
│ │ └── 响应式布局.md
│ ├── 响应式尺寸
│ │ ├── 基于 vw 的移动端适配.md
│ │ ├── 响应式尺寸.md
│ │ └── 屏幕尺寸.md
│ └── 媒介查询
│ │ ├── 媒介查询.md
│ │ └── 响应式设计.md
└── PWA
│ ├── 离线优先.md
│ ├── README.md
│ ├── 离线存储.md
│ ├── App Manifest.md
│ ├── ServiceWorker.md
│ └── Workbox.md
├── INTRODUCTION.md
├── 02~网页性能监控
├── 体验度量
│ ├── 可访问性.md
│ ├── Web Vitals
│ │ ├── README.md
│ │ └── 99~参考资料
│ │ │ └── 2021-Web Vitals:谷歌的新一代 Web 性能体验和质量指标.md
│ ├── RAIL
│ │ └── Google RAIL.md
│ ├── README.md
│ └── 兼容性.md
├── 异常监控与分析
│ ├── 监控体系.md
│ ├── 错误捕获.md
│ ├── 异常上报
│ │ ├── 使用 GIF 发送请求.md
│ │ └── README.md
│ ├── README.md
│ └── 常见异常.md
└── 99~参考资料
│ └── 2021-应用性能前端监控,字节跳动这些年经验都在这了.md
├── 01~浏览器工作机制
├── 05~渲染引擎
│ ├── RenderingNG.md
│ ├── 浏览器多进程架构
│ │ ├── README.md
│ │ ├── Edge 多进程架构.md
│ │ └── Chrome 多进程架构.md
│ └── 进程与线程架构.md
├── README.md
├── 03.事件处理
│ ├── 浏览器对事件的优化.md
│ └── README.md
├── 04.页面的生命周期
│ └── 页面的生命周期.md
├── 02.解析与渲染
│ └── 解析与渲染.md
└── 01.网络与资源请求
│ └── README.md
├── 05~录屏与重放
└── README.md
├── .gitignore
├── _sidebar.md
├── README.md
├── index.html
├── header.svg
└── 99~参考资料
└── De Voorhoede.md
/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/03~性能优化/内存优化/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/03~性能优化/资源加载优化/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/04~移动端与响应式/H5 优化/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/INTRODUCTION.md:
--------------------------------------------------------------------------------
1 | # 本篇导读
2 |
--------------------------------------------------------------------------------
/02~网页性能监控/体验度量/可访问性.md:
--------------------------------------------------------------------------------
1 | # 可访问性
2 |
--------------------------------------------------------------------------------
/02~网页性能监控/异常监控与分析/监控体系.md:
--------------------------------------------------------------------------------
1 | # 监控体系
2 |
--------------------------------------------------------------------------------
/03~性能优化/渲染执行优化/渲染策略.md:
--------------------------------------------------------------------------------
1 | # Virtual DOM
2 |
--------------------------------------------------------------------------------
/02~网页性能监控/体验度量/Web Vitals/README.md:
--------------------------------------------------------------------------------
1 | # Web Vitals
2 |
--------------------------------------------------------------------------------
/03~性能优化/图片优化/Webp.md:
--------------------------------------------------------------------------------
1 | # Webp
2 |
3 | # Links
4 |
5 | - https://www.jianshu.com/p/849058bc4855 浅谈Webp
--------------------------------------------------------------------------------
/03~性能优化/图片优化/README.md:
--------------------------------------------------------------------------------
1 | # Web 图片优化
2 |
3 | 图片一直是网站中容量占比最大的部分,合理优化图片容量将会大大提高用户的体验。同时,优化图片容量也会大大减少网站流量的消耗,达到节约流量费用的目的。
--------------------------------------------------------------------------------
/04~移动端与响应式/响应式适配/README.md:
--------------------------------------------------------------------------------
1 | # 响应式适配
2 |
3 | # Links
4 |
5 | - https://mp.weixin.qq.com/s/IFJyfOm4Sv4n4nWiS2BY3w
6 |
--------------------------------------------------------------------------------
/04~移动端与响应式/PWA/离线优先.md:
--------------------------------------------------------------------------------
1 | # 离线优先
2 |
3 | # Links
4 |
5 | - https://mp.weixin.qq.com/s/EpGQwGnUYZhmhSQk4DjJ7g 如何处理浏览器的断网情况?
6 |
--------------------------------------------------------------------------------
/03~性能优化/资源加载优化/字体优化.md:
--------------------------------------------------------------------------------
1 | # 字体优化
2 |
3 | # Links
4 |
5 | - https://web.dev/font-best-practices/ Optimize web fonts for Core Web Vitals.
6 |
--------------------------------------------------------------------------------
/03~性能优化/渲染执行优化/布局与渲染策略.md:
--------------------------------------------------------------------------------
1 | # 布局与渲染策略
2 |
3 | # 布局优化
4 |
5 | # Virtual DOM
6 |
7 | # 预加载
8 |
9 | # 懒加载
10 |
11 | # 按需加载
12 |
13 | # 分页加载
14 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/05~渲染引擎/RenderingNG.md:
--------------------------------------------------------------------------------
1 | # RenderingNG
2 |
3 | # Links
4 |
5 | - https://mp.weixin.qq.com/s/9W3WZZxNxuRsohNh2zSRwQ Chrome 的下一代 Web 渲染架构:RenderingNG
6 |
--------------------------------------------------------------------------------
/02~网页性能监控/异常监控与分析/错误捕获.md:
--------------------------------------------------------------------------------
1 | # 异常处理
2 |
3 | # Links
4 |
5 | - https://mp.weixin.qq.com/s/mEV6YLeILTQfghGW6lMWtw
6 |
7 | - https://mp.weixin.qq.com/s/KYwYoshrUWbwEgjB0HbF0Q
8 |
--------------------------------------------------------------------------------
/02~网页性能监控/体验度量/RAIL/Google RAIL.md:
--------------------------------------------------------------------------------
1 | # RAIL 性能指标
2 |
3 | # Links
4 |
5 | - https://sinaad.github.io/xfe/2016/04/22/the-rail-performance-model/ RAIL性能模型
6 |
7 | - https://juejin.cn/post/6850037273312886797 前端性能优化 - 用RAIL模型分析性能
--------------------------------------------------------------------------------
/03~性能优化/资源加载优化/预加载.md:
--------------------------------------------------------------------------------
1 | # 资源预加载
2 |
3 | # Links
4 |
5 | - https://blog.dteam.top/posts/2021-08/web-performance-preload-prefetch-preconnect.html?hmsr=toutiao.io&utm_campaign=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io 前端性能优化-preload,prefetch,preconnect
--------------------------------------------------------------------------------
/03~性能优化/内存优化/内存使用分析.md:
--------------------------------------------------------------------------------
1 | # 内存使用分析
2 |
3 | Chrome DevTools 控制台上有一个小众的 API 叫 queryObjects(),它可以从原型树上反查所有直接或间接的继承了某个对象的其它对象,比如 queryObjects(Array.prototype)可以拿到所有的数组对象,queryObjects(Object.prototype)则基本上可以拿到页面里的所有对象了(除了继承自 Object.create(null)的对象之外)。而且关键是这个 API 会在内存里搜索对象前先进行一次垃圾回收。
4 |
--------------------------------------------------------------------------------
/03~性能优化/图片优化/图片压缩.md:
--------------------------------------------------------------------------------
1 | # 图片压缩
2 |
3 | 对于网页图片用户往往并不在乎太高的质量,更关心的是加载速度,因此合理化压缩图片可以带来更好的用户体验。
4 |
5 | 在线压缩推荐 tinypng(https://tinypng.com/),同时支持 png 和 jpg 图片,使用简单,压缩质量好,建议图片放到版本库之前都先用 tinypng 压缩一遍。
6 |
7 | 如果本地需要批量压缩可以使用命令行工具,这里推荐下 pngquant(只支持 png)和 imgopt(同时支持 png 和 jpg),他们都可以很方便的在脚本中进行调用,进行快速批量化的图片压缩,注意合理设置一下量化质量,可以达到更好的压缩效果。
8 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/05~渲染引擎/浏览器多进程架构/README.md:
--------------------------------------------------------------------------------
1 | # 浏览器多进程架构
2 |
3 | 
4 |
5 | 这是整个浏览器的一个大概框架图,比如我开了 4 个标签页,整个浏览器是跑在 browser 进程里的,每个标签页都有自己的渲染进程。如果你想要安装一些扩展,比如说去广告的扩展,这些扩展会跑在扩展进程里。同时你看到这有一个广告,这个广告实际上是跑在一个独立的渲染进程里的,这主要是基于安全问题而考虑的。然后,中间视频需要编辑,可能要跑一个插件,那插件又有自己的进程,最左边就是我们关心的地方,这里有很多的视频,这些视频可能是 MP4 格式 x264 编码的,浏览器可能要借助显卡驱动来进行硬件解码,这些东西都会跑到 GPU 的进程里面去。
6 |
--------------------------------------------------------------------------------
/03~性能优化/渲染执行优化/交互与动画.md:
--------------------------------------------------------------------------------
1 | # 交互与动画
2 |
3 | ```js
4 | export default class LeashedOne extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.onChange = this.onChange.bind(this);
8 | this.onChangeDebounce = _.debounce(
9 | value => this.props.onChange(value),
10 | 300
11 | );
12 | }
13 | onChange(e) {
14 | this.onChangeDebounce(e.target.value);
15 | }
16 | render() {
17 | return ;
18 | }
19 | }
20 | ```
21 |
22 | # 页面帧率 FPS
23 |
24 | # 重渲染策略
25 |
--------------------------------------------------------------------------------
/04~移动端与响应式/PWA/README.md:
--------------------------------------------------------------------------------
1 | # Progressive Web Apps
2 |
3 | PWA 即 Progressive Web App,是谷歌于 2016 年在 Google I/O 大会上提出的下一代 Web App 概念,于 2017 年落地的 web 技术,旨在增强 Web 体验,缩小 Web App 与 Native App 的差距并创建类似的用户体验。理论上来说,对于所有 Web App,只要参考 PWA 的标准对其进行改造,就可实现 PWA,而对 App 来说,用户的体验是判断一个应用好坏的重要标准,PWA 的改造并不复杂,却可以很大程度上提升用户体验,是投入/产出比很高的一项技术,且 PWA 的相关功能和浏览器对 PWA 的支持程度也在不断地增加,未来还有很大的潜力。
4 |
5 | 市面上现存的 App 类型可以分为以下四种,我们比较了它们在不同指标上的表现,可以看到 PWA 本质上还是一个 Web App,但在表现上比 Web App 更加接近 Native App 。虽然我们在此与 Native App 进行了比较,但是 PWA 的目的并不是为了取代 Native App,也不是为了与 Native App 一较高下,而是对 Web App 的升级,是为了带给用户更好的用户体验。
6 |
7 | 
8 |
9 | # 离线优先
10 |
11 | # 交互友好
12 |
13 | # 原生体验
14 |
--------------------------------------------------------------------------------
/04~移动端与响应式/响应式适配/响应式布局/长宽比.md:
--------------------------------------------------------------------------------
1 | # 长宽比
2 |
3 | # postcss-aspect-ratio-mini
4 |
5 | ```css
6 | [aspectratio] {
7 | position: relative;
8 | }
9 | [aspectratio]::before {
10 | content: "";
11 | display: block;
12 | width: 1px;
13 | margin-left: -1px;
14 | height: 0;
15 | }
16 |
17 | [aspectratio-content] {
18 | position: absolute;
19 | top: 0;
20 | left: 0;
21 | right: 0;
22 | bottom: 0;
23 | width: 100%;
24 | height: 100%;
25 | }
26 | [aspectratio][aspect-ratio="188/246"] {
27 | aspect-ratio: "188:246";
28 | }
29 | ```
30 |
31 | 编译出来:
32 |
33 | ```css
34 | [aspectratio][aspect-ratio="188/246"]:before {
35 | padding-top: 130.85106382978725%;
36 | }
37 | ```
38 |
--------------------------------------------------------------------------------
/03~性能优化/渲染执行优化/README.md:
--------------------------------------------------------------------------------
1 | # 渲染与执行优化
2 |
3 | 自 Chrome 60 以来,V8 中的原始 JavaScript 解析速度提高了 2 倍。与此同时,原始解析(和编译)成本变得不那么明显/重要,因为 Chrome 中的其他优化工作将其并行化。
4 |
5 | V8 通过对工作人员进行解析和编译,将主线程上的解析和编译工作量平均减少了 40%(例如 Facebook 上为 46%,Pinterest 为 62%),最高改进率为 81%(YouTube)线。这是现有的离线主流线程解析/编译的补充。
6 |
7 | - SSR: Server-Side Rendering - rendering a client-side or universal app to HTML on the server.
8 |
9 | - CSR: Client-Side Rendering - rendering an app in a browser, generally using the DOM.
10 |
11 | - Rehydration: “booting up” JavaScript views on the client such that they reuse the server-rendered HTML’s DOM tree and data.
12 |
13 | - Prerendering: running a client-side application at build time to capture its initial state as static HTML.
14 |
--------------------------------------------------------------------------------
/02~网页性能监控/异常监控与分析/异常上报/使用 GIF 发送请求.md:
--------------------------------------------------------------------------------
1 | # 使用 GIF 发送请求
2 |
3 | 向服务器端上报数据,可以通过请求接口,请求普通文件,或者请求图片资源的方式进行。只要能上报数据,无论是请求 GIF 文件还是请求 js 文件或者是调用页面接口,服务器端其实并不关心具体的上报方式。那为什么所有系统都统一使用了请求 GIF 图片的方式上报数据呢?
4 |
5 | - 防止跨域
6 |
7 | 一般而言,打点域名都不是当前域名,所以所有的接口请求都会构成跨域。而跨域请求很容易出现由于配置不当被浏览器拦截并报错,这是不能接受的。但图片的 src 属性并不会跨域,并且同样可以发起请求。(排除接口上报)
8 |
9 | - 防止阻塞页面加载,影响用户体验
10 |
11 | 通常,创建资源节点后只有将对象注入到浏览器 DOM 树后,浏览器才会实际发送资源请求。反复操作 DOM 不仅会引发性能问题,而且载入 js/css 资源还会阻塞页面渲染,影响用户体验。
12 |
13 | 但是图片请求例外。构造图片打点不仅不用插入 DOM,只要在 js 中 new 出 Image 对象就能发起请求,而且还没有阻塞问题,在没有 js 的浏览器环境中也能通过 img 标签正常打点,这是其他类型的资源请求所做不到的。(排除文件方式)
14 |
15 | - 相比 PNG/JPG,GIF 的体积最小
16 |
17 | 最小的 BMP 文件需要 74 个字节,PNG 需要 67 个字节,而合法的 GIF,只需要 43 个字节。同样的响应,GIF 可以比 BMP 节约 41%的流量,比 PNG 节约 35%的流量。**并且大多采用的是 1\*1 像素的透明 GIF 来上报**
18 |
19 | 1x1 像素是最小的合法图片。而且,因为是通过图片打点,所以图片最好是透明的,这样一来不会影响页面本身展示效果,二者表示图片透明只要使用一个二进制位标记图片是透明色即可,不用存储色彩空间数据,可以节约体积。
20 |
--------------------------------------------------------------------------------
/03~性能优化/资源加载优化/设计优化.md:
--------------------------------------------------------------------------------
1 | # 性能调优始于设计
2 |
3 | 在前端项目中,我们常常与产品经理以及 UI 设计讨论如何在美感与性能之间达到平衡,我们坚信更快地内容呈现是好的用户体验的不可分割的一部分。在我们自己的网站中,我们是以性能优于美感。好的内容、布局、图片与交互都是构成你网站吸引力的不可或缺的部分,不过这些复杂的元素的使用往往也意味着页面加载速度的增加。设计的核心即在于决定我们网站需要呈现哪些内容,往往这里的内容会指图片、字体这样的偏静态的部分,我们首先也从对于静态内容的优化开始。
4 |
5 | ## Static Site Generator
6 |
7 | 为了演示与测试方便,我们基于 NodeJS 搭建了一个混合使用 MarkDown 与 JSON 作为配置的静态网站生成器,其中一个简单的博客类型的网站的配置信息如下:
8 |
9 | ```
10 | {
11 | "keywords": ["performance", "critical rendering path", "static site", "..."],
12 | "publishDate": "2016-08-12",
13 | "authors": ["Declan"]
14 | }
15 | ```
16 |
17 | 而其内容为:
18 |
19 | ```
20 | # A case study on boosting front-end performance
21 | At [De Voorhoede](https://www.voorhoede.nl/en/) we try to boost front-end performance...
22 | ## Design for performance
23 | In our projects we have daily discussions...
24 | ```
25 |
26 | 下面,我们就这个静态网站,进行一些讨论。
27 |
--------------------------------------------------------------------------------
/03~性能优化/图片优化/视频优化.md:
--------------------------------------------------------------------------------
1 | # 视频优化
2 |
3 | # GIF
4 |
5 | ```html
6 |
11 | ```
12 |
13 | Using the autoplay, loop, and muted attributes gives us the same behavior that we expect from a GIF. While serving a video is not as straightforward as serving a GIF, the file size savings can be tremendous. The following video is 103 KB in size, compared to 4.1 MB for the GIF that I converted it from. That's a size reduction of over 97%.
14 |
15 | To quickly convert a GIF into a video, there are plenty of websites that let you do a one-off conversion.To convert a GIF into a video file on the command line, I use FFmpeg.
16 |
17 | ```js
18 | $ ffmpeg -f gif -i dancing-baby.gif dancing-baby.mp4
19 | ```
--------------------------------------------------------------------------------
/05~录屏与重放/README.md:
--------------------------------------------------------------------------------
1 | # Web 系统中的录屏与重放
2 |
3 | 主流的录屏软件一般通过截获操作系统底层的 API 将绘图句柄或采集视讯端输出获得帧数据,再通过视频压缩技术将影像转为二进制文件进行存储及传输。但是这也随之带来以下几个问题:
4 |
5 | - 无法静默录屏:现有的录屏软件不支持通过 API 调用,同时需要在每一位客服小二端安装录屏软件或插件;
6 |
7 | - 操作系统及终端限制:录屏软件通常只适用于特定操作系统,无法适用于全平台操作系统,更无法用于移动设备;
8 |
9 | - 对性能及带宽影响:由于 MP4 格式需要利用 CPU 进行编码及压缩,同时需要将压缩后的数据实时上传至服务器因此对客户端的性能及带宽都有较明显的影响;
10 |
11 | - 全量录屏受制于存储及质量:MP4 格式的压缩及播放质量不可兼得,以 8 小时工作计算,即使用中低码率进行录制压缩,一名小二每天将产生 48 GB 以上录屏数据;
12 |
13 | - 不利于后续数据分析:视频影像数据不利于将来做大规模结构化分析及统计,更无法满足风控需求。
14 |
15 | 这一类产品利用 HTML 5 的 Mutation Observer 技术侦听 HTML DOM 的变更事件,同时通过截获 Document、Winodw 对象上的全局鼠标操作、滚动等事件捕获用户操作,然后再将事件拼接成 JSON 帧对象并通过 WebSocket 实时上传至服务器。播放时,客户端将自服务器传回的帧数据按照相对时间重新还原到新的 HTML DOM 中,从用户的视角看,就像真的录屏视频一样逼真。由于录制及播放的都是 HTML,因此网页录屏方案的还原度几乎可在 99% 以上,数据量也大幅降低。
16 |
17 | 但是这种录屏方案也存在着一些问题,例如时长限制。由于现有产品均为针对问题反馈、用户体验研究设计,最佳录制时长在 5 分钟以内。其根本原因是此类录屏记录的是“单向增量数据”,每一帧的播放都前项依赖于此前所有的帧数据表现,每一次向前跳转时均需要将此前所有事件完整的执行一遍,这使得长视频的随机跳转几乎成为不可能。
18 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/README.md:
--------------------------------------------------------------------------------
1 | # 浏览器工作机制
2 |
3 | 
4 |
5 | 本节我们将会简要分析下浏览器的工作机制与原理,首先,从用户数据流的角度来看,整个交互与产生的数据包包含以下流程:
6 |
7 | - 用户在浏览器地址栏中输入一个 URL。
8 | - 浏览器从 URL 中获取域名,并从 DNS 中请求服务器的 IP 地址。
9 | - 浏览器创建一个 HTTP 包,表示它请求一个位于远程服务器上的网页。
10 | - 该数据包被发送到 TCP 层,TCP 层在 HTTP 数据包的基础上添加自己的信息。这些信息是维持启动的会话所需要的。
11 | - 然后,数据包被交给 IP 层,IP 层的主要工作是想办法把数据包从你这里发送到远程服务器。这些信息也被存储在数据包之上。
12 | - 数据包被发送到远程服务器。
13 | - 一旦收到数据包,响应也会以类似的方式发回来。
14 |
15 | 基于以上主要的流程,我们可以梳理出浏览器需要包含以下关键组成:
16 |
17 | 
18 |
19 | - 用户界面: 这包括地址栏、后退和前进按钮、书签菜单等。从本质上讲,这是浏览器显示的除了你看到网页本身的窗口之外的部分。
20 | - 浏览器引擎: 处理用户界面和渲染引擎之间的交互。
21 | - 渲染引擎: 它负责显示网页。渲染引擎解析 HTML 和 CSS,并将解析后的内容显示在屏幕上。
22 | - 网络: 这些都是网络调用,如 XHR 请求,通过对不同平台使用不同的实现来实现,这些都是在一个平台独立的接口后面。
23 | - UI 后端: 它用于绘制核心小组件,如复选框和窗口。这个后端暴露了一个不针对平台的通用接口。它在下面使用操作系统的 UI 方法。
24 | - JavaScript 引擎: 即 JavaScript 解析与执行的引擎。
25 | - 数据持久层: 你的应用程序可能需要在本地存储所有数据,譬如 localStorage, indexDB, WebSQL, FileSystem。
26 |
27 | # Links
28 |
29 | - https://mp.weixin.qq.com/s/CPqNwUMRaKgW-7WFrq9jSg
30 |
--------------------------------------------------------------------------------
/03~性能优化/图片优化/图片格式与转换.md:
--------------------------------------------------------------------------------
1 | # 图片格式与转换
2 |
3 | 图片格式繁多,不同的浏览器对图片支持也略有不同,概览表格可以参见MDN 图像文件类型与格式指南。为了最大限度的通用性,网站通常只考虑以下三种图片: png, jpg, gif(未来如果 webp 能大一统基本不做考虑了)。
4 |
5 | - png: 优点:无损压缩,质量最好,支持透明背景。缺点:对于色彩丰富的图片容量压缩效果差。推荐场景:有透明背景、颜色简单、对细节质量要求较高的场景。如结构图(架构图、流程图、思维导图等等)、Logo、桌面截图、二维码、条码等等。这些场景绝大多数情况下用 png 会比 jpg 容量更小。
6 |
7 | - jpg: 优点:采用有损压缩算法,压缩率较高,支持渐进式加载。缺点:图像细节损失,对于文字可能边缘模糊看不清,不支持透明背景。推荐场景:照片、背景图等总体分辨率较大,对细节要求不高,只关注总体风貌的图片。
8 |
9 | - gif: 优点:采用伪色,容量最小,加载最快,这三种格式中唯一支持动图。缺点:色彩细节丢失严重。推荐场景:网站 Logo 图,动图等。
10 |
11 | # webp
12 |
13 | ## 利用阿里云 CDN 边缘脚本实现自适应转换 webp
14 |
15 | 理论上完全可以在浏览器端添加一个 js,自动判断浏览器是否支持 webp,决定访问 webp 图片或者原图。不过这样在本地的工作量就太大了,每次添加图片还得多转换一个 webp 图片,本地的工作量太大了。我们可以利用阿里云 OSS 的图片转换功能,以及 CDN 的边缘脚本实现浏览器自动使用 webp 图片的功能,本地不作任何处理,不影响原来的开发流程。
16 |
17 | ```java
18 | m1 = and($http_accept, match_re($http_accept, '.*image\/webp.*'))
19 | m2 = match_re($uri, '.+(.JPEG|.jpeg|.JPG|.jpg|.PNG|.png)$')
20 | uri_is_posts = match_re($uri, '^/posts')
21 |
22 | if and(m1, m2) {
23 | if uri_is_posts {
24 | rewrite(concat($uri, '!webp_watermark'), 'break')
25 | } else {
26 | rewrite(concat($uri, '!webp'), 'break')
27 | }
28 | }
29 | ```
--------------------------------------------------------------------------------
/01~浏览器工作机制/03.事件处理/浏览器对事件的优化.md:
--------------------------------------------------------------------------------
1 | # 浏览器对事件的优化
2 |
3 | ## 最小化发送给主线程的事件数
4 |
5 | 一般我们屏幕的帧率是每秒 60 帧,也就是 60fps,但是某些事件触发的频率超过了这个数值,比如 wheel,mousewheel,mousemove,pointermove,touchmove,这些连续性的事件一般每秒会触发 60~120 次,假如每一次触发事件都将事件发送到主线程处理,由于屏幕的刷新速率相对来说较低,这样使得主线程会触发过量的命中测试以及 JS 代码,使得性能有了没必要是损耗。
6 |
7 | 
8 |
9 | 出于优化的目的,浏览器会合并这些连续的事件,延迟到下一帧渲染是执行,也就是 requestAnimationFrame 之前。
10 |
11 | 
12 |
13 | 而对于非连续性的事件,如 keydown,keyup,mousedown,mouseup,touchstart,touchend 等,会直接派发给主线程去执行。
14 |
15 | ## 使用 getCoalesecedEvents 来获取帧内(intra-frame)事件
16 |
17 | 对于大多数 web 应用来说,合并事件应该已经足够用来提供很好的用户体验了,然而,如果你正在构建的是一个根据用户的 touchmove 坐标来进行绘图的应用的话,合并事件可能会使页面画的线不够顺畅和连续。在这种情况下,你可以使用鼠标事件的 getCoalescedEvents 来获取被合成的事件的详细信息。
18 |
19 | 
20 |
21 | ```js
22 | window.addEventListener("pointermove", (event) => {
23 | const events = event.getCoalescedEvents();
24 | for (let event of events) {
25 | const x = event.pageX;
26 | const y = event.pageY;
27 | // draw a line using x and y coordinates.
28 | }
29 | });
30 | ```
31 |
--------------------------------------------------------------------------------
/02~网页性能监控/异常监控与分析/README.md:
--------------------------------------------------------------------------------
1 | # 前端监控
2 |
3 | 前端质量监控包括:PC Web 监控、H5 监控、RN/Weex 监控、小程序监控等。本章主要围绕 Web 技术简介常用的客户端监控系统的设计与实现。
4 |
5 | 
6 |
7 | 前端监控往往是承接自端到端测试,譬如合成测试监控(Synthetic Tests),所谓合成测试监控(SYN)就是在一个模拟场景里提交一个需要做质量审计的页面,通过一系列的工具、规则去运行你的页面,提取一些质量指标,并最终得出一份审计报告。最典型的合成测试监控工具就是大名鼎鼎的 Google Lighthouse。
8 |
9 | 
10 |
11 | 合成测试监控可以帮助我们发现一些明显的前端质量问题(如图片未压缩、没有设置合理的静态资源缓存策略、页面是否可访问等),在分布式部署的前提下还可以将上述能力扩展到全球多个节点同时监控。由于是模拟运行环境,并不能反映用户真实的数据,因此在做前端监控时,一般还会搭配真实用户监控能力一起实现。
12 |
13 | 真实用户监控(Real User Monitoring)所谓真实用户监控(RUM),就是用户在我们的页面上访问,访问过程中就会产生各种各样的指标,我们在用户访问结束的时候,把这些指标上传到我们的日志服务器上,进行数据的提取清洗加工,最后在监控平台上进行展示的一个过程。RUM 很好的补充了合成测试监控因为模拟导致的无法还原真实场景的问题。对于访问量比较大的业务(或页面)来说,通过 RUM 获取到的指标基本可以客观反映该业务(或页面)的线上质量情况。当然,RUM 也不是完美无缺的。因为数据需要在用户访问页面的过程中采集,那数据传输在某些性能和带宽比较敏感的场景就不能采集并上传大量的数据。
14 |
15 | # 监控的指标与维度
16 |
17 | 前端质量监控平台一般从页面性能(测速)、页面稳定性(JS Error)和外部服务调用成功率(API)这三个方面监测页面的健康度。
18 |
19 | - JS 错误:错误名称、错误堆栈、JS 错误数量、JS 错误率、影响用户数、高频错误。
20 |
21 | - 页面性能指标:首次渲染、首屏时间、首次可交互、DOM Ready、页面完全加载时间、DNS/TCP 连接等区间耗时更详细的 Web 性能指标可以参阅[性能优化](https://ngte-web.gitbook.io/i/?性能优化)章节。
22 |
23 | - API:API 名称、API 成功率、常见 API 返回码、API 请求耗时等。
24 |
25 | 通常的分布维度包括但不限于地理、设备、浏览器、网络、应用版本等。
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore all
2 | *
3 |
4 | # Unignore all with extensions
5 | !*.*
6 |
7 | # Unignore all dirs
8 | !*/
9 |
10 | .DS_Store
11 |
12 | # Logs
13 | logs
14 | *.log
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 | # Runtime data
20 | pids
21 | *.pid
22 | *.seed
23 | *.pid.lock
24 |
25 | # Directory for instrumented libs generated by jscoverage/JSCover
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 | coverage
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (https://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 | jspm_packages/
49 |
50 | # TypeScript v1 declaration files
51 | typings/
52 |
53 | # Optional npm cache directory
54 | .npm
55 |
56 | # Optional eslint cache
57 | .eslintcache
58 |
59 | # Optional REPL history
60 | .node_repl_history
61 |
62 | # Output of 'npm pack'
63 | *.tgz
64 |
65 | # Yarn Integrity file
66 | .yarn-integrity
67 |
68 | # dotenv environment variables file
69 | .env
70 |
71 | # next.js build output
72 | .next
73 |
--------------------------------------------------------------------------------
/04~移动端与响应式/H5 优化/用户体验.md:
--------------------------------------------------------------------------------
1 | # 页面生成图片与二维码
2 |
3 | 在工作中有需要将页面生成图片或者二维码的需求。可能我们第一想到的,交给后端来生成更简单。但是这样我们需要把页面代码全部传给后端,网络性能消耗太大。使用 QRCode 生成二维码
4 |
5 | ```js
6 | import QRCode from "qrcode";
7 | // 使用 async 生成图片
8 | const options = {};
9 | const url = window.location.href;
10 | async (url) => {
11 | try {
12 | console.log(await QRCode.toDataURL(url, options));
13 | } catch (err) {
14 | console.error(err);
15 | }
16 | };
17 | ```
18 |
19 | 将 `await QRCode.toDataURL(url, options)` 赋值给 图片 `url` 即可;对于生成图片主要是使用 `htmlToCanvas` 生成 `canvas` 画布
20 |
21 | ```js
22 | import html2canvas from "html2canvas";
23 |
24 | html2canvas(document.body).then(function (canvas) {
25 | document.body.appendChild(canvas);
26 | });
27 | ```
28 |
29 | 但是不单单在此处就完了,由于是 `canvas` 的原因。移动端生成出来的图片比较模糊。我们使用一个新的 `canvas` 方法多倍生成,放入一倍容器里面,达到更加清晰的效果,通过超链接下载图片:
30 |
31 | ```js
32 | const scaleSize = 2;
33 | const newCanvas = document.createElement("canvas");
34 | const target = document.querySelector('div');
35 | const width = parseInt(window.getComputedStyle(target).width);
36 | const height = parseInt(window.getComputedStyle(target).height);
37 | newCanvas.width = width * scaleSize;
38 | newCanvas.height = widthh * scaleSize;
39 | newCanvas.style.width = width + "px";
40 | newCanvas.style.height =width + "px";
41 | const context = newCanvas.getContext("2d");
42 | context.scale(scaleSize, scaleSize);
43 | html2canvas(document.querySelector('.demo'), { canvas: newCanvas }).then(function(canvas) {
44 | // 简单的通过超链接设置下载功能
45 | document.querySelector(".btn").setAttribute('href', canvas.toDataURL());
46 | }
47 | ```
48 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/05~渲染引擎/进程与线程架构.md:
--------------------------------------------------------------------------------
1 | # 浏览器的进程与线程架构
2 |
3 | # CPU,GPU,进程与线程
4 |
5 | CPU 是计算机里面的一块芯片,上面有一个或者多个核心(core)。我们可以把 CPU 的一个核心(core)比喻成一个办公室工人,他功能强大,上知天文下知地理,琴棋书画无所不能,它可以串行地一件接着一件处理交给它的任务。很久之前的时候大多数 CPU 只有一个核心,不过在现在的硬件设备上 CPU 通常会有多个核心,因为多核心 CPU 可以大大提高手机和电脑的运算能力。
6 |
7 | 
8 |
9 | 图形处理器或者说 GPU(Graphics Processing Unit)是计算机的另外一个重要组成部分。和功能强大的 CPU 核心不一样的是,单个 GPU 核心只能处理一些简单的任务,不过它胜在数量多,单片 GPU 上会有很多很多的核心可以同时工作,也就是说它的并行计算能力是非常强的。图形处理器(GPU)顾名思义一开始就是专门用来处理图形的,所以在说到图形使用 GPU(using)或者 GPU 支持(backed)时,人们就会联想到图形快速渲染或者流畅的用户体验相关的概念。最近几年来,随着 GPU 加速概念的流行,在 GPU 上单独进行的计算也变得越来越多了。
10 |
11 | 
12 |
13 | 当你在手机或者电脑上打开某个应用程序的时候,背后其实是 CPU 和 GPU 支撑着这个应用程序的运行。通常来说,你的应用要通过操作系统提供的一些机制才能跑在 CPU 和 GPU 上面。
14 |
15 | 
16 |
17 | 就像我们在 [《Linux-Notes](https://github.com/wx-chevalier/Linux-Notes?q=)》中介绍的进程与线程之间的关系,进程(process)是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,线程(thread)是 CPU 调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。浏览器属于一个应用程序,而应用程序的一次执行,可以理解为计算机启动了一个进程,进程启动后,CPU 会给该进程分配相应的内存空间,当我们的进程得到了内存之后,就可以使用线程进行资源调度,进而完成我们应用程序的功能。
18 |
19 | 而在应用程序中,为了满足功能的需要,启动的进程会创建另外的新的进程来处理其他任务,这些创建出来的新的进程拥有全新的独立的内存空间,不能与原来的进程内向内存,如果这些进程之间需要通信,可以通过 IPC 机制(Inter Process Communication)来进行。
20 |
21 | 
22 |
23 | 那么浏览器是怎么使用进程和线程来工作的呢?其实大概可以分为两种架构,一种是单进程架构,也就是只启动一个进程,这个进程里面有多个线程工作。第二种是多进程架构,浏览器会启动多个进程,每个进程里面有多个线程,不同进程通过 IPC 进行通信。
24 |
25 | 
26 |
27 | 上面的图表架构其实包含了浏览器架构的具体实现了,在现实中其实并没有一个大家都遵循的浏览器实现标准,所以不同浏览器的实现方式可能会完全不一样。
28 |
--------------------------------------------------------------------------------
/02~网页性能监控/体验度量/README.md:
--------------------------------------------------------------------------------
1 | # 性能监控与度量
2 |
3 | TTFB: Time to First Byte - seen as the time between clicking a link and the first bit of content coming in.
4 |
5 | FP: First Paint - the first time any pixel gets becomes visible to the user.
6 |
7 | FCP: First Contentful Paint - the time when requested content (article body, etc) becomes visible.
8 |
9 | TTI: Time To Interactive - the time at which a page becomes interactive (events wired up, etc).
10 |
11 | 在构建 Web 站点的过程中,任何一个细节都有可能影响网站的访问速度。如果开发人员不了解前端性能相关知识,很多不利网站访问速度的因素会在线上形成累加,从而严重影响网站的性能,导致网站访问速度变慢、用户体验低下,最终导致用户流失。页面性能对网页而言,可谓举足轻重。因此,对页面的性能进行检测分析,是开发者不可忽视的课题。那么我们如何对页面进行监控分析及性能评判?对性能评判的规则又是什么样的呢?
12 |
13 | 从技术方面来讲,前端性能监控主要分为两种方式,一种叫做合成监控(Synthetic Monitoring,SYN),另一种是真实用户监控(Real User Monitoring,RUM)。
14 |
15 | - 合成监控,就是在一个模拟场景里,去提交一个需要做性能检测的页面,通过一系列的工具、规则去运行你的页面,提取一些性能指标,得出一个性能报告。
16 |
17 | - 真实用户监控,就是用户在我们的页面上浏览,浏览过程就会产生各种各样的性能数据,我们把这些性能数据上传到我们的日志服务器上,进行数据的提取清洗加工,最后在我们的监控平台上进行展示的一个过程。
18 |
19 | 前者注重“检测”,后者注重“监控”。
20 |
21 | 在性能优化之前,我们首先需要对性能评测的指标与常见的监控、审计方法有所了解。
22 |
23 | 这里我们统一地对于性能评测的工具与量化指标进行讨论,而后续文章中提到的很多优化点也可以作为评测的指标之一。
24 |
25 | 从技术方面来讲,前端性能监控主要分为两种方式,一种叫做合成监控(Synthetic Monitoring,SYN),另一种是真实用户监控(Real User Monitoring,RUM)。合成监控就是在一个模拟场景里,去提交一个需要做性能审计的页面,通过一系列的工具、规则去运行你的页面,提取一些性能指标,得出一个审计报告。合成监控中最近比较流行的是 Google 的 Lighthouse,下面我们就以 Lighthouse 为例。
26 |
27 | 
28 |
29 | 合成监控相对实现简单,并且流程可控,在不影响真实用户访问性能的情况下能够采集到更丰富的数据。不过合成监控很难还原全部用户场景,并且需要额外解决登录等复杂场景,其采集到的数据量也相对较少。所谓真实用户监控,就是用户在我们的页面上访问,访问之后就会产生各种各样的性能指标,我们在用户访问结束的时候,把这些性能指标上传到我们的日志服务器上,进行数据的提取清洗加工,最后在我们的监控平台上进行展示的一个过程。
30 |
31 | # Links
32 |
33 | - https://zhuanlan.zhihu.com/p/82981365 10 分钟彻底搞懂前端页面性能监控
34 |
--------------------------------------------------------------------------------
/03~性能优化/资源加载优化/懒加载.md:
--------------------------------------------------------------------------------
1 | # 元素懒加载
2 |
3 | Web pages often contain a large number of images, which contribute to data-usage, page-bloat and how fast a page can load. Many of these images are offscreen, requiring a user to scroll in order to view them.
4 |
5 | Historically, to limit the impact offscreen images have on page load times, developers have needed to use a JavaScript library (like LazySizes) in order to defer fetching these images until a user scrolls near them.
6 |
7 | 
8 |
9 | Chrome 75 之后,浏览器原生地支持了 lazy 标签:
10 |
11 | ```html
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
37 |
38 |
39 |
40 | ```
41 |
--------------------------------------------------------------------------------
/04~移动端与响应式/响应式适配/响应式布局/响应式布局.md:
--------------------------------------------------------------------------------
1 | # 响应式布局
2 |
3 | 未来的布局方式,可能不同于现有的 Flexbox 与 CSS Grid 这样的从上到下的流式布局,随着 Resize Observer API 这类新的 DOM API,我们未来可能会出现响应式布局或者其他关系型布局。比如写布局时的逻辑可能是“模式匹配”的,当我独占一行时,我怎么布局;当我左侧有某个元素时,我怎么布局。
4 | 比如用 react 写一个瀑布流时,可以这样做:我是一个组件,当我 didMount 时,我获取我顶上的元素的位置 position,然后加上一个间距 padingTop,就得到了我自己的位置。
5 |
6 | # Auto Resize: 自动缩放
7 |
8 | ## CSS Responsive Grid System
9 |
10 | ## Flexbox: 动态分配空间
11 |
12 | ## Proportional Scale: 按比例缩放
13 |
14 | # Auto Layout 自动布局
15 |
16 | ## Flexbox:flex-wrap
17 |
18 | ### Bugs
19 |
20 | 在 iOS 中有时候会发现如果没有设置 flex 元素的尺寸,只是设置了最大值或者最小值会导致计算是否需要换行出现错误,可以参考[row-wrap-in-flex-box-not-wrapping-in-safari](http://stackoverflow.com/questions/25360526/row-wrap-in-flex-box-not-wrapping-in-safari/30792851#30792851)这个问题,即如果我们的样式是:
21 |
22 | ```css
23 | div.flex {
24 | display: -webkit-flex;
25 | display: flex;
26 | -webkit-flex-wrap: wrap;
27 | flex-wrap: wrap;
28 | -webkit-flex-direction: row;
29 | flex-direction: row;
30 | }
31 |
32 | div.flex .item {
33 | min-width: 15em;
34 | -webkit-flex: 1;
35 | flex: 1;
36 | }
37 | ```
38 |
39 | 这样的 item 是不会自动换行的,而需要显式的指明 item 的尺寸:
40 |
41 | ```css
42 | div.flex {
43 | display: -webkit-flex;
44 |
45 | display: flex;
46 | -webkit-flex-wrap: wrap;
47 |
48 | flex-wrap: wrap;
49 | -webkit-flex-direction: row;
50 |
51 | flex-direction: row;
52 | }
53 |
54 | div.flex .item {
55 | min-width: 15em;
56 | -webkit-flex: 1 1 15em; /* this */
57 |
58 | flex: 1;
59 | }
60 | ```
61 |
62 | 该 Bug 已经提交到了在[Safari (WebKit) doesn't wrap element within flex when width comes below min-width](https://bugs.webkit.org/show_bug.cgi?id=136041),可以使用 iOS 打开[CodePen](http://codepen.io/philipwalton/pen/BNrGwN)来查看问题复现。
63 |
64 | 不过笔者在实际使用 flex-wrap 属性的时候发现在 iPhone 5/5s(iOS 8/9)上出现了一个很奇怪的错误,详情可见笔者在 StackOverflow 上提出的[flex-wrap-doesnt-work-in-iphone5-ios-8](http://stackoverflow.com/questions/38365121/flex-wrap-doesnt-work-in-iphone5-ios-8)问题。
65 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/03.事件处理/README.md:
--------------------------------------------------------------------------------
1 | # 浏览器中事件处理
2 |
3 | 当页面渲染完毕以后,Tab 内已经显示出了可交互的 Web 页面,用户可以进行移动鼠标、点击页面等操作了,而当这些事件发生时候,浏览器是如何处理这些事件的呢?以点击事件(click event)为例,让鼠标点击页面时候,首先接受到事件信息的是 Browser Process,但是 Browser Process 只知道事件发生的类型和发生的位置,具体怎么对这个点击事件进行处理,还是由 Tab 内的 Renderer Process 进行的。Browser Process 接受到事件后,随后便把事件的信息传递给了渲染进程,渲染进程会找到根据事件发生的坐标,找到目标对象(target),并且运行这个目标对象的点击事件绑定的监听函数(listener)。
4 |
5 | 
6 |
7 | 
8 |
9 | # 渲染进程中合成器线程接收事件
10 |
11 | 前面我们说到,合成器线程可以独立于主线程之外通过已光栅化的层创建组合帧,例如页面滚动,如果没有对页面滚动绑定相关的事件,组合器线程可以独立于主线程创建组合帧,如果页面绑定了页面滚动事件,合成器线程会等待主线程进行事件处理后才会创建组合帧。那么,合成器线程是如何判断出这个事件是否需要路由给主线程处理的呢?
12 |
13 | 由于执行 JS 是主线程的工作,当页面合成时,合成器线程会标记页面中绑定有事件处理器的区域为非快速滚动区域(non-fast scrollable region),如果事件发生在这些存在标注的区域,合成器线程会把事件信息发送给主线程,等待主线程进行事件处理,如果事件不是发生在这些区域,合成器线程则会直接合成新的帧而不用等到主线程的响应。
14 |
15 | 
16 |
17 | 而对于非快速滚动区域的标记,开发者需要注意全局事件的绑定,比如我们使用事件委托,将目标元素的事件交给根元素 body 进行处理,代码如下:
18 |
19 | ```js
20 | document.body.addEventListener("touchstart", (event) => {
21 | if (event.target === area) {
22 | event.preventDefault();
23 | }
24 | });
25 | ```
26 |
27 | 在开发者角度看,这一段代码没什么问题,但是从浏览器角度看,这一段代码给 body 元素绑定了事件监听器,也就意味着整个页面都被编辑为一个非快速滚动区域,这会使得即使你的页面的某些区域没有绑定任何事件,每次用户触发事件时,合成器线程也需要和主线程通信并等待反馈,流畅的合成器独立处理合成帧的模式就失效了。
28 |
29 | 
30 |
31 | 其实这种情况也很好处理,只需要在事件监听时传递 passtive 参数为 true,passtive 会告诉浏览器你既要绑定事件,又要让组合器线程直接跳过主线程的事件处理直接合成创建组合帧。
32 |
33 | ```js
34 | document.body.addEventListener(
35 | "touchstart",
36 | (event) => {
37 | if (event.target === area) {
38 | event.preventDefault();
39 | }
40 | },
41 | { passive: true }
42 | );
43 | ```
44 |
45 | # 查找事件的目标对象(event target)
46 |
47 | 当合成器线程接收到事件信息,判定到事件发生不在非快速滚动区域后,合成器线程会向主线程发送这个时间信息,主线程获取到事件信息的第一件事就是通过命中测试(hit test)去找到事件的目标对象。具体的命中测试流程是遍历在绘制阶段生成的绘画记录(paint records)来找到包含了事件发生坐标上的元素对象。
48 |
49 | 
50 |
--------------------------------------------------------------------------------
/04~移动端与响应式/PWA/离线存储.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 离线存储
4 |
5 | 我们不可以在 Service Worker 中使用 Local Storage,并且 Service Worker 不可以使用任何的同步 API,不过可以使用 IndexDB,CacheAPI,或者利用 postMessage() 与界面进行交互。参考 [GoogleChromeLabs/airhorn](https://github.com/GoogleChromeLabs/airhorn),可以使用如下的代码注册 ServiceWorker,并且使用 Cache API 来进行资源与请求缓存,首先需要注册 ServiceWorker:
6 |
7 | ```js
8 | if (‘serviceWorker’ in navigator) {
9 | window.addEventListener(‘load’, function() {
10 | navigator.serviceWorker.register(‘/sw.js’).then(
11 | function(registration) {
12 | // Registration was successful
13 | console.log(‘ServiceWorker registration successful with scope: ‘, registration.scope); },
14 | function(err) {
15 | // registration failed :(
16 | console.log(‘ServiceWorker registration failed: ‘, err);
17 | });
18 | });
19 | }
20 | ```
21 |
22 | 然后在 sw.js 文件中缓存资源:
23 |
24 | ```js
25 | self.addEventListener("install", (e) => {
26 | let timeStamp = Date.now();
27 | e.waitUntil(
28 | caches.open("airhorner").then((cache) => {
29 | return cache
30 | .addAll([
31 | `/`,
32 | `/index.html?timestamp=${timeStamp}`,
33 | `/styles/main.css?timestamp=${timeStamp}`,
34 | `/scripts/main.min.js?timestamp=${timeStamp}`,
35 | `/scripts/comlink.global.js?timestamp=${timeStamp}`,
36 | `/scripts/messagechanneladapter.global.js?timestamp=${timeStamp}`,
37 | `/sounds/airhorn.mp3?timestamp=${timeStamp}`,
38 | ])
39 | .then(() => self.skipWaiting());
40 | })
41 | );
42 | });
43 |
44 | self.addEventListener("activate", (event) => {
45 | event.waitUntil(self.clients.claim());
46 | });
47 |
48 | self.addEventListener("fetch", (event) => {
49 | event.respondWith(
50 | caches.match(event.request, { ignoreSearch: true }).then((response) => {
51 | // 默认不会保护 Cookie,可以使用 fetch(url, {credentials: 'include'}) 来发送 Cookie
52 | return response || fetch(event.request);
53 | })
54 | );
55 | });
56 | ```
57 |
--------------------------------------------------------------------------------
/03~性能优化/图片优化/图片优化.md:
--------------------------------------------------------------------------------
1 | # 图片优化
2 |
3 | # JPEG
4 |
5 | # WebP
6 |
7 | ## Image Delivery
8 |
9 | 图片是网站的不可或缺的部分,其能够大大提升网站的表现力与视觉效果,而目前[平均大小为 2406KB 的网页中就有 1535KB 是图片资源](http://httparchive.org/interesting.php?a=All&l=Jul%2015%202016),可见图片占据了静态资源多么大的一个比重,这也是我们需要重点优化的部分。
10 | 
11 |
12 | ### WebP
13 |
14 | [WebP](https://developers.google.com/speed/webp/) 是面向现代网页的高压缩低损失的图片格式,通常会比 JPEG 小 25%左右。然后 WebP 目前被很多人忽视,也不常使用。截止到本文撰写的时候,WebP 目前只能够在[Chrome, Opera and Android](http://caniuse.com/#feat=webp) (大概占用户数的 50%)这些浏览器中使用,不过我们还是有办法以 JPG/PNG 来弥补部分浏览器中不支持 WebP 的缺憾。
15 |
16 | ### `picture`标签
17 |
18 | 使用 picture 标签可以方便的对于 WebP 格式不支持的情况下完成替换:
19 |
20 | ```
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ```
31 |
32 | 这里我们使用了 [picturefill by Scott Jehl](https://github.com/scottjehl/picturefill)作为 Polyfill 库来保证低版本的浏览器中能够支持 picture 标签,并且保证跨浏览器的功能一致性。并且我们还使用了 img 标签来保证那些不支持 picture 的浏览器能够正常工作。
33 |
34 | ### 图片多格式生成
35 |
36 | 现在我们已经可以通过设置不同的图片尺寸、格式来保证图片的分发优化,不过我们总不希望每次要用一张图片的时候就去生成 6 个不同的尺寸/实例。我们希望有一种抽象的方法可以帮我们自动完成这一步,为我们自动生成不同的格式/尺寸,然后自动插入合适的 picture 元素,在我们的静态网站生成器中是这么做的:
37 |
38 | - 首先是要[gulp responsive](https://github.com/mahnunchik/gulp-responsive)来生成不同尺寸的图片,该插件同样会输出 WebP 格式的图片
39 | - 压缩生成好的图片
40 | - 用户只需要在 MarkDown 中编写``即可
41 | - 我们自定义的 MarkDown 渲染引擎会在处理过程中自动使用 picture 元素替换这些 img 标签
42 |
43 | ## SVG Animation
44 |
45 | 我们的网站中也存在着很多的 Icon 以及动画性质图片,这里我们是选择 SVG 作为 Icon 与 Animation 的格式,主要考虑有下:
46 |
47 | - SVG 是矢量表示,往往比位图文件更小
48 | - SVG 自带响应式功效,能够根据容器大小进行自动缩放,因此我们不需要再为了 picture 元素生成不同尺寸的图片
49 | - 最重要的一点是我们可以使用 CSS 去改变其样式或者添加动画效果,关于这一点可以参考[CodePen 上的这个演示](https://codepen.io/voorhoede/pen/qNgWod/)。
50 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/05~渲染引擎/浏览器多进程架构/Edge 多进程架构.md:
--------------------------------------------------------------------------------
1 | # Edge 多进程体系结构
2 |
3 | Edge 的进程主要划分为以下类别:
4 |
5 | - 浏览器进程(Browser process):这是主要的进程,它帮助管理窗口和标签,并控制浏览器框架,如地址栏和前进和后退按钮。它还可以对网络请求和文件访问等实用程序的特权访问进行路由。
6 |
7 | - 渲染进程(Renderer processes):这些控件通过执行网站提供的代码来控制网站如何在标签页中渲染。它们处理 HTML、CSS、JavaScript、图像等。每个渲染器进程的资源使用情况都取决于所托管的内容。
8 |
9 | - GPU 进程(GPU process):该进程负责与 GPU(图形处理单元)进行通信并处理所有 GPU 任务。GPU 是一种硬件,可以快速执行与图形相关的计算,并将输出发送到显示器以进行显示,现代浏览器使用 GPU 来快速渲染网页。
10 |
11 | - 实用程序进程(Utility processes):音频播放、网络服务、视频采集、数据解码、收藏管理器都由这些进程来处理,这样 Microsoft Edge 就可以控制和审核这些资源的访问,协调全局系统资源的使用。
12 |
13 | - 插件进行和扩展程序进程(Plug-in processes and extension processes):插件进程包含活动插件,例如 Adobe Flash,而扩展进程包含活动扩展。每个进程执行由插件或扩展提供的代码,每个进程的资源使用情况根据所提供的代码而不同。每个进程也有允许插件或扩展与浏览器和渲染器进程通信的代码。
14 |
15 | - Crashpad 处理程序进程(Crashpad handler process):这可以跟踪 Microsoft Edge 中不同进程的健康状况。如果 Microsoft Edge 崩溃,这个过程将帮助浏览器捕获并将崩溃报告传输到微软服务器,我们使用这些崩溃报告来寻找和修复崩溃。
16 |
17 | 现在我们已经介绍了每个进程的作用,让我们来看看一个进程的例子,它将为一个打开了一个标签页并在 Microsoft Edge 中打开了两个扩展的用户运行。
18 |
19 | 
20 |
21 | 在此示例中,用户将看到九个进程正在运行:
22 |
23 | - 浏览器框架的浏览器进程
24 | - 一个帮助显示图形的 GPU 进程
25 | - 一个正在运行示例网站提供的代码的渲染器进程
26 | - 网络服务实用程序进程,帮助处理网络请求
27 | - 音频服务实用程序进程,可帮助播放音频
28 | - 运行 Flash 提供的代码的插件进程
29 | - 两个扩展进程,每个扩展进程一个,运行扩展提供的代码
30 | - 一个监控 Microsoft Edge 健康状况的 crashpad 处理程序
31 |
32 | 所有这些过程一起写作,给你今天使用的浏览体验。
33 |
34 | 现在让我们来看另一个例子。在下一个示例中,用户打开了四个选项卡,并启用了两个扩展(图 2)。每个标签都有一个广告(两个来自一个来源,两个来自另一个来源)。
35 |
36 | 
37 |
38 | 在此示例中,如果用户打开任务管理器,他们将看到 14 个进程正在运行:
39 |
40 | - 浏览器框架的浏览器进程
41 | - 一个帮助显示图形的 GPU 进程
42 | - 六个渲染器进程:
43 |
44 | - 四个标签页的渲染器进程,每个标签页都有自己的渲染器进程,并运行网站提供的代码。有时,来自同一域的选项卡将共享一个进程。
45 | - 两个广告的渲染器进程。来自同一域名的广告将共享一个进程,并将运行广告提供的代码。在本例中,第一个来源的两个广告将共享一个进程,第二个来源的两个广告将共享一个单独的进程。这些广告使用称为子帧的东西嵌入网页中。(稍后我们将详细讨论子帧。)
46 |
47 | - 网络服务实用程序进程,帮助处理网络请求
48 | - 音频服务实用程序进程,可帮助播放音频
49 | - 一个正在运行 Flash 的插件进程
50 | - 两个扩展进程,每个扩展进程一个,运行扩展提供的代码
51 | - 一个监控 Microsoft Edge 健康状况的 crashpad 处理程序
52 |
53 | 一些例子更加复杂。您可能会看到对您不可见的子框架的其他进程,或者您可能会看到项目(如 service workers 人员或 web workers)与选项卡或子框架共享进程。service workers 和 web workers 是在后台运行的脚本,以提高性能,并允许您在没有互联网连接的情况下使用一些网站和应用程序。
54 |
--------------------------------------------------------------------------------
/02~网页性能监控/异常监控与分析/常见异常.md:
--------------------------------------------------------------------------------
1 | # 常见异常
2 |
3 | # ‘xxx’ is undefined
4 |
5 | 造成 `‘xxx’ is undefined` 的主要原因有两种:
6 |
7 | - 其实大家自己写代码的时候,现在各种 Lint 工具都可以帮我们规避掉 `‘xxx’ is undefined` 这类问题,但为什么还是会出现呢?常见的一个情况就是后台接口返回的数据是 JSON 格式,加上某个后端服务一旦出现了问题,导致返回的数据异常。这是遇到的最多的一个情况。
8 |
9 | - 大家现在的应用里面应该都有分包的工作,一般都会把一些主体的包单独拆出来,一旦这种包加载失败,就会出现 `‘xxx’ is undefined` 。
10 |
11 | ## 数据校验
12 |
13 | 后端接口的数据不可信,该做的判断一定不能少。在引入一些其他的优化方案的时候,要考虑一下它的副作用。可能这个问题不是你导致的,但是别人的问题可能会导致你的问题!
14 |
15 | ## CDN 劫持
16 |
17 | 我们有个业务在没有任何发布的情况下,然后它的页面加载的失败率从 0.5% 到了 10%。我们就找 CDN 的同学去看哪里出了问题,CDN 也没异常。后来几经波折,多方定位,发现某一个节点上面的文件指纹和主栈的 CDN 节点的文件指纹是不一样的。也就是说这个节点的 CDN 文件被篡改了,被劫持了。大家知道 CDN 实际上是主栈要推到各个 CDN 栈上去的。虽然在我们的页面上面是走 HTTPS,但是从主栈把资源推到各个 CDN 上的时候,为了追求速度是不会去走 HTTPS 的,而是走 HTTP。其实这就给了一些运营商可趁之机,在这个阶段就把 CDN 上的资源做了一个篡改,导致资源上了 HTTPS 还是被劫持。
18 |
19 | 当然,我们肯定知道会存在这种劫持情况。所以很早就加了一个 SRI 的方案,做防劫持的处理。简单讲一下,比如 SRI 会在 Script 标签上面有一个指纹,当你通过构建工具生成一个 JS 文件的加密后的一个指纹,然后在服务器下载下来的文件上面,也会对那个文件做一个指纹,然后再和这个标签上面的文件做指纹的比对。如果发现不一样,就会认为这个文件被篡改了,是被劫持的。当然防劫持的目的达到了,但同时也带来了新的问题——白屏。因为浏览器认为资源不可信,就不会去执行 JS 了,不会执行这些 JS 就不会去做任何异步尝试,也就说 JS 最终还是被当作加载失败来处理了。
20 |
21 | 当 SRI 失败的时候,还是会触发 Script Error 事件。所以我们要做一些补充的方案,如果 Script 触发 onError,我们会向主栈拉取一次 JS 资源,不是 CDN 节点上的 JS,然后拉取主栈的 JS 的话,我们会取他前头部的几个字节以及尾部的字节,和我们现有的 CDN 上的资源做一个对比,全文对比太长了,我们做前后的这种字符检测就可以了。如果检查字符不一样,我们就直接使用主栈的资源进行加载,然后再走到上报的平台。
22 |
23 | ```html
24 |
32 | ```
33 |
34 | # Script Error
35 |
36 | - iOS 中跨域的异步脚本的报错信息在 window.onError 中是捕获不到的。
37 |
38 | 举个例子,在 a 域名的页面下引入了 b 域名的脚本,b 域名的脚本在执行 setTimeout 中的一段代码,出现了异常。window.onError 是无法获取到这个错误的。目前针对这种情况的解决方法,只能对异步脚本中的代码手动地抛出错误进行捕获。
39 |
40 | - 通过 native 代码执行的脚本报错,是无法被捕获的。
41 |
42 | 对于 Hybird 的 APP 中一些复杂的页面,客户端都会去调一些我们 JS 的代码,执行一些能力。客户端同学可能网上搜了一段代码,并不会告诉你这段代码有什么,然后本地跑执行没问题,所以他就直接加上去了。但比如说他回调你页面里面的一个接口,windows 怎么 xxx 执行一下。但其实可能你代码有什么问题,或者是他在你的代码声明之前就执行了,这个时候实际上也会有报错且没有 Script Error,所以这种问题你就需要联合客户端这边,要对他们执行的这种 JS 代码做一些保护,常见的就是执行前,先对 JS 回调进行检查。
43 |
44 | - 离线缓存
45 |
46 | 因为一些离线缓存实现的问题不太对,现在一些大的 App 里面,都有做这种离线缓存功能。理论上来说,把接口拦截掉,返回一个本地的资源,当然资源的 Header 是客户端来设置的,这个时候不会设置跨域头,也会报 Script Error 。为了解决这个问题,我们做了一个临时策略,把离线包里面所有的资源都换成主域的资源,解决历史版本中的问题,同时推动客户端添加缓存头。
47 |
--------------------------------------------------------------------------------
/02~网页性能监控/异常监控与分析/异常上报/README.md:
--------------------------------------------------------------------------------
1 | # Web 异常上报
2 |
3 | # 上报类型
4 |
5 | 整体的监控,我们需要采集的数据分为 4 类,即:JS 的异常错误、资源测速、接口的成功率、失败率、性能。
6 |
7 | 
8 |
9 | 其实浏览器已经提供了比较好的 API;当然对于不好支持的部分,我们可以采用 Patch 方式,对一些原生方法进行处理,从而做到无侵入开发上报的方式。如上图,大家可以看到 JS 错误监控里面有个 window.onEerror,但在资源测速监控里面又用了 window.addEventLisenter('error'),其实两者并不能互相代替。window.onError 是一个标准的错误捕获接口,它可以拿到对应的这种 JS 错误;windows.addEventLisenter('error')也可以捕获到错误,但是它拿到的 JS 报错堆栈往往是不完整的。同时 window.onError 无法获取到资源加载失败的一个情况,必须使用 window.addEventLisenter('error')来捕获资源加载失败的情况。
10 |
11 | # 上报端框架
12 |
13 | 
14 |
15 | 我们先会在页面上内嵌一个最小的 SDK,并且一定是要放在头部的。如果把 SDK 全部异步加载,那么 SDK 必然无法采集到加载之前的一些数据。这里我们采用了一个折中的办法,只把最小的采集端缩小成一个最简版的 SDK 放在头部,然后再进一步的去加载主体的 SDK,采集端采集到的数据,会先放入到一个内存的池子里面,然后等到主体端的 SDK 加载完成之后,再从池子里面把数据读取出来,然后上报。
16 |
17 | 
18 |
19 | 那么问题来了,如果主体 SDK 加载失败了怎么办?我们的做法是再加入一层保护机制,首先加载主体 SDK 会有两次重试的机制。如果再次失败,会先把上报池中要上报的数据先存在 IndexedDB 里面,作为本地日志。等到用户下一次进入页面的时候,再把本地日志历史中的这些日志一并进行上报;或者服务器直接下发上传指令,主动上传日志。
20 |
21 | # 上报请求
22 |
23 | 上报一般都是用 new Image 来发送,因为我们会对上报的请求做一个合并,请求长度太大会导致请求失败。比如说,三秒内的多条请求,会合并成一条请求发出去,但是合并之后就会导致一个问题,发出的长度如果超过了 2KB,也就是一个 get 请求的最大长度,那么这条请求是会失败的。最直接的解决办法是用 xhr 走 post 请求的方式来发送。但是又会面临另外一个问题:用 XHR 发送的话,这个请求的优先级会升级为最高,将影响到业务主体的消息请求。
24 |
25 | 这个时候我们又看到一个新的 API,就是 sendBeacon,那么用这个是不是问题就得到解决了呢?我们尝试用了 sendBeacon,同样也发现了问题,它的优先级也是要区分的。如果 sendBeacon 没有带 FormData 的话,它的优先级是比较低的,不会影响业务。如果用的是 FormData 的形式,它的优先级依然是比较高的,这也是我们之前遇到的问题,这一点在官方文档中并没有提到。当时我们在本地测试之后,就上线了使用 sendBeacon 的版本,发布之后很快就出现测速告警,图片延迟率变得特别高,多了三秒之多。我们针对这个问题定位了很久,最后发现是因为 sendBeacon 的这一改动。
26 |
27 | 兜兜转转,好像只能回到 new Image 的解法,我们再次使用了它,并给它加了一层保护,首先压缩合并并判断它压缩后的请求,之后再对过长的压缩进行拆分,保证它在可用的长度范围内进行压缩。压缩之后用 new Image 发送的话,由于压缩的计算耗时,终究会影响到业务的体验。所以我们就借助了 requestIdleCallback 这个方法。这个 API 做了什么事情呢?它可以检测到浏览器的资源空闲状态。可以在空闲时发送业务请求。
28 |
29 | 本来以为这样就可以了,后来产品同学又来找我们反馈,说上报数据老是有丢失,还有用户页面停留时间过长,以及数据量各种不对。我们试验之后得出的结果是,一个浏览器关闭了之后,那些没有发送的请求其实是会丢掉的。一般的做法就是在 Windows 里面发一个同步的 XHR 请求,但是在移动端其实是没有效果的。遗憾的是,即使我们用 sendBeacon,页面关闭后仍然不会去发送请求,好像陷入了死局,最终我们的解决方案是借助终端的能力去发送关闭这种事件的一些请求。
30 |
31 | ## Beacon
32 |
33 | The Beacon API is used for sending small amounts of data to a server without waiting for a response. That last part is critical and is the key to why Beacon is so useful — our code never even gets to see a response, even if the server sends one. Beacons are specifically for sending data and then forgetting about it. We don’t expect a response and we don’t get a response.
34 |
35 | Beacon 常用于用户追踪,行为分析,调试与日志。
36 |
37 | # Links
38 |
39 | - https://zhuanlan.zhihu.com/p/68838820 如何在 Web 关闭页面时发送 Ajax 请求
40 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/04.页面的生命周期/页面的生命周期.md:
--------------------------------------------------------------------------------
1 | # 浏览器页面的生命周期
2 |
3 | 应用程序生命周期是现代操作系统管理资源的一种重要方式。在 Android、iOS 和最近的 Windows 版本上,应用程序可以由操作系统随时启动和停止。这使得这些平台能够精简和重新分配资源,使其最有利于用户。在网络上,历史上没有这样的生命周期,应用程序可以无限期地保持活力。随着大量网页的运行,内存、CPU、电池和网络等关键系统资源可能会被超额占用,导致终端用户体验不佳。虽然 Web 平台早就有了与生命周期状态相关的事件--比如加载、卸载和可见性改变,但这些事件只允许开发人员对用户发起的生命周期状态变化做出响应。要想让 web 在低功耗设备上可靠地工作(并且在所有平台上普遍更加注重资源),浏览器需要一种主动回收和重新分配系统资源的方法。
4 |
5 | 事实上,今天的浏览器已经在主动采取措施为后台标签页中的页面节约资源,许多浏览器(尤其是 Chrome)希望在这方面做得更多--以减少其整体资源占用。问题是开发者目前没有办法为这些类型的系统主动干预做准备,甚至不知道它们正在发生。这意味着浏览器需要保守,否则就会有破坏网页的风险。页面生命周期 API 试图通过以下方式来解决这个问题。
6 |
7 | - 引入网页生命周期状态的概念并使之标准化。
8 | - 定义新的、由系统发起的状态,允许浏览器限制隐藏的或不活动的标签消耗的资源。
9 | - 创建新的 API 和事件,使 Web 开发人员能够响应这些新的系统启动状态的转换。
10 |
11 | 该解决方案提供了网络开发者构建应用程序对系统干预的弹性所需的可预测性,它允许浏览器更积极地优化系统资源,最终使所有网络用户受益。
12 |
13 | # 生命周期概述
14 |
15 | 所有的页面生命周期状态都是离散的、互斥的,也就是说一个页面一次只能处于一种状态。而页面生命周期状态的大部分变化一般都可以通过 DOM 事件来观察。也许最简单的解释页面生命周期状态以及在它们之间转换的信号事件的方法是用一张图。
16 |
17 | 
18 |
19 | # APIs
20 |
21 | ```js
22 | const getState = () => {
23 | if (document.visibilityState === "hidden") {
24 | return "hidden";
25 | }
26 | if (document.hasFocus()) {
27 | return "active";
28 | }
29 | return "passive";
30 | };
31 |
32 | // Stores the initial state using the `getState()` function (defined above).
33 | let state = getState();
34 |
35 | // Accepts a next state and, if there's been a state change, logs the
36 | // change to the console. It also updates the `state` value defined above.
37 | const logStateChange = (nextState) => {
38 | const prevState = state;
39 | if (nextState !== prevState) {
40 | console.log(`State change: ${prevState} >>> ${nextState}`);
41 | state = nextState;
42 | }
43 | };
44 |
45 | // These lifecycle events can all use the same listener to observe state
46 | // changes (they call the `getState()` function to determine the next state).
47 | ["pageshow", "focus", "blur", "visibilitychange", "resume"].forEach((type) => {
48 | window.addEventListener(type, () => logStateChange(getState()), {
49 | capture: true,
50 | });
51 | });
52 |
53 | // The next two listeners, on the other hand, can determine the next
54 | // state from the event itself.
55 | window.addEventListener(
56 | "freeze",
57 | () => {
58 | // In the freeze event, the next state is always frozen.
59 | logStateChange("frozen");
60 | },
61 | { capture: true }
62 | );
63 |
64 | window.addEventListener(
65 | "pagehide",
66 | (event) => {
67 | if (event.persisted) {
68 | // If the event's persisted property is `true` the page is about
69 | // to enter the Back-Forward Cache, which is also in the frozen state.
70 | logStateChange("frozen");
71 | } else {
72 | // If the event's persisted property is not `true` the page is
73 | // about to be unloaded.
74 | logStateChange("terminated");
75 | }
76 | },
77 | { capture: true }
78 | );
79 | ```
80 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/05~渲染引擎/浏览器多进程架构/Chrome 多进程架构.md:
--------------------------------------------------------------------------------
1 | # Chrome 多进程架构
2 |
3 | 不同的浏览器使用不同的架构,下面主要以 Chrome 为例,介绍浏览器的多进程架构。在 Chrome 中,主要的进程有 4 个:
4 |
5 | | 进程 | 负责的工作 |
6 | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
7 | | Browser | 负责浏览器的“Chrome”部分,包括导航栏,书签,前进和后退按钮。同时这个进程还会控制那些我们看不见的部分,包括网络请求的发送以及文件的读写。 |
8 | | Renderer | 负责 tab 内和网页展示相关的所有工作。 |
9 | | Plugin | 控制网页使用的所有插件,例如 flash 插件。 |
10 | | GPU | 负责独立于其它进程的 GPU 任务。它之所以被独立为一个进程是因为它要处理来自于不同 tab 的渲染请求并把它在同一个界面上画出来。 |
11 |
12 | 
13 |
14 | 首先,当我们是要浏览一个网页,我们会在浏览器的地址栏里输入 URL,这个时候 Browser Process 会向这个 URL 发送请求,获取这个 URL 的 HTML 内容,然后将 HTML 交给 Renderer Process,Renderer Process 解析 HTML 内容,解析遇到需要请求网络的资源又返回来交给 Browser Process 进行加载,同时通知 Browser Process,需要 Plugin Process 加载插件资源,执行插件代码。解析完成后,Renderer Process 计算得到图像帧,并将这些图像帧交给 GPU Process,GPU Process 将其转化为图像显示屏幕。
15 |
16 | 
17 |
18 | 除了上面列出来的进程,Chrome 还有很多其他进程在工作,例如扩展进程(Extension Process)和工具进程(utility process)。如果你想看一下你的 Chrome 浏览器现在有多少个进程在跑可以点击浏览器右上角的更多按钮,选择更多工具和任务管理器。
19 |
20 | ## 多进程架构的好处
21 |
22 | 当今 Web 应用中,HTML,JavaScript 和 CSS 日益复杂,这些跑在渲染引擎的代码,频繁的出现 BUG,而有些 BUG 会直接导致渲染引擎崩溃,多进程架构使得每一个渲染引擎运行在各自的进程中,相互之间不受影响,也就是说,当其中一个页面崩溃挂掉之后,其他页面还可以正常的运行不收影响。
23 |
24 | 
25 |
26 | 更高的安全性和沙盒性(sanboxing)。渲染引擎会经常性的在网络上遇到不可信、甚至是恶意的代码,它们会利用这些漏洞在你的电脑上安装恶意的软件,针对这一问题,浏览器对不同进程限制了不同的权限,并为其提供沙盒运行环境,使其更安全更可靠
27 |
28 | 更高的响应速度。在单进程的架构中,各个任务相互竞争抢夺 CPU 资源,使得浏览器响应速度变慢,而多进程架构正好规避了这一缺点。
29 |
30 | ## 浏览器的进程模式与网站隔离(Site Isolation)
31 |
32 | 为了节省内存,Chrome 提供了四种进程模式(Process Models),不同的进程模式会对 tab 进程做不同的处理。
33 |
34 | - **Process-per-site-instance** (default) - 同一个 **site-instance** 使用一个进程
35 | - **Process-per-site -** 同一个 **site** 使用一个进程
36 | - **Process-per-tab -** 每个 tab 使用一个进程
37 | - **Single process -** 所有 tab 共用一个进程
38 |
39 | 这里需要给出 site 和 site-instance 的定义:
40 |
41 | - **site** 指的是相同的 registered domain name(如:google.com,bbc.co.uk)和 scheme (如:https://)。比如 a.baidu.com 和 b.baidu.com 就可以理解为同一个 site(注意这里要和 Same-origin policy 区分开来,同源策略还涉及到子域名和端口)。
42 |
43 | - **site-instance** 指的是一组 **connected pages from the same site**,这里 **connected** 的定义是 **can obtain references to each other in script code** 怎么理解这段话呢。满足下面两中情况并且打开的新页面和旧页面属于上面定义的同一个 site,就属于同一个 **site-instance**
44 |
45 | - 用户通过``这种方式点击打开的新页面
46 | - JS 代码打开的新页面(比如 `window.open`)
47 |
48 | 首先是 Single process,顾名思义,单进程模式,所有 tab 都会使用同一个进程。接下来是 Process-per-tab,也是顾名思义,每打开一个 tab,会新建一个进程。而对于 Process-per-site,当你打开 a.baidu.com 页面,在打开 b.baidu.com 的页面,这两个页面的 tab 使用的是共一个进程,因为这两个页面的 site 相同,而如此一来,如果其中一个 tab 崩溃了,而另一个 tab 也会崩溃。
49 |
50 | Process-per-site-instance 是最重要的,因为这个是 Chrome 默认使用的模式,也就是几乎所有的用户都在用的模式。当你打开一个 tab 访问 a.baidu.com,然后再打开一个 tab 访问 b.baidu.com,这两个 tab 会使用两个进程。而如果你在 a.baidu.com 中,通过 JS 代码打开了 b.baidu.com 页面,这两个 tab 会使用同一个进程。
51 |
52 | Process-per-site-instance 兼容了性能与易用性,是一个比较中庸通用的模式。相较于 Process-per-tab,能够少开很多进程,就意味着更少的内存占用;相较于 Process-per-site,能够更好的隔离相同域名下毫无关联的 tab,更加安全。
53 |
--------------------------------------------------------------------------------
/04~移动端与响应式/H5 优化/触摸滚动.md:
--------------------------------------------------------------------------------
1 | # iOS 滑动不流畅
2 |
3 | 上下滑动页面会产生卡顿,手指离开页面,页面立即停止运动。整体表现就是滑动不流畅,没有滑动惯性。原来在 iOS 5.0 以及之后的版本,滑动有定义有两个值 auto 和 touch,默认值为 auto。
4 |
5 | ```css
6 | -webkit-overflow-scrolling: touch; /* 当手指从触摸屏上移开,会保持一段时间的滚动 */
7 | -webkit-overflow-scrolling: auto; /* 当手指从触摸屏上移开,滚动会立即停止 */
8 | ```
9 |
10 | 简单的解决办法就是将-webkit-overflow-scrolling 值设置为 touch,也设置滚动条隐藏:`.container ::-webkit-scrollbar {display: none;}`;不过这样可能会导致使用`position:fixed;` 固定定位的元素,随着页面一起滚动。搭配可以设置外部 overflow 为 hidden,设置内容元素 overflow 为 auto。内部元素超出 body 即产生滚动,超出的部分 body 隐藏。
11 |
12 | ```css
13 | body {
14 | overflow-y: hidden;
15 | }
16 | .wrapper {
17 | overflow-y: auto;
18 | }
19 | ```
20 |
21 | # iOS 上拉边界下拉出现白色空白
22 |
23 | 手指按住屏幕下拉,屏幕顶部会多出一块白色区域。手指按住屏幕上拉,底部多出一块白色区域。在 iOS 中,手指按住屏幕上下拖动,会触发 touchmove 事件。这个事件触发的对象是整个 webview 容器,容器自然会被拖动,剩下的部分会成空白。我们可以通过监听 touchmove,让需要滑动的地方滑动,不需要滑动的地方禁止滑动。
24 |
25 | ```js
26 | document.body.addEventListener(
27 | "touchmove",
28 | function (e) {
29 | if (e._isScroller) return;
30 | // 阻止默认事件
31 | e.preventDefault();
32 | },
33 | {
34 | passive: false,
35 | }
36 | );
37 | ```
38 |
39 | 在很多时候,我们可以不去解决这个问题,换一直思路。根据场景,我们可以将下拉作为一个功能性的操作。
40 |
41 | # 页面放大或缩小不确定性行为
42 |
43 | 双击或者双指张开手指页面元素,页面会放大或缩小。HTML 本身会产生放大或缩小的行为,比如在 PC 浏览器上,可以自由控制页面的放大缩小。但是在移动端,我们是不需要这个行为的。所以,我们需要禁止该不确定性行为,来提升用户体验。HTML meta 元标签标准中有个 中 viewport 属性,用来控制页面的缩放,一般用于移动端。
44 |
45 | ```html
46 |
47 | ```
48 |
49 | 因此我们可以设置 maximum-scale、minimum-scale 与 user-scalable=no 用来避免这个问题:
50 |
51 | ```html
52 |
56 | ```
57 |
58 | # click 点击事件延时与穿透
59 |
60 | - 监听元素 click 事件,点击元素触发时间延迟约 300ms。iOS 中的 safari,为了实现双击缩放操作,在单击 300ms 之后,如果未进行第二次点击,则执行 click 单击操作。也就是说来判断用户行为是否为双击产生的。但是,在 App 中,无论是否需要双击缩放这种行为,click 单击都会产生 300ms 延迟。
61 |
62 | - 点击蒙层,蒙层消失后,下层元素点击触发。双层元素叠加时,在上层元素上绑定 touch 事件,下层元素绑定 click 事件。由于 click 发生在 touch 之后,点击上层元素,元素消失,下层元素会触发 click 事件,由此产生了点击穿透的效果。
63 |
64 | 首先可以使用 touchstart 替换 click,将 click 替换成 touchstart 不仅解决了 click 事件都延时问题,还解决了穿透问题。因为穿透问题是在 touch 和 click 混用时产生。
65 |
66 | ```js
67 | el.addEventListener(
68 | "touchstart",
69 | () => {
70 | console.log("ok");
71 | },
72 | false
73 | );
74 | ```
75 |
76 | 不过需要注意的是,我们并不可以将 click 事件全部替换成 touchstart。因为事件触发顺序: touchstart, touchmove, touchend, click,很容易想象,在我需要 touchmove 滑动时候,优先触发了 touchstart 的点击事件,会产生冲突。所以呢,在具有滚动的情况下,还是建议使用 click 处理。在接下来的 fastclick 开源库中也做了如下处理。针对 touchstart 和 touchend,截取了部分源码。
77 |
78 | ```js
79 | // If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
80 | // 1) the user does a fling scroll on the scrollable layer
81 | // 2) the user stops the fling scroll with another tap
82 | // then the event.target of the last 'touchend' event will be the element that was under the user's finger
83 | // when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
84 | // is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
85 | this.updateScrollParent(targetElement);
86 |
87 | // Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
88 | // and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
89 | scrollParent = targetElement.fastClickScrollParent;
90 | if (
91 | scrollParent &&
92 | scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop
93 | ) {
94 | return true;
95 | }
96 | ```
97 |
98 | 主要目的就是,在使用 touchstart 合成 click 事件时,保证其不在滚动的父元素之下。
99 |
--------------------------------------------------------------------------------
/01~浏览器工作机制/02.解析与渲染/解析与渲染.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Web 解析与渲染
4 |
5 | 导航过程完成之后,浏览器进程把数据交给了渲染进程,渲染进程负责 tab 内的所有事情,核心目的就是将 HTML/CSS/JS 代码,转化为用户可进行交互的 Web 页面。渲染进程中,包含线程分别是:一个主线程(main thread)、多个工作线程(work thread)、一个合成器线程(compositor thread)、多个光栅化线程(raster thread)。
6 |
7 | 
8 |
9 | # DOM 构建与资源子加载
10 |
11 | 当渲染进程接受到导航的确认信息后,开始接受来自浏览器进程的数据,这个时候,主线程会解析数据转化为 DOM(Document Object Model)对象。DOM 为 Web 开发人员通过 JavaScript 与网页进行交互的数据结构及 API。在构建 DOM 的过程中,会解析到图片、CSS、JavaScript 脚本等资源,这些资源是需要从网络或者缓存中获取的,主线程在构建 DOM 过程中如果遇到了这些资源,逐一发起请求去获取,而为了提升效率,浏览器也会运行预加载扫描(preload scanner)程序,如果如果 HTML 中存在 img、link 等标签,预加载扫描程序会把这些请求传递给 Browser Process 的 network thread 进行资源下载。
12 |
13 | 
14 |
15 | # JavaScript 解析与编译
16 |
17 | 构建 DOM 过程中,如果遇到 `
75 | ```
76 |
77 | Polyfill.io 通过分析请求头信息中的 UserAgent 实现自动加载浏览器所需的 polyfills。Polyfill.io 有一份默认功能列表,包括了最常见的 polyfills:document.querySelector、Element.classList、ES5 新增的 Array 方法、Date.now、ES6 中的 Object.assign、Promise 等。
78 |
79 | Polyfill.io 同样允许我们进行异步加载:
80 |
81 | ```html
82 |
83 |
84 |
85 |
86 | Document
87 |
92 |
99 |
100 |
101 |
102 | ```
103 |
104 | # DOM
105 |
106 | ```js
107 | if (!document.querySelectorAll) {
108 | document.querySelectorAll = function (selectors) {
109 | var style = document.createElement("style"),
110 | elements = [],
111 | element;
112 | document.documentElement.firstChild.appendChild(style);
113 | document._qsa = [];
114 |
115 | style.styleSheet.cssText =
116 | selectors +
117 | "{x-qsa:expression(document._qsa && document._qsa.push(this))}";
118 | window.scrollBy(0, 0);
119 | style.parentNode.removeChild(style);
120 |
121 | while (document._qsa.length) {
122 | element = document._qsa.shift();
123 | element.style.removeAttribute("x-qsa");
124 | elements.push(element);
125 | }
126 | document._qsa = null;
127 | return elements;
128 | };
129 | }
130 |
131 | if (!document.querySelector) {
132 | document.querySelector = function (selectors) {
133 | var elements = document.querySelectorAll(selectors);
134 | return elements.length ? elements[0] : null;
135 | };
136 | }
137 | ```
138 |
139 | # 优雅降级与渐进增强
140 |
141 | 渐进增强是指针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。优雅降级则是反其道行之,一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
142 |
143 | 优雅降级和渐进增强印象中是随着 css3 流出来的一个概念。由于低级浏览器不支持 css3,但 css3 的效果又太优秀不忍放弃,所以在高级浏览中使用 css3 而低级浏览器只保证最基本的功能。咋一看两个概念差不多,都是在关注不同浏览器下的不同体验,关键的区别是他们所侧重的内容,以及这种不同造成的工作流程的差异。
144 |
145 | 渐进增强 progressive enhancement:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。 优雅降级 graceful degradation:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
146 |
147 | 区别:优雅降级是从复杂的现状开始,并试图减少用户体验的供给,而渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要。降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带。
148 | “优雅降级”观点认为应该针对那些最高级、最完善的浏览器来设计网站。而将那些被认为“过时”或有功能缺失的浏览器下的测试工作安排在开发周期的最后阶段,并把测试对象限定为主流浏览器(如 IE、Mozilla 等)的前一个版本。
149 |
150 | 在这种设计范例下,旧版的浏览器被认为仅能提供“简陋却无妨 (poor, but passable)” 的浏览体验。你可以做一些小的调整来适应某个特定的浏览器。但由于它们并非我们所关注的焦点,因此除了修复较大的错误之外,其它的差异将被直接忽略。
151 | “渐进增强”观点则认为应关注于内容本身。
152 |
153 | 内容是我们建立网站的诱因。有的网站展示它,有的则收集它,有的寻求,有的操作,还有的网站甚至会包含以上的种种,但相同点是它们全都涉及到内容。这使得“渐进增强”成为一种更为合理的设计范例。这也是它立即被 Yahoo! 所采纳并用以构建其“分级式浏览器支持 (Graded Browser Support)”策略的原因所在。
154 |
--------------------------------------------------------------------------------
/04~移动端与响应式/响应式适配/响应式尺寸/响应式尺寸.md:
--------------------------------------------------------------------------------
1 | # Element Query
2 |
3 | # 合理的单位搭配
4 |
5 | ## Absolute Size & Percent:绝对尺寸与百分比尺寸
6 |
7 | 在移动端开发中,笔者最早是倾向于用百分比布局,这样相较于传统的绝对布局肯定是具有一定灵活性,
8 |
9 | # Responsive Size:响应式尺寸
10 |
11 | ## Media Query:媒介查询
12 |
13 | > - [CSS Media Queries for iPads & iPhones](http://stephen.io/mediaqueries/)
14 |
15 | ### Media Query By SCSS
16 |
17 | ## Flexible Units:灵活的尺寸单位
18 |
19 | ### Viewport Size:基于 Viewport 的单位
20 |
21 | ```s
22 | 1vw = 1% of viewport width
23 | 1vh = 1% of viewport height
24 | 1vmin = 1vw or 1vh, whichever is smaller
25 | 1vmax = 1vw or 1vh, whichever is larger
26 | ```
27 |
28 | 总而言之,就笔者目前的认知而言,虽然在下文中也会介绍几种关于 Viewport Size 的 Polyfill,不过总体而言笔者还不是很建议现在就大范围地使用 Viewport Size。
29 |
30 | #### [viewport-units-buggyfill](https://github.com/rodneyrehm/viewport-units-buggyfill)
31 |
32 | #### [vminpoly](https://github.com/saabi/vminpoly)
33 |
34 | 整体而言该 Polyfill 的完善度与社区活跃度皆不如上者,建议有需要的还是参考下上面那个 Polyfill。
35 |
36 | ### FontSize:字体
37 |
38 | > - [css-font-size-em-vs-px-vs-pt-vs](http://kyleschaeffer.com/development/css-font-size-em-vs-px-vs-pt-vs/)
39 |
40 | - [the-ems-have-it-proportional-media-queries-ftw](https://cloudfour.com/thinks/the-ems-have-it-proportional-media-queries-ftw/)
41 |
42 | ### Fixed Size
43 |
44 | To avoid mobile browsers (iOS Safari, _et al_.) from zooming in on HTML form elements when a ``drop-down is tapped, add`font-size` to the selector rule:
45 |
46 | ```html
47 | input[type="text"], input[type="number"], select, textarea { font-size: 16px;
48 | }
49 | ```
50 |
51 | ### Flexible Size
52 |
53 | The type font size in a responsive layout should be able to adjust with each viewport. You can calculate the font size based on the viewport height and width using `:root`:
54 |
55 | ```css
56 | :root {
57 | font-size: calc(1vw + 1vh + 0.5vmin);
58 | }
59 | ```
60 |
61 | Now you can utilize the `root em` unit based on the value calculated by `:root`:
62 |
63 | ```css
64 | body {
65 | font: 1em/1.6 sans-serif;
66 | }
67 | ```
68 |
69 | ```scss
70 | :root {
71 | font-size: calc(1vw + 1vh + 0.25vmin);
72 | }
73 | body {
74 | font: 1em/1.75 "Open Sans", sans-serif;
75 | }
76 | .container {
77 | padding: 0 1em;
78 | }
79 | code {
80 | background: #eee;
81 | border-radius: 3px;
82 | }
83 | em {
84 | background: #ffeb3b;
85 | font-style: normal;
86 | }
87 | code,
88 | em {
89 | padding: 0.1em 0.2em;
90 | }
91 | ```
92 |
93 | ## 合理的单位搭配
94 |
95 | 在响应式开发中,很多人都会建议不要使用 px 这种绝对值作为尺寸,不过这也是因人而异的,过度的使用相对值也会导致开发的复杂度与不可预测性的增加。老实说,最傻瓜的开发方案就是在一套视觉稿基础上使用绝对值开发,然后使用下文介绍的按比例缩放的技巧去适应各个屏幕。
96 |
97 | For layout type things like the sizes of boxes, you want to use % because you will typically have several columns sized as a percentage of their parent that will stack on top of each other at a certain breakpoint (width:100%). No other unit will allow you to fill 100% of the space like % does. But with the min-height, max-height, min-width, max-width CSS keys.
98 | For padding/margins use em, normally you will want to space your elements out relative to the size of your text. With em (the with of an 'M' character) you can quite easily say I want approximately 1 character spacing here.
99 |
100 | For borders you can use px or em, there is a difference though. If you want your border to look like it's one pixel wide on all devices, use 1px. It may not be one pixel on all devices however, high density displays convert 1px into 2px for example. If you want your border to be a size based on your font, use em.
101 |
102 | [dabblet](http://dabblet.com/gist/3734579)
103 |
104 | For fonts use em (or %), the use of em carries through parents to children and it just a nicer unit to work with over px.
105 | For the next generation
106 | vw and vh. The vw is 1/100th of the window's width and the vh is 1/100th of the window's height. For responsiveness they are going to be the new units.
107 |
108 | 在阅读本文的时候,反而希望你能先忘却关于 CSS、Web 开发那些你已经知道的东西,我们今天讨论的并不是多么复杂深奥的内容,如果你觉得准备好了那我们可以从下面这个简单的点图开始:
109 |
110 | 上面这些点分布的有些随意,分分合合,有近有远,我们的问题就是如何将这些点划分入到五个组中。最简单的,我们可以在那些相距较远的两个点之间设置为分隔区划分到不同的组合中。
111 |
112 | 上面这几个圈都是我随手画出来的,你当然可以选择其他的划分方式,譬如将最右边的两个点划分到一个分组中。其实这个问题并无所谓错误答案,不过如果你以如下方式划分的话:
113 |
114 | 看上去是不是觉得怪怪的?我问这个问题也不是无中生有,当我们需要为不同尺寸的屏幕设置不同的 CSS 样式稿时,会有人喜欢按照最常见的尺寸作为分割点,即 320px,768px 与 1024px。
115 |
116 | 不知道你有没有听过或者说过下面这些话:中等屏幕的话是不是按照 768px 来划分?还是应该把 768px 也划分到中等屏幕的范围内?不过这个尺寸是 iPad 横屏状态下的尺寸,应该算是大屏幕了吧?唔那大屏幕就是 768px 和以上尺寸咯?然后 320px 左右的是小屏幕?那 319px 算啥?区分蚂蚁的吗?本文的主旨即使讨论如何选择合适的分割点与分隔组。
117 |
118 | # 选择合适的分隔点
119 |
120 | 你在幼儿园里就会画上面的这些圆吧,我现在用矩形度量来详细阐述下:
121 |
122 | 我们在这里选择了 600px,900px,1200px 以及 1800px 作为分割点,这些分隔组包含了最常见的 14 个机型:
123 |
124 | 我们把这两张图合并下,可以得出下面这个更适合你的老板、设计师、开发者以及测试人员看的一张图:
125 |
126 | # 用更直观的方式命名你的分组
127 |
128 | 你愿意的话,也可以使用[Papa-Bear 与 Baby-Bear](https://css-tricks.com/naming-media-queries/)来称呼你选定的分割点。不过当你和设计师一起讨论网站在不同屏幕上的展示效果时,肯定希望双方都能够在脑海中形成感性直观的认知。如果你用平板竖屏尺寸来形容的话,那到底是 iPad 还是 Surface 呢?特别是现在这种手机越来越大,平板越来越小的情况,你很难用单纯的平板或者手机来划分尺寸。不过好消息是苹果已经不做新产品了,他们只是不断地将按钮、耳机口从现在的产品中移除。这边我也不好给出什么建议,只能说设计师和产品之间需要多多沟通。
129 |
130 | # Flexible
131 |
132 | Flexible 到今天也有几年的历史了,解救了很多同学针对于 H5 页面布局的适配问题。而这套方案也相对而言是一个较为成熟的方案。简单的回忆一下,当初为了能让页面更好的适配各种不同的终端,通过 Hack 手段来根据设备的 dpr 值相应改变`` 标签中 viewport 的值:
133 |
134 | ```html
135 |
136 |
140 |
141 |
145 |
146 |
150 | ```
151 |
152 | 从而让页面达么缩放的效果,也变相的实现页面的适配功能。而其主要的思想有三点:
153 |
154 | - 根据 dpr 的值来修改 viewport 实现 1px 的线
155 |
156 | - 根据 dpr 的值来修改 html 的 font-size,从而使用 rem 实现等比缩放
157 |
158 | - 使用 Hack 手段用 rem 模拟 vw 特性
159 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Documents
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
34 |
35 |
36 |
|
37 |
38 |
|
39 |
40 |
41 |
42 |
59 |
92 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
144 |
145 |
146 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/04~移动端与响应式/H5 优化/像素适配.md:
--------------------------------------------------------------------------------
1 | # 1px 问题
2 |
3 | 所谓 1px 问题,即在高清屏下,移动端的 1px 会很粗,如下两个边框对比:
4 |
5 | 
6 |
7 | ```sh
8 | window.devicePixelRatio=物理像素 / CSS像素
9 | ```
10 |
11 | 目前主流的屏幕 DPR=2(iPhone 8),或者 3(iPhone 8 Plus)。拿 2 倍屏来说,设备的物理像素要实现 1 像素,而 DPR=2,所以 css 像素只能是 0.5。一般设计稿是按照 750 来设计的,它上面的 1px 是以 750 来参照的,而我们写 css 样式是以设备 375 为参照的,最简单的办法就是将边框设置为 0.5px。
12 |
13 | ```css
14 | border: 0.5px solid #e5e5e5;
15 | ```
16 |
17 | 或者使用边框图片:
18 |
19 | ```css
20 | border: 1px solid transparent;
21 | border-image: url("./../../image/96.jpg") 2 repeat;
22 | ```
23 |
24 | 或者使用阴影:
25 |
26 | ```css
27 | box-shadow: 0 -1px 1px -1px #e5e5e5, //上边线
28 | 1px 0 1px -1px #e5e5e5,
29 | //右边线
30 | 0 1px 1px -1px #e5e5e5, // 下边线
31 | -1px 0 1px -1px #e5e5e5; /** 左边线 */
32 | ```
33 |
34 | 或者使用伪元素:
35 |
36 | ```css
37 | .setOnePx {
38 | position: relative;
39 | &::after {
40 | position: absolute;
41 | content: "";
42 | background-color: #e5e5e5;
43 | display: block;
44 | width: 100%;
45 | height: 1px; /*no*/
46 | transform: scale(1, 0.5);
47 | top: 0;
48 | left: 0;
49 | }
50 | }
51 | ```
52 |
53 | 可以看到,将伪元素设置绝对定位,并且和父元素的左上角对齐,将 width 设置 100%,height 设置为 1px,然后进行在 Y 方向缩小 0.5 倍。最后,终极解决办法是使用基于 vw 进行适配:
54 |
55 | ```html
56 |
57 |
58 | 1px question
59 |
60 |
65 |
94 |
119 |
120 |
121 | 下面的底边宽度是虚拟 1 像素的
122 | 上面的边框宽度是虚拟 1 像素的
123 |
124 |
125 | ```
126 |
127 | # 软键盘将页面顶起来、收起未回落问题
128 |
129 | Android 手机中,点击 input 框时,键盘弹出,将页面顶起来,导致页面样式错乱。移开焦点时,键盘收起,键盘区域空白,未回落。我们在 app 布局中会有个固定的底部。安卓一些版本中,输入弹窗出来,会将解压 absolute 和 fixed 定位的元素。导致可视区域变小,布局错乱。
130 |
131 | 解决方案是软键盘将页面顶起来的解决方案,主要是通过监听页面高度变化,强制恢复成弹出前的高度。
132 |
133 | ```js
134 | // 记录原有的视口高度
135 | const originalHeight =
136 | document.body.clientHeight || document.documentElement.clientHeight;
137 |
138 | window.onresize = function () {
139 | var resizeHeight =
140 | document.documentElement.clientHeight || document.body.clientHeight;
141 | if (resizeHeight < originalHeight) {
142 | // 恢复内容区域高度
143 | // const container = document.getElementById("container")
144 | // 例如 container.style.height = originalHeight;
145 | }
146 | };
147 | ```
148 |
149 | 键盘不能回落问题出现在 iOS 12+ 和 wechat 6.7.4+ 中,而在微信 H5 开发中是比较常见的 Bug。兼容原理,1.判断版本类型 2.更改滚动的可视区域
150 |
151 | ```js
152 | const isWechat = window.navigator.userAgent.match(/MicroMessenger\/([\d\.]+)/i);
153 | if (!isWechat) return;
154 | const wechatVersion = wechatInfo[1];
155 | const version = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
156 |
157 | // 如果设备类型为 iOS 12+ 和 wechat 6.7.4+,恢复成原来的视口
158 | if (+wechatVersion.replace(/\./g, "") >= 674 && +version[1] >= 12) {
159 | window.scrollTo(
160 | 0,
161 | Math.max(document.body.clientHeight, document.documentElement.clientHeight)
162 | );
163 | }
164 | ```
165 |
166 | window.scrollTo(x-coord, y-coord),其中 window.scrollTo(0, clientHeight)恢复成原来的视口
167 |
168 | # iPhone X 系列安全区域适配问题
169 |
170 | 头部刘海两侧区域或者底部区域,出现刘海遮挡文字,或者呈现黑底或白底空白区域。iPhone X 以及它以上的系列,都采用刘海屏设计和全面屏手势。头部、底部、侧边都需要做特殊处理。才能适配 iPhone X 的特殊情况。具体操作为:viewport-fit meta 标签设置为 cover,获取所有区域填充。判断设备是否属于 iPhone X,给头部底部增加适配层:
171 |
172 | - `auto`:此值不影响初始布局视图端口,并且整个 web 页面都是可查看的。
173 | - `contain`:视图端口按比例缩放,以适合显示内嵌的最大矩形。
174 | - `cover`:视图端口被缩放以填充设备显示。强烈建议使用 `safe area inset` 变量,以确保重要内容不会出现在显示之外。
175 |
176 | ## 设置 viewport-fit 为 cover
177 |
178 | ```html
179 |
183 | ```
184 |
185 | ## 增加适配层
186 |
187 | ```css
188 | /* 适配 iPhone X 顶部填充*/
189 | @supports (top: env(safe-area-inset-top)) {
190 | body,
191 | .header {
192 | padding-top: constant(safe-area-inset-top, 40px);
193 | padding-top: env(safe-area-inset-top, 40px);
194 | padding-top: var(safe-area-inset-top, 40px);
195 | }
196 | }
197 | /* 判断iPhoneX 将 footer 的 padding-bottom 填充到最底部 */
198 | @supports (bottom: env(safe-area-inset-bottom)) {
199 | body,
200 | .footer {
201 | padding-bottom: constant(safe-area-inset-bottom, 20px);
202 | padding-bottom: env(safe-area-inset-bottom, 20px);
203 | padding-top: var(safe-area-inset-bottom, 20px);
204 | }
205 | }
206 | ```
207 |
208 | safe-area-inset-top, safe-area-inset-right, safe-area-inset-bottom, safe-area-inset-left safe-area-inset-\*由四个定义了视口边缘内矩形的 top, right, bottom 和 left 的环境变量组成,这样可以安全地放入内容,而不会有被非矩形的显示切断的风险。对于矩形视口,例如普通的笔记本电脑显示器,其值等于零。对于非矩形显示器(如圆形表盘,iPhoneX 屏幕),在用户代理设置的四个值形成的矩形内,所有内容均可见。
209 |
210 | 其中 env() 用法为 env( , ? ),第一个参数为自定义的区域,第二个为备用值。其中 var() 用法为 var( , ? ),作用是在 env() 不生效的情况下,给出一个备用值。
211 |
--------------------------------------------------------------------------------
/04~移动端与响应式/响应式适配/响应式尺寸/屏幕尺寸.md:
--------------------------------------------------------------------------------
1 | # CSS 屏幕基础与尺寸单元详解
2 |
3 | 对页面布局原理有更深的了解,从而准确地控制好网页元素,保证按照预定的方式显示。
4 |
5 | # 屏幕基础
6 |
7 | ## Pixel | 像素
8 |
9 | 像素(Pixel)是数字图像的最小组成单元,它不是一个物理尺寸,但和物理尺寸存在一个可变的换算关系(物理尺寸之间的换算是固定的)。像素就是分辨率的单位,分辨率 1920px\*1080px 的意思就是,在这个华为荣耀 7 的 5.2 英寸屏幕上,在竖向的高度上有 1920 个像素块,在横向的宽度上有 1080 个像素块。
10 |
11 | PPI(Pixel Per Inch)是指每英寸包含的像素数,同时也是针对这个换算关系的一个描述性指标。其中的英寸(Inch)和厘米(cm)、毫米(mm)等尺寸一样,都属于物理尺寸。不同的显示设备会有不同的 PPI,因此,每一个像素点的物理尺寸(可以理解为你如果能拿刻度尺来量的话,得到的数值),也会因为设备的不同而存在差异。一般来说,越高的 PPI,相当于在单位物理尺寸内用了更多、更小的像素点来显示图像,因此会更清晰。
12 |
13 | 与 PPI 类似的,还有一个 DPI(Dot Per Inch),是指每英寸打印的点数,表示了打印机的打印精度,是属于印刷行业的概念。但如今随着数字化的输入输出设备的发展,很多人也把数字图像的解析度用 DPI 来表示。严格来说,印刷时计算的网点和电脑显示器的显示像素并不相同,但现在已普遍认同,数字图像显示屏的信息,用 DPI 或 PPI 表示均可行,是相同的含义。
14 |
15 | 设备像素(device pixels)是指与硬件设备直接相关的像素,是真实的屏幕设备中的像素点。比如说,一个电脑显示器的参数中,最佳分辨率是 1920x1080,那么指的就是这个显示器在屏幕上用于显示的实际像素点,也就是设备像素。
16 |
17 | 另一个概念是 css 像素(css pixels)。css 像素是指网页布局和样式定义所使用的像素,也就是说,css 代码中的 px,对应的就是 css 像素。那么,css 像素和设备像素有什么区别呢?简单地说,css 像素比设备像素要更“虚拟”一些。下面来解释这一点。在桌面电脑上,浏览器有一个很少使用的功能:缩放。比如下边这个矩形元素,宽度是 128px,高度是 40px。显然,这里的尺寸是 css 像素。矩形元素的 css 像素尺寸没有变化,同样,你的显示器的设备像素尺寸也不会变化。但是,放大后,元素看起来变大了,在你的屏幕上占据了更大的空间。对应地,如果是缩小,则元素看起来变小了,在屏幕上占据的空间也变小了。
18 |
19 | css 像素和设备像素之间是一种可变的转化关系。在 100%缩放比例下,1 个 css 像素等于 1 个设备像素。在表示某一数目的 css 像素时,在放大状态下使用了更多的设备像素,而在缩小状态下使用了更少的设备像素。这就是 css 像素和设备像素的概念。
20 |
21 | 对前端开发来说,设备像素没有意义,我只会关心 css 像素。只有 css 像素才描述了网页的布局与外观,我只需要让我的网页在 100%缩放比例下看起来不错就可以了。
22 |
23 | ## viewport | 视口
24 |
25 | 正常情况下,viewport 和浏览器的显示窗口是一样大小的。但是,在移动设备上,两者可能不是一样大小。
26 |
27 | ```js
28 | if (typeof window.innerWidth != "undefined") {
29 | (viewportwidth = window.innerWidth), (viewportheight = window.innerHeight);
30 | }
31 | ```
32 |
33 | viewport 的作用即为:
34 |
35 | - width 控制 viewport 的宽度,可以是固定值,也可以是 device-width,即设备的宽度
36 | - height 高度
37 | - initial-scale 控制初始化缩放比例,1.0 表示不可以缩放
38 | - maximum-scale 最大缩放比例
39 | - minimum-scale 最小缩放比例
40 | - user-scalable: 是否允许用户缩放
41 |
42 | 可见如果不定义 viewport 的话,页面宽度以屏幕分辨率为基准,而设置以后可以根据设备宽度来调整页面,达到适配终端大小的效果
43 |
44 | 比如,手机浏览器的窗口宽度可能是 640 像素,这时 viewport 宽度就是 640 像素,但是网页宽度有 950 像素,正常情况下,浏览器会提供横向滚动条,让用户查看窗口容纳不下的 310 个像素。另一种方法则是,将 viewport 设成 950 像素,也就是说,浏览器的显示宽度还是 640 像素,但是网页的显示区域达到 950 像素,整个网页缩小了,在浏览器中可以看清楚全貌。这样一来,手机浏览器就可以看到网页在桌面浏览器上的显示效果。
45 |
46 | 
47 |
48 | ```css
49 | .full-width {
50 | width: 100vw;
51 | position: relative;
52 | left: 50%;
53 | right: 50%;
54 | margin-left: -50vw;
55 | margin-right: -50vw;
56 | }
57 | ```
58 |
59 | 然后,手机浏览器的供应商是这么考虑的:由于手机屏幕的宽度对于 css 网页布局来说太小,为了让更多的网页能正常显示(一些流体布局的网页会在过窄的视口中变得一团乱),应该让视口更宽,超越屏幕的宽度。所以,在手机浏览器中,视口被划分为了两个:可见视口(visual viewport)和布局视口(layout viewport)。可见视口是指当前在手机屏幕上显示的部分。当你做缩放的时候,可见视口的尺寸(css 像素值)也会变化。
60 |
61 | 和可见视口不同,布局视口用于元素布局和尺寸计算(比如百分比的宽度值),而且比可见视口明显要更宽。无论你缩放,或者滑动页面,甚至翻转手机屏幕,布局视口始终不变。前文介绍过元素会取视口的宽度值,在手机上,这个限定和确定的是布局视口。这就是手机浏览器在处理时和桌面电脑浏览器不一样的地方,而这个布局视口的引入,保证了网页在手机里中的显示与在桌面电脑上的一致。布局视口的宽度是由手机浏览器定义的,随浏览器不同而不同。比如 Safari 是 980px,Android Webkit 是 800px。这都远比屏幕宽度值要大。
62 |
63 | 手机中的布局视口是可以更改的。你一定在很多移动版网页中见到过下边这个标签元素。
64 |
65 | ```
66 |
67 | ```
68 |
69 | 这是最早由 Safari 引入,但如今已普遍被各类手机浏览器认可了的一项设置。这其中有一句 width=device-width,它的意思是,把手机浏览器的布局视口的宽度,更改为当前设备的宽度。你还可以使用 width=500 这样的具体数值(也是 css 像素值)。总的来说,使用这个标签元素,就可以告诉手机浏览器当前页面应该使用的布局视口的尺寸。
70 |
71 | 做一下总结:
72 |
73 | - 当 视觉视口 = 布局视口,不会出现横向滚动条,而且 1css 像素 = 1 设备像素
74 | - 当 视觉视口 < 布局视口,会出现横向滚动条,而且 1css 像素 = 1 设备像素
75 | - 当 视觉视口 > 布局视口,不会出现横向滚动条,而且 1css 像素 > 1 设备像素
76 |
77 | # Viewport
78 |
79 | Viewport 指的是网页的显示区域,也就是不借助滚动条的情况下,用户可以看到的部分网页大小,中文译为“视口”。视窗(Viewport)指的就是浏览器可视化的区域,而这个可视区域是 window.innerWidth/window.innerHeight 的大小。
80 |
81 | - vw:是 Viewport's width 的简写,1vw 等于 window.innerWidth 的 1%
82 |
83 | - vh:和 vw 类似,是 Viewport's height 的简写,1vh 等于 window.innerHeihgt 的 1%
84 |
85 | - vmin:vmin 的值是当前 vw 和 vh 中较小的值
86 |
87 | - vmax:vmax 的值是当前 vw 和 vh 中较大的值
88 |
89 | vmin 和 vmax 是根据 Viewport 中长度偏大的那个维度值计算出来的,如果 window.innerHeight > window.innerWidth 则 vmin 取百分之一的 window.innerWidth,vmax 取百分之一的 window.innerHeight 计算。
90 |
91 | 
92 |
93 | # Lengths Units | 尺寸单元
94 |
95 | ## Relative Lengths Units | 相对尺寸单元
96 |
97 | Relative length units specify a length relative to another length property. Relative length units scales better between different rendering mediums.
98 |
99 | | Unit | Description |
100 | | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
101 | | em | Relative to the font-size of the element (2em means 2 times the size of the current font)[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_em) |
102 | | ex | Relative to the x-height of the current font (rarely used)[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_ex) |
103 | | ch | Relative to width of the "0" (zero) |
104 | | rem | Relative to font-size of the root element |
105 | | vw | Relative to 1% of the width of the viewport\*[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_vw) |
106 | | vh | Relative to 1% of the height of the viewport\*[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_vh) |
107 | | vmin | Relative to 1% of viewport's\* smaller dimension[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_vmin) |
108 | | vmax | Relative to 1% of viewport's\* larger dimension[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_vmax) |
109 | | % | |
110 |
111 | ## Absolute Lengths
112 |
113 | The absolute length units are fixed and a length expressed in any of these will appear as exactly that size.
114 |
115 | Absolute length units are not recommended for use on screen, because screen sizes vary so much. However, they can be used if the output medium is known, such as for print layout.
116 |
117 | | Unit | Description |
118 | | ---- | ------------------------------------------------------------------------------------------------------- |
119 | | cm | centimeters[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_cm) |
120 | | mm | millimeters[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_mm) |
121 | | in | inches (1in = 96px = 2.54cm)[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_in) |
122 | | px | pixels (1px = 1/96th of 1in)[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_px) |
123 | | pt | points (1pt = 1/72 of 1in)[Try it](http://www.w3schools.com/cssref/tryit.asp?filename=trycss_unit_pt) |
124 | | pc | picas (1pc = 12 pt) |
125 |
126 | # Links
127 |
128 | - https://evilmartians.com/chronicles/images-done-right-web-graphics-good-to-the-last-byte-optimization-techniques 其中有对 Pixel 的详细介绍
129 |
--------------------------------------------------------------------------------
/04~移动端与响应式/响应式适配/媒介查询/媒介查询.md:
--------------------------------------------------------------------------------
1 | # 媒介查询
2 |
3 | # 声明式的设置响应式布局
4 |
5 | 声明式的概念在前端很是流行,譬如著名的 React 就是典型的声明式组件库。声明式编程应用到 CSS 中即是 CSS 应当定义 What it wants,而不是 How it should。我们上面讨论过的一个关于分割点的容易混淆之处就是分割点同时代表了某个范围。譬如`$large:600px`这种定义在`large`这个词本身代表一个范围值的时候就会混淆。因此在具体的某个组件或者标签中,我们应该对其隐藏具体的尺寸设置,譬如:
6 |
7 | ```scss
8 | @mixin for-phone-only {
9 | @media (max-width: 599px) {
10 | @content;
11 | }
12 | }
13 | @mixin for-tablet-portrait-up {
14 | @media (min-width: 600px) {
15 | @content;
16 | }
17 | }
18 | @mixin for-tablet-portait-only {
19 | @media (min-width: 600px) and (max-width: 899px) {
20 | @content;
21 | }
22 | }
23 | @mixin for-tablet-landscape-up {
24 | @media (min-width: 900px) {
25 | @content;
26 | }
27 | }
28 | @mixin for-tablet-landscape-only {
29 | @media (min-width: 900px) and (max-width: 1199px) {
30 | @content;
31 | }
32 | }
33 | @mixin for-desktop-up {
34 | @media (min-width: 1200px) {
35 | @content;
36 | }
37 | }
38 | @mixin for-desktop-only {
39 | @media (min-width: 1200px) and (max-width: 1799px) {
40 | @content;
41 | }
42 | }
43 | @mixin for-big-desktop-up {
44 | @media (min-width: 1800px) {
45 | @content;
46 | }
47 | }
48 |
49 | // usage
50 | .my-box {
51 | padding: 10px;
52 |
53 | @include for-desktop-up {
54 | padding: 20px;
55 | }
56 | }
57 | ```
58 |
59 | 你会发现在上面的定义中我很推荐使用`-up`与`-only`后缀,在具体使用样式的地方:
60 |
61 | ```scss
62 | .phone-only {
63 | @include for-phone-only {
64 | background: purple;
65 | }
66 | }
67 |
68 | .tablet-portait-only {
69 | @include for-tablet-portait-only {
70 | background: purple;
71 | }
72 | }
73 |
74 | .tablet-portrait-up {
75 | @include for-tablet-portrait-up {
76 | background: purple;
77 | }
78 | }
79 |
80 | .tablet-landscape-only {
81 | @include for-tablet-landscape-only {
82 | background: purple;
83 | }
84 | }
85 |
86 | .tablet-landscape-up {
87 | @include for-tablet-landscape-up {
88 | background: purple;
89 | }
90 | }
91 |
92 | .desktop-only {
93 | @include for-desktop-only {
94 | background: purple;
95 | }
96 | }
97 |
98 | .desktop-up {
99 | @include for-desktop-up {
100 | background: purple;
101 | }
102 | }
103 |
104 | .big-desktop-up {
105 | @include for-big-desktop-up {
106 | background: purple;
107 | }
108 | }
109 | ```
110 |
111 | 不过这种方式在需要大量自定义媒介查询搭配的时候就显得不是那么灵活,我们可以提供更加细粒度的控制方式:
112 |
113 | ```scss
114 | @mixin for-size($size) {
115 | @if $size == phone-only {
116 | @media (max-width: 599px) {
117 | @content;
118 | }
119 | } @else if $size == tablet-portrait-up {
120 | @media (min-width: 600px) {
121 | @content;
122 | }
123 | } @else if $size == tablet-portait-only {
124 | @media (min-width: 600px) and (max-width: 899px) {
125 | @content;
126 | }
127 | } @else if $size == tablet-landscape-up {
128 | @media (min-width: 900px) {
129 | @content;
130 | }
131 | } @else if $size == tablet-landscape-only {
132 | @media (min-width: 900px) and (max-width: 1199px) {
133 | @content;
134 | }
135 | } @else if $size == desktop-up {
136 | @media (min-width: 1200px) {
137 | @content;
138 | }
139 | } @else if $size == desktop-only {
140 | @media (min-width: 1200px) and (max-width: 1799px) {
141 | @content;
142 | }
143 | } @else if $size == big-desktop-up {
144 | @media (min-width: 1800px) {
145 | @content;
146 | }
147 | }
148 | }
149 |
150 | // usage
151 | .my-box {
152 | padding: 10px;
153 |
154 | @include for-size(desktop-up) {
155 | padding: 20px;
156 | }
157 | }
158 | ```
159 |
160 | 这种方式自然能够达成预期的效果,不过如果某个粗心的开发者传入了某个未预定义的范围名,那么久尴尬了,因此我们还是建议不要传入某个具体的尺寸,而是传入某个范围:
161 |
162 | ```scss
163 | @mixin for-size($range) {
164 | $phone-upper-boundary: 600px;
165 | $tablet-portrait-upper-boundary: 900px;
166 | $tablet-landscape-upper-boundary: 1200px;
167 | $desktop-upper-boundary: 1800px;
168 |
169 | @if $range == phone-only {
170 | @media (max-width: #{$phone-upper-boundary - 1}) {
171 | @content;
172 | }
173 | } @else if $range == tablet-portrait-up {
174 | @media (min-width: $phone-upper-boundary) {
175 | @content;
176 | }
177 | } @else if $range == tablet-portait-only {
178 | @media (min-width: $phone-upper-boundary) and (max-width: #{$tablet-portrait-upper-boundary - 1}) {
179 | @content;
180 | }
181 | } @else if $range == tablet-landscape-up {
182 | @media (min-width: $tablet-landscape-upper-boundary) {
183 | @content;
184 | }
185 | } @else if $range == tablet-landscape-only {
186 | @media (min-width: $tablet-portrait-upper-boundary) and (max-width: $tablet-landscape-upper-boundary - 1px) {
187 | @content;
188 | }
189 | } @else if $range == desktop-up {
190 | @media (min-width: $tablet-landscape-upper-boundary) {
191 | @content;
192 | }
193 | } @else if $range == desktop-only {
194 | @media (min-width: $tablet-landscape-upper-boundary) and (max-width: $desktop-upper-boundary - 1px) {
195 | @content;
196 | }
197 | } @else if $range == big-desktop-up {
198 | @media (min-width: $desktop-upper-boundary) {
199 | @content;
200 | }
201 | }
202 | }
203 |
204 | // usage
205 | .my-box {
206 | padding: 10px;
207 |
208 | @include for-size(desktop-up) {
209 | padding: 20px;
210 | }
211 | }
212 | ```
213 |
214 | # Responsive Menu
215 |
216 | 老实说,笔者一直觉得响应式开发最成功的应用就是菜单了,这里介绍简单的响应式菜单的开发方式,借鉴了[W3School](http://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_topnav)的教程,首先,HTML 的元素布局为:
217 |
218 | ```html
219 |
228 | ```
229 |
230 | 然后我们可以添加基本的样式:
231 |
232 | ```scss
233 | /* Remove margins and padding from the list, and add a black background color */
234 | ul.topnav {
235 | list-style-type: none;
236 | margin: 0;
237 | padding: 0;
238 | overflow: hidden;
239 | background-color: #333;
240 | }
241 |
242 | /* Float the list items side by side */
243 | ul.topnav li {
244 | float: left;
245 | }
246 |
247 | /* Style the links inside the list items */
248 | ul.topnav li a {
249 | display: inline-block;
250 | color: #f2f2f2;
251 | text-align: center;
252 | padding: 14px 16px;
253 | text-decoration: none;
254 | transition: 0.3s;
255 | font-size: 17px;
256 | }
257 |
258 | /* Change background color of links on hover */
259 | ul.topnav li a:hover {
260 | background-color: #111;
261 | }
262 |
263 | /* Hide the list item that contains the link that should open and close the topnav on small screens */
264 | ul.topnav li.icon {
265 | display: none;
266 | }
267 | ```
268 |
269 | 然后通过 Media Query 进行不同的尺寸下的样式设置:
270 |
271 | ```scss
272 | /* When the screen is less than 680 pixels wide, hide all list items, except for the first one ("Home"). Show the list item that contains the link to open and close the topnav (li.icon) */
273 | @media screen and (max-width:680px) {
274 | ul.topnav li:not(:first-child) {display: none;}
275 | ul.topnav li.icon {
276 | float: right;
277 | display: inline-block;
278 | }
279 | }
280 |
281 | /* The "responsive" class is added to the topnav with JavaScript when the user clicks on the icon. This class makes the topnav look good on small screens */
282 | @media screen and (max-width:680px) {
283 | ul.topnav.responsive {position: relative;}
284 | ul.topnav.responsive li.icon {
285 | position: absolute;
286 | right: 0;
287 | top: 0;
288 | }
289 | ul.topnav.responsive li {
290 | float: none;
291 | display: inline;
292 | }
293 | ul.topnav.responsive li a {
294 | display: block;
295 | text-align: left;
296 | }
297 | }
298 | ```
299 |
300 | 对于实际的效果的例子在[这里](http://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_topnav)
301 |
--------------------------------------------------------------------------------
/03~性能优化/渲染执行优化/脚本解析与执行.md:
--------------------------------------------------------------------------------
1 | # 脚本解析与执行
2 |
3 | # JavaScript 脚本初始化
4 |
5 | # JavaScript 函数执行优化
6 |
7 | ## Debounce: 防抖动
8 |
9 | ## Throttling: 限流
10 |
11 | # CSS 解析与应用
12 |
13 | # DOM Manipulation Optimization: DOM 操作优化
14 |
15 | 随着现代 Web 技术的发展与用户交互复杂度的增加,我们的网站变得日益臃肿,也要求着我们不断地优化网站性能以保证友好的用户体验。本文作者则着眼于 JavaScript 启动阶段优化,首先以大量的数据分析阐述了语法分析、编译等步骤耗时占比过多是很多网站的性能瓶颈之一。然后作者提供了一系列用于在现代浏览器中进行性能评测的工具,还分别从开发者工程实践与 JavaScript 引擎内部实现的角度阐述了应当如何提高解析与编译速度。
16 |
17 | 在 Web 开发中,随着需求的增加与代码库的扩张,我们最终发布的 Web 页面也逐渐膨胀。不过这种膨胀远不止意味着占据更多的传输带宽,其还意味着用户浏览网页时可能更差劲的性能体验。浏览器在下载完某个页面依赖的脚本之后,其还需要经过语法分析、解释与运行这些步骤。而本文则会深入分析浏览器对于 JavaScript 的这些处理流程,挖掘出那些影响你应用启动时间的罪魁祸首,并且根据我个人的经验提出相对应的解决方案。回顾过去,我们还没有专门地考虑过如何去优化 JavaScript 解析/编译这些步骤;我们预想中的是解析器在发现`
118 | ```
119 |
120 | ## Lazy Load CSS
121 |
122 | 正如上文所述,我们的网站偏向于静态展示,因此首屏的最大问题就是 CSS 文件的加载问题。浏览器会在 head 标签中声明的所有 CSS 文件下载完毕之前一直处于阻塞状态,这种机制很是明智的,不然的话浏览器在加载多个 CSS 文件的时候会进行重复的布局与渲染,这更是对于性能的浪费。为了避免非首屏的 CSS 文件阻塞页面渲染,我们使用[loadCSS](https://github.com/filamentgroup/loadCSS)这个小的工具库来进行异步的 CSS 文件加载,它会在 CSS 文件加载完毕后执行回调。不过,异步加载 CSS 也会带来一个新的问题,如果我们将所有的 CSS 全部设置为了异步加载,那么用户会首先看到单纯的 HTML 页面,这也会给用户不好的体验。那么我们就需要在异步加载与首屏渲染之间找到一个平衡点,即首先加载那些必要的 CSS 文件。我们一般将首屏渲染中必要的 CSS 文件成为 Critical CSS,即关键的 CSS 文件,代指在保证页面的可读性的前提下需要加载的最少的 CSS 文件数目。Critical CSS 的选定会是一个非常耗时的过程,特别是我们网站本身的 CSS 样式设置也在不停变更,我们不可能完全依赖于人工去提取出关键的 CSS 文件,这里推荐[Critical](https://github.com/addyosmani/critical)这个辅助工具能够帮你自动提取压缩 Critical CSS。下图的一个对比即是仅加载 Critical CSS 与加载全部 CSS 的区别:
123 |
124 | 
125 |
126 | 上图中红色的线,即是所谓的折叠分割点。
127 |
128 | # 服务端与缓存
129 |
130 | 高性能的前端离不开服务端的支持,在我们的实践中也发现不同的服务端配置同样会影响到前端的性能。目前我们主要使用 Apache Web Server 作为中间件,并且通过 HTTPS 来安全地传递内容。
131 |
132 | ## Configuration
133 |
134 | 我们首先对于合适的服务端配置做了些调研,这里推荐是使用[H5BP Boilerplate Apache Configuration](https://github.com/h5bp/server-configs-apache)作为配置模板,它是个不错的兼顾了性能与安全性的配置建议。同样地它也提供了面向其他服务端环境的配置。我们对于大部分的 HTML、CSS 以及 JavaScript 都开启了 GZip 压缩选项,并且对于大部分的资源都设置了缓存策略,详见下文的 File Level Caching 章节。
135 |
136 | ## HTTPS
137 |
138 | 使用 HTTPS 可以保证站点的安全性,但是也会影响到你网站的性能表现,性能损耗主要发生在建立 SSL 握手协议的时候,这会导致很多的延迟,不过我们同样可以通过某些设置来进行优化。
139 |
140 | - 设置 HTTP Strict Transport Security 请求头可以让服务端告诉浏览器其只允许通过 HTTPS 进行交互,这就避免了浏览器从 HTTP 再重定向到 HTTPS 的时间消耗。
141 |
142 | - 设置 TLS false start 允许客户端在第一轮 TLS 中就能够立刻传递加密数据。握手协议余下的操作,譬如确认没有人进行中间人监听可以同步进行,这一点也能节约部分时间。
143 |
144 | - 设置 TLS Session Resumption,当浏览器与服务端曾经通过 TLS 进行过通信,那么浏览器会自动记录下 Session Identifier,当下次需要重新建立连接的时候,其可以复用该 Identifier,从而解决了一轮的时间。
145 |
146 | 这里推荐扩展阅读下 [Mythbusting HTTPS: Squashing security’s urban legends by Emily Stark](https://www.youtube.com/watch?v=YMfW1bfyGSY)。
147 |
148 | ## Cookies
149 |
150 | 我们并没有使用某个服务端框架,而是直接使用了静态的 Apache Web Server,不过 Apache Web Server 也是能够读取 Cookie 并且进行些简单的操作。譬如在下面这个例子中我们将 CSS 缓存信息存放在了 Cookie 中,然后交付 Apache 进行判断是否需要重复加载 CSS 文件:
151 |
152 | ```html
153 |
154 |
155 |
156 |
168 |
169 |
172 |
173 |
174 |
175 |
176 | ```
177 |
178 | 这里 Apache Server 中的逻辑控制代码就是有点类似于注释形式的 `` 值为 `true` 我们便能假设该用户是第一次访问该站点
185 |
186 | - 如果用户是首次浏览,我们添加了一个 `