我们正式开始学习之前,首先让我们了解一些浏览器的一些知识点,然后再对浏览器渲染原理深刻的理解一下,这次学习是针对Chrome浏览器的渲染机制。
最新的Chrome浏览器包括:1个浏览器主进程,一个GPU进程,一个网络进程,多个渲染进程和多个插件进程。
浏览器进程:主要负责界面显示、用户交互、子进程管理、同时提供存储等功能
渲染进程:核心任务将HTML、CSS和JavaScript转化为用户可以交互的网页,排版引擎Blink和JavaScript引擎V8都是运行在该线程。默认情况下,Chrome浏览器为每个标签页创建一个渲染进程,但从一个页面打开另一个页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染流程
GPU进程:GPU的初衷是为了使用3D CSS效果,随后网页、Chrome的UI界面都采用了GPU来绘制,使得GPU成为浏览器普遍的需求,Chrome在其多进程架构上也引入了GPU进程
网络进程:主要负责页面的网络资源加载
插件进程:主要负责插件的运行,因插件容易崩溃,所以需要通过插件进程来隔离,保证插件进程崩溃不会对浏览器和页面造成影响。[1] [张2]
服务器响应浏览器的HTML请求后,浏览器进程开始准备渲染流程,浏览器进程解析HTML文件,构建DOM树
CSS解析
CSS样式来源主要有三种:
1. 通过link或@import引入外部CSS
2. 标记内的CSS
3. 元素style的属性内嵌的CSS
解析CSS的顺序是浏览器样式>用户自定义样式>页面link标签等引进来的样式>style标签的内联样式
浏览器的渲染引擎接收到CSS文本时,将CSS文本转换为浏览器可以理解的结构styleSheets(CSSOM)
1. 创建布局树
DOM树中的所有可见节点,并把这些节点加到布局中
忽略不可见节点,比如:head标签,具有display:node样式的元素
2. 布局计算
根据布局树对各节点的几何坐标位置进行计算,输出带有坐标位置的布局树
在生成布局树之后不能直接绘制,渲染进程会将一些复杂的3D动画,滚动条,z-index层级高的生成图层,并生成一颗图层树交给GPU加速渲染。
可以通过chrome开发工具,选择more tool下的layers标签,来查看网页的图层状态。
拥有层叠上下文对象的元素会被提升为单独的一层。
文档中层叠上下文满足以下任意一个条件的元素形成:
1. 根元素(html)
2. z-index值不为auto的绝对/相对定位元素
3. fixed/sticky定位
4. z-index值不为auto的flex子项
5. opacity属性值小于1的元素
6. transform、filter、perspective、clip-path、mask、mask-image、mask-border属性值不为none的元素
7. isolation属性被设置为isolate的元素
8. -webkit-overflow-scrolling属性值为touch的元素
9. 在will-change中指定任意CSS属性
10. contain属性为layout、paint或者综合值(如:strict、content)
在层叠上下文中,子元素同样按照这个规则进行层叠。重要的是,子级层叠上下文的z-idnex值只在父级中有意义,子级层叠上下文被自动视为父级层叠上下文的一个独立单元。
需要剪裁(clip)的地方会被创建图层
当子元素需要展示的区域大于父元素的大小时,就会需要剪裁。出现剪裁情况的时候,渲染引擎回味文字部分单独擦护功能键一个层,如果出现滚动条,滚动条也会被提升为单独的层。
下面看这么一个案例:
总结:元素有了层叠上下文属性或者需要被剪裁,满足任意一点,就会提升为单独一层。
渲染引擎会把一个图层的绘制拆分成很多小绘制指令,然后把这些指令按照顺序组成待绘制列表
栅格化的过程就是将图块转化成位图的过程。图块是栅格化的最小单位(通常为256×256或者512×512)。合成线程会按照视口附近的图块优先生成位图的原则将图块生成位图,而实际生成位图的操作是由栅格化来执行的。渲染过程中借用GPU来加速生成,最后将生成的位图保存到GPU内存中。
图块被栅格化后完成后,合成线程会生成绘制图块的指令-DrawQuad并提交给浏览器进程。浏览器进程viz组件,用来接收DrawQuad指令,然后将页面内容会知道内存中,最后将内存数据显示在屏幕上。
完整的渲染流程大致总结如下:
1.渲染引擎将HTML内容转换为DOM树结构
2.渲染引擎将CSS样式表转化为浏览器理解的styleSheets,计算DOM节点样式
3.创建布局树,计算元素的布局信息
4.对布局树进行分层,生成分层树
5.对每个图层生成绘制列表,并将其提交到合成线程
6.合成线程将图层分成图块,并在栅格化线程池中将图块转换成位图
7.合成线程发送绘制图块命令DrawQuad给浏览器进程
8.浏览器进程根据DrawQuad生成页面,显示在显示器上
[3] [张4]
1. CSS不会阻塞DOM解析,但会阻塞页面渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
div {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
}
</style>
<link rel="stylesheet" href="./index.css" />
<script src="./index.js" defer></script>
</head>
<body>
</body>
</html>
//index.js
const div = document.querySelector("div");
console.log(div);
//index.css
div {
width: 200px;
height: 200px;
background: gray;
}
//server.js
const express = require("express");
const fs = require("fs");
const app = express();
app.get("/", (req, res) => {
fs.readFile("./index.html", (err, data) => {
res.send(data.toString());
});
});
app.get("/index.css", (req, res) => {
res.set("Content-Type", "text/css");
fs.readFile("./index.css", (err, data) => {
console.log(data);
setTimeout(() => {
res.send(data.toString());
}, 3000);
});
});
app.get("/index.js", (req, res) => {
fs.readFile("./index.js", (err, data) => {
res.send(data.toString());
});
});
app.listen(8080, (err) => {
if (err) {
console.log(err);
} else {
console.log("服务器启动成功");
}
});
script标签上的defer属性是通知浏览器该脚本在文档完成解析后执行,这样在DOM解析后马上能打印出来div。
通过结果看的出来,css文件内容会延迟3秒后响应,通过效果看出来的是控制台先打印的div标签,3秒后div蓝色背景样式出来,这就证明了css是不会阻塞DOM解析的。
CSS解析不会阻塞DOM解析,那我们认为的应该是div背景色的一个改变,从红色变为灰色,但是效果是仅仅呈现了最终结果灰色,没有变色这个过程,因此CSS是阻塞页面渲染的。
2. JS阻塞DOM解析,浏览器遇到<script>标签时,会触发页面渲染
当把scrpit标签的defer属性删除掉,我们延迟加载js文件,发现我们打印的结果是null,随后页面才会出现效果,这个效果说明JS阻塞了DOM解析。
现代浏览器会先提前下载script,link,src等标签的资源,不会等解析到标签的时候才下载。
当我们把script标签和link标签放在div标签下面加载的时候,发现页面会先展示红色然后变成灰色的一个过程,浏览器在遇到script标签的时候渲染了一次页面,加载完css文件后,由重新渲染了一次。这是因为浏览器不知道脚本内容,因此碰到脚本内容时,先渲染一遍页面,确保脚本获取到最新的DOM信息。
总结:
1. CSS不会阻塞DOM解析,但会阻塞DOM渲染
2. JS阻塞DOM解析,但浏览器会预先下载相关资源
3. 浏览器遇到scirpt且没有defer或async属性的时,会触发页面渲染,如果前面CSS资源未加载完毕,浏览器会等待加载完成后执行脚本
1. 重绘:元素样式的改变(但宽高、大小、位置等不变)如:outline,visibility,color等
2. 回流(重排):元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染。如:添加或删除可见的DOM元素;元素的位置、尺寸发生变化,内容发生变化(文本变化或图片被另一个尺寸图片替代)
注意:回流一定会触发重绘,重绘不一定发生回流,回流比重绘会更消耗性能,他们会导致web应用的UI反映迟钝。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#box {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
let box = document.getElementById("box");
/*
分离读写:
box.style.width = "200px";
console.log(box.clientHeight) 这一步会修改渲染机制,会造成两次回流
box.style.height = "200px";
box.style.margin = "200px";
现代浏览器有渲染等待机制,会触发一次回流(重排),老版浏览器会触发三次回流(重排)
*/
/*
批量处理(样式集中改变)
box.style.cssText="width:20px,height:20px"
box.className="box"
*/
/*
缓存布局信息
因为有读操作box.clientWidth和box.clientHeight 会触发两次回流
box.style.width = box.clientWidth+10+'px'
box.style.heght = box.clientHeight+10+'px'
实现(本质:分离读写):
a=box.clientWidth b=box.clientHeight
box.style.width = a+10+'px'
box.style.heght = b+10+'px'
*/
/*
元素批量修改
1.文档碎片
let frg = document.createDocumentFragment()
for (let i = 0; i < 5; i++) {
let newLi = document.createElement("li");
newLi.innerHTML = i;
//box.appendChild(frg); 触发5次回流
frg.appendChild(newLi);
}
// 一次性把内容放到容器中,触发一次回流
box.appendChild(frg);
frg = null;
2. 字符串拼接
let str=''
for (let i = 0; i < 5; i++) {
str+= '<li>i</li>'
}
box.innerHTML(str); //触发一次回流
str = ''
*/
/*
动画效果应用到postion属性为absolute或fixed的元素(脱离文档流,防止影响其他元素)
*/
/*
CSS3硬件加速(硬件加速规避回流)
使用CSS样式:transform、opacity等这些属性会触发硬件加速,不会引入回流和重绘
box.style.left = '100px'//触发一次回流
box.style.transform='translateX('200px')'
*/
/*
牺牲平滑度换取速度
每次1像素移动动画,如果动画使用100%CPU,动画就会看上去跳动的,浏览器不断的在更新回流
每次移动3像素效果的话平滑度降低了,但不会导致在CPU较慢的机器中抖动
*/
/*
避免table布局和使用CSS的javascript表达式(尽可能不使用table)
*/
</script>
</body>
</html>
[5] [张6] 我们可以通过以下几种方式来避免DOM的回流
1. 放弃传统操作DOM,基于vue、react数据影响视图模数
2. 分离读写操作(现代浏览器都有渲染队列的机制)
3. 样式集中改变
4. 缓存布局信息
5. 元素批量修改
6. 动画效果应用到position属性为absolute或fixed的元素(脱离文档流)
7. CSS3硬件加速
8. 牺牲平滑度换取速度
9. 避免table布局
web前端之二叉搜索树
天我们将利用vue的条件指令来完成一个简易的动态变色功能按钮
首先我们还是要对vue进行配置.
然后我们要在html页面上搭建三个简易的按钮,颜色分别为紫,绿和蓝(颜色随意)其代码如下:
<body>
<div id="app">
<p>
<button @click="btn_click('pg1')" :style="{background:'rebeccapurple'}">紫</button>
<button @click="btn_click('pg2')" :style="{background:'yellowgreen'}">绿</button>
<button @click="btn_click('pg3')" :style="{background:'cornflowerblue'}">蓝</button>
</p>
</div>
</body>
这里的@是v-on事件指令,在这里要在三个按钮上设置点击事件
接着我们要进行条件指令的判断,其代码如下:
<div class="box pg1" v-if="pg == 'pg1'" key="pg1"></div>
<div class="box pg2" v-else-if="pg == 'pg2'" key="pg2"></div>
<div class="box pg3" v-else key="pg3"></div>
通过条件来判断点击不同的按钮触发不同的效果
接下来我们将进行挂载点,事件的渲染以及为事件提供实现体操作
<script>
new Vue({
el: '#app',
data: {
pg: 'pg1' },
methods: {
btn_click: function (pg_num) {
this.pg = pg_num
}
}
})
</script>
这里我们设置进入页面后默认显示第一个按钮显示的图片,通过点击来完成事件的转换.
最后就是把图片给设置出来啦
<style>
.box {
width: 200px;
height: 100px;
background-color: darkgray;
}
.pg1 { background-color: rebeccapurple; }
.pg2 { background-color: yellowgreen; }
.pg3 { background-color: cornflowerblue; }
</style>
当然也可以选择你喜欢的图片进行放置,这里我们只是放置颜色填充的框
具体的实现效果如下:
通过不同的点击来获得不同的图片!
整体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.box {
width: 200px;
height: 100px;
background-color: darkgray;
}
.pg1 { background-color: rebeccapurple; }
.pg2 { background-color: yellowgreen; }
.pg3 { background-color: cornflowerblue; }
</style>
</head>
<body>
<div id="app">
<p v-if="is_if" key="my_if">v-if的显示与隐藏</p>
<p v-show="is_show" key="my_show">v-show的显示与隐藏</p>
<p>
<button @click="btn_click('pg1')" :style="{background:'rebeccapurple'}">紫</button>
<button @click="btn_click('pg2')" :style="{background:'yellowgreen'}">绿</button>
<button @click="btn_click('pg3')" :style="{background:'cornflowerblue'}">蓝</button>
<div class="box pg1" v-if="pg == 'pg1'" key="pg1"></div>
<div class="box pg2" v-else-if="pg == 'pg2'" key="pg2"></div>
<div class="box pg3" v-else key="pg3"></div>
</p>
</div>
</body>
<script src="js/vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
is_if: false,
is_show: true,
pg: 'pg1'
},
methods: {
btn_click: function (pg_num) {
this.pg = pg_num
}
}
})
</script>
</html>
“我们相信人人都可以成为一个WEB前端开发大神,现在开始,找个师兄,带你入门,学习的路上不再迷茫。这里是WEB前端开发修真院,初学者转行到互联网行业的聚集地。"
大师兄送你套WEB前端入门心法
限时免费获取方式
▼
领取方法:
关注“速学编程” 然后私信回复“前端”即可
私信不要多字,不要少字,不要错字,私信方法:点击我头像,进入主页面,右上角有私信功能,在关注的上方位置。
如果对您有帮助请记得给速学编程先来个“评论+转发”
到题目是不是吓了一跳?css竟然还有这个操作?还真是第一次听说~
原理嘛,其实很简单的,用到的就是 CSS3 滤镜filter中的drop-shadow,该滤镜可以给图片非透明区域添加投影。你可以理解为下图
它实现的效果看上去就像使原来的对象离开页面,然后在页面上显示出该对象的投影。是有一点类似box-shadow,但是二者还是有显著差别的,我后面会写一篇专门的文章来比较二者的差别。
先来看一下语法:
filter:drop-shadow(水平阴影偏移距离 垂直阴影偏移距离 投射的阴影颜色 );
我们准备一张背景色是透明的图片(图片尺寸40px X 40px),
用一个div将该图片包裹住,给图片添加
filter: drop-shadow(40px 40px yellow)
这段代码,代表投射出一个和该图片一样的形状,
三个参数分别代表:
水平向右移动40px,
垂直向下移动40px,
投射出的形状颜色为黄色。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
.box{
width: 40px;
height: 40px;
border: 1px solid red;
margin: 200px auto;
}
.pic{
filter: drop-shadow(40px 40px yellow);
}
</style>
</head>
<body>
<div class="box">
<img src="img/delete.png" class="pic"/>
</div>
</body>
</html>
效果为
接下来我们稍微更改一下原代码,将原图设置在div外部并隐藏,变色后的投影放置在div中
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
.box{
width: 40px;
height: 40px;
border: 1px solid red;
margin: 200px auto;
text-indent: -40px;
overflow: hidden;
}
.pic{
filter: drop-shadow(40px 0px yellow);
}
</style>
</head>
<body>
<div class="box">
<img src="img/delete.png" class="pic"/>
</div>
</body>
</html>
如果想换成其他颜色,直接更改第三个参数就Ok了~是不是很简单
*请认真填写需求信息,我们会在24小时内与您取得联系。