防抖节流
优化高频率事件 onscroll oninput resize onkeyup keydown.... 降低代码执行频率
- js动画/往页面里添加一些dom元素
- style确定每个dom应该用什么样式规则
- Layout布局,计算最终显示的位置和大小
- Paint绘制dom,在不同的层上绘制
- Composite渲染层合并
用户scroll和resize行为会导致页面不断的重新渲染,如果在绑定的回调函数中大量操作dom也会出现页面卡顿
优化方案:
函数节流
节流就是保证一段时间内,核心代码只执行一次
打个比方:水滴积攒到一定重量才会下落
简易节流函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">点我aaaa</button>
</body>
</html>
<script>
// 节流,1s内的点击算一次
let btn = document.getElementById('btn');
//1.版本
// function throttle(func,wait) {
// let previous=0;
// return function() {
// let now=Date.now();
// if (now-previous>wait) {
// func.apply(this,arguments);
// previous=now;
// }
// }
// }
//2.0版本
function throttle(func, wait, option) {
let args, context, previous = 0, timeout;
let later = function () {
previous = Date.now();
func.apply(context, args);
args=context=null;//避免内存泄漏
}
let throttled = function () {
args = arguments;
context = this;
let now = Date.now();
let remaning = wait - (now - previous);
if (remaning <= 0) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
func.apply(context, args);
previous = now;
} else if (!timeout && option.trailing !== false) {
//最后一次的点击,要执行,就多了这么个功能的2.0
timeout = setTimeout(later, remaning);
}
}
return throttled;
}
function logger() {
console.log('logger');
}
// btn.addEventListener('click',throttle(logger,1000));
btn.addEventListener('click', throttle(logger, 1000, { trailing: true }));
</script>
防抖
防抖就是一段时间结束后,才触发一次事件,如果一段时间未结束再次触发事件,就会重新开始计算时间
打个比方:电梯中,门快要关了,突然游刃准备上来,电梯并没有改变楼层,而是再次打开电梯门。电梯延迟了改变楼层的功能,但是优化了资源。
简易防抖代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">点我aaaa</button>
</body>
</html>
<script>
//停止点击之后执行
let btn = document.getElementById('btn');
//1.0版本
// function debounce(func,wait) {
// let timeout;
// return function() {
// clearTimeout(timeout);
// timeout=setTimeout(()=>{
// func.apply(this,arguments);
// },wait);
// }
// }
function debounce(func,wait,immediate) {
let timeout;
return function() {
clearTimeout(timeout);
//首次点击有效果
if (immediate) {
let callNow=!timeout;
if (callNow) {
func.apply(this,arguments);
}
}
timeout=setTimeout(()=>{
func.apply(this,arguments);
clearTimeout(timeout);
timeout=null;
},wait);
}
}
function logger() {
console.log('logger');
}
btn.addEventListener('click', debounce(logger, 1000,true));//第三个参数表示首次先触发一下
</script>
requestAnimationFrame
编写动画循环的关键是要知道延迟时间多长合适,如果时间过长会导致动画补流畅,时间过短会造成过度的绘制。
requestAnimationFrame采用系统时间间隔,保持最佳绘制效率。此方法是用来在页面重绘之前,
通知浏览器调用一个指定的函数,被调用的频率是约每秒60次,在运行时浏览器会自动优化方法的调用.
重点:这个函数的核心就是浏览器可以根据不同PC性能算出最佳绘制时间,以实现最佳显示效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="canvas" width="500px" height="500px"> </canvas>
</body>
</html>
<script>
window.onload=function(){
let oC=document.getElementById('canvas');
let gd=oC.getContext('2d');
let left=100;
function next() {
gd.clearRect(0,0,oC.with,oC.height);
left+=5;
gd.strokeRect(left,20,100,100);
requestAnimationFrame(next);
}
requestAnimationFrame(next);
}
</script>
柯里化
函数柯里化,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。
其实质就是预先处理机制
,核心就是利用闭包
实现
(function () {
function myBind(context=window,...outerArg) {
//此处的this是fn,因为是fn.myBind
let _this=this;
return function(...innerArg) {
//此处就相当于fn.call
// _this.call(context,...outerArg.concat(innerArg));
//innerArg:就是自动传递的event对象
_this.apply (context,outerArg.concat(innerArg));
}
}
Function.prototype.myBind = myBind;
})();
let obj = {
name: "OBJ"
}
function fn(...args) {
console.log(this, args);
}
/**
自定义实现了bind机制,而这个核心其实就是预先合并参数
利用这个机制可以实现下面案例的题目
*/
document.body.onclick=fn.myBind(obj,100,200);
//ev浏览器会自动追加在最后面传递,bind其实返回就是匿名函数,和下面的等效
// document.body.onclick=fn.bind(obj,100,200);
//其实bind函数内部就是这么实现的,可以避免立即执行
// document.body.onclick = function (ev) {
// fn.call(obj, 100, 200, ev);
// }
假如需求是固定三层相加需求,但是每层参数不固定,可以如下实现
function add(...A) {
return function(...B) {
return function(...C) {
return eval([...A,...B,...C].join('+'))
}
}
}
//也可以(1,2,4)(3)(5)这种层数固定但是每层参数不固定
add(1)(2)(3)
下面实现核心逻辑
/**请实现一个add函数,满足以下功能
* add(1); 1
* add(1)(2); 3
* add(1)(2)(3) 6
* add(1)(2)(3)(4) 10
* add(1)(2,3) 6
* add(1,2)(3) 6
* add(1,2,3) 6
*/
function currying(fn, length) {
length = length || fn.length;
return function (...args) {
if (args.length >= length) {
return fn(...args);
}
//传入null/undefined的时候将执行js全局对象浏览器中是window,其他环境是global
return currying(fn.bind(null, ...args), length - args.length);
}
}
// function $add(n1, n2, n3, n4) {
// return n1 + n2 + n3 + n4;
// }
// let add = currying($add, 4);
// console.log(add(1)(2)(3)(4));
// console.log(add(1, 2, 3, 4));
//$add.bind(null,1).bind(null,2).bind(null,3)(4);
//联系之前柯里化的案例
//function any1(...innerArg){$add.call(null,...[1,...innerArg])} 这样一层层关联预处理了参数
//function any2(...innerArg){any1.call(null,...[2,...innerArg])}
//function any3(...innerArg){any2.call(null,...[3,...innerArg])}
//any3(4)
//any2.call(null,3,4)
//any1.call(null,2,3,4)
//$add.call(null,1,2,3,4)
let add=currying((...arg)=>eval(arg.join('+')),5);
console.log(add(1,2,3,4,5));
总结:currying传递的第二参数就是实际真实计算是几个数据,一定要对应。
而从currying内部逻辑可发现,其实是递归实现,内部也有闭包,而这种需求核心其实就是不论多少层每层多少参数都要可以计算,参考前面的柯里化思想,每层递归其实类似于call这种绑定关联。
而在最终递归(预先处理)结束开始计算的时候,实际就是参数的合并逻辑。
参考视频:https://www.bilibili.com/video/BV1aE411C7pt?p=27
反柯里化
从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。
Function.prototype.uncurrying=function() {
return str=>{
// Object.prototype.toString.call('str')
//this就是 Object.prototype.toString
//Object.prototype.toString.call(参数)就是输出类型
return this.call(str);
//这只是简单案例,其实可以添加很多自己的逻辑
}
}
let toString=Object.prototype.toString.uncurrying();
//扩展了函数的功能
console.log(toString('hello')); //[object String]
//Object.prototype.toString本身就是一个函数,函数自然就有Function.prototype上面的属性
总结:柯里化和反柯里化是一个思路,理解即可,不需要关心N多的具体实现,没有实际意义
SourceMap
说起sourceMap大家肯定都不陌生,随着前端工程化的演进,我们打包出来的代码都是混淆压缩过的,当源代码经过转换后,调试就成了一个问题。在浏览器中调试时,如何判断原始代码的位置?
为了解决这个问题,google 提出了sourceMap 的想法,并在chorme上最先支持sourceMap的使用。sourceMap 由于包含许多信息,前期也经过多版的编码算法优化,最后在2011年探索出了Source Map Revision 3.0 ,这个版本也就是我们现在一直在使用的sourceMap版本。这一版本的mapping信息使用Base64 VLQ 编码,大大缩小了.map文件的体积。
sourceMap可以帮我们直接定位到编译前代码的特定位置,接下来我们直接拿个sourceMap文件来看看它包含了一些什么信息:
上面可以看到,sourceMap其实就是就是一段维护了前后代码映射关系的json描述文件,包含了以下一些信息:
- version:sourcemap版本(现在都是v3)
- file:转换后的文件名。
- sourceRoot:转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。
- sources:转换前的文件。该项是一个数组,表示可能存在多个文件合并。
- names:转换前的所有变量名和属性名。
- mappings:记录位置信息的字符串。
mappings 信息是关键,它使用Base64 VLQ 编码,包含了源代码与生成代码的位置映射信息。mappings的编码原理详解可见:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html,这里就不再详述。
webpack中的sourceMap配置
webpack 给出了多种sourceMap配置方式,相信很多人第一眼看到的时候和我一样,疑惑这些都有啥区别
其实不难发现这么多配置,这些就是source-map和eval、inline、cheap、module 的自由组合。所以我们来拆开看下每项配置。
为了方便演示,这里的源代码只包含了一行代码
console.log( hello world );
最原始的只设置’source-map’配置,可以看到输出了两个文件,其中包含一个map文件
- eval
每个模块用eval()包裹执行。-
devtool: eval
我们先看看单独的eval配置,这个配置相对于其他会特殊一点 。因为配置里没有sourceMap,实际上它也会生出map,只是它映射的是转换后的代码,而不是映射到原始代码。
-
2)devtool: eval-source-map
所以eval-source-map就会带上源码的sourceMap,打包结果如下:
-
值得注意的是加了eval的配置生成的sourceMap会作为DataURI嵌入,不单独生成.map文件。
对于eval的构建模式,我们可以看看官方的描述:可以看出官方是比较推荐开发场景下使用的,因为它能cache sourceMap,从而rebuild的速度会比较快。
-
inline
inline配置想必大家肯定已经能猜到了,就是将map作为DataURI嵌入,不单独生成.map文件。
devtool: inline-source-map构建出来的文件如下, 这个比较好理解,就不多说了
cheap
这是 “cheap(低开销)” 的 source map,因为它没有生成列映射(column mapping),只是映射行数 。
为了方便演示,我们在代码加一行错误抛出:
console.log('hello');
throw new Error('this is test');
可以看到错误信息只有行映射,但实际上开发时我们有行映射也基本足够了,所以开发场景下完全可以使用cheap 模式 ,来节省sourceMap的开销
- module
Webpack会利用loader将所有非js模块转化为webpack可处理的js模块,而增加上面的cheap配置后也不会有loader模块之间对应的sourceMap。
什么是模块之间的sourceMap呢?比如jsx文件会经历loader处理成js文件再混淆压缩, 如果没有loader之间的sourceMap,那么在debug的时候定义到上图中的压缩前的js处,而不能追踪到jsx中。
所以为了映射到loader处理前的代码,我们一般也会加上module配置
总结
1、开发环境
综上所述,考虑到我们在开发环境对sourceMap的要求是:快(eval),信息全(module),且由于此时代码未压缩,我们并不那么在意代码列信息(cheap),所以开发环境比较推荐配置:devtool: cheap-module-eval-source-map
2、生产环境
一般情况下,我们并不希望任何人都可以在浏览器直接看到我们未编译的源码,所以我们不应该直接提供sourceMap给浏览器。但我们又需要sourceMap来定位我们的错误信息, 这时我们可以设置hidden-source-map:
一方面webpack会生成sourcemap文件以提供给错误收集工具比如sentry,另一方面又不会为 bundle 添加引用注释,以避免浏览器使用。
当然如果没有这一类的错误处理工具,可以看看webpack推荐的其他配置:
https://www.webpackjs.com/configuration/devtool/
CSS sourceMap
说起sourceMap我们第一反应通常是JavaScript的sourceMap,实际上现在css也可以使用sourceMap。因为sourceMap本质只是一个json,里面包含了源码的映射信息。所以其实只要了解sourcemap的编码规范,我们可以对任何我们想要的资源生成sourceMap,当然sourceMap 的支持也还是要取决于浏览器的支持。
现在,对于css我们也有同样诉求,比如我现在打开调试器看到的样式配置没有任何源信息。如果想像js一样,知道这个css样式是在哪个文件需要怎么弄呢?
上面讲解的配置其实都是针对js的sourceMap,配置后webpack会自动帮我们生成各类js sourceMap。因为本质上webpack只处理js,对于webpack来说,css是否有sourceMap依赖于对css处理的loader是否有sourceMap输出,所以loader需要开启并传递sourceMap,这样最后生成的css才会带上sourceMap 。
目前使用的css-loader,sass-loader都已经提供了生成sourceMap的能力,只需要我们加上配置即可。
需要注意的是,这里如果要拿到sass编译前的源码信息,那么sourceMap一定要从sass-loader一直传递到css-loader,中间如有其他loader处理,也要透传sourceMap
可以看到,加了sourceMap 配置后,sourceMap会被内联在css代码里(这一层是css-loader处理的,与你是否使用min-extract-css-plugin抽出css无关)
加了css sourceMap后,我们可以很轻松的定位到sass编译前的源码路径了。
通过debug,打印出生成的css sourceMap,和js sourceMap对比并无他样:
利用css sourceMap 解决css url resolve的问题
如果大家用了sass的话,很可能会遇到一个css url resolve的问题,在之前的一篇讲webpack 配置的文章里我也提到过:
实际上,利用css sourceMap这个问题便可以在不改变源码的情况下就可以完美解决。
这里会增加一个loader去处理,loader处理流程主要分为二步:
1、根据sourceMap的sourcesContent和url内容进行匹配,然后从sources定位到原有的css资源路径
2、将传递给下个loader的url内容替换成绝对路径
代码如下:
module.exports = function (content, map) {
const res = content.replace(/url((?: |")?((./|../)+([^ ")]*))( |")?)/g, (str, img, p2, imgPath) => {
let index = -1;
const {sourcesContent = [], sources = [], sourceRoot = []} = map || {};
sourcesContent.some((item, i)=> {
if (item.indexOf(img) !== -1) {
index = i;
return true;
}
});
if (index !== -1) {
const dir = path.dirname(sources[index]); // 获取文件所在目录
str = str.replace(img, `~${path.join(dir, img)}`);
}
return str;
});
this.callback(null, res, map);
return;
}
因为依赖sass-loader 处理之后的sourceMap, 所以@tencent/im-resolve-url-loader应配置在sass-loader 前面,配置如下:
说明:sourcemap部分内容,完全复制前端Q公众号文章:hSourceMap知多少:介绍与实践
SVG矢量图操作
概述
svg是有一种基于xml语法的图像格式,全称是可缩放矢量图。其他图像格式都是基于像素处理的,svg则是属于对图像的形状描述,所以它本质上是文本文件,体积比较小,且不管放多少倍都不会失真。
SVG文件可以直接插入网页,成为DOM的一部分,然后js和css进行操作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<svg id="mysvg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 800 600" preserverAspectRatio="xMidYMid meet">
<circle id="mycircle" cx="400" cy="300" r="50" ></circle>
</svg>
</body>
</html>
上面是svg代码直接插入网页的例子。
SVG代码也可以写在一个独立文件中,然后使用img/object/iframe/embed
等标签插入网页。
<img src="circle.svg>
<object id="object" data="circle.svg" type="image/svg+xml"></object>
<embed id="embed" src="icon.svg" type="image/svg+xml">
<iframe id="iframe" src="icon.svg">
css也可以使用SVG文件
.logo{
background:url(icon.svg);
}
SVG文件还可以转为BASE64编码,然后作为DataURL写入网页
<img src="data:image/svg+xml;base64,[data]"/>
语法
-
<svg>
标签
svg代码都放在顶层标签<svg>
之中,下面是一个例子
<svg width="100%" height="100%">
<circle id="mycircle" cx="50" cy="50" r="50" fill="red"></circle>
</svg>
fill是填充色,width属性和height属性,制定了SVG图像再HTML元素中所占据的宽度和高度。除了相对单位,也可以采用绝对单位(px)。如果不指定这两个元素,SVG图像默认大小是300px(宽)*150px(高)
如果只想展示SVG图像的一部分,就要指定viewBox属性
<svg width="100" height="100" viewBox="50 50 50 50">
<circle id="mycircle" cx="50" cy="50" r="50"/>
</svg>
viewBox属性的的值有四个数字,分别是左上角的横坐标和纵坐标,视口的高度和宽度。上面的代码中SVG图像是100px*100px,viewBox属性指定视口从(50,50)这个点开始。所以,实际看到的是右下角的四分之一圆。
注意:视口必须适配所在的空间。上面代码中,视口的大小是50 50,由于SVG图像的大小是100*100;所以视口会放大去适配SVG图像的大小,即放大了四倍。
<circle>标签
<svg width="300" height="180">
<circle cx="30" cy="50" r="25" ></circle>
<circle cx="90" cy="50" r="25" class="red"></circle>
<circle cx="150" cy="50" r="25" class="fancy"></circle>
</svg>
上面的代码定义了三个圆,<circle>
标签的cx,cy,r属性分别是横坐标,纵坐标和半径,单位为像素。坐标都是相对于<svg>
画布的左上角原点。
class属性用来指定对应的css类
<style>
.red{
/* 填充色 */
fill: red;
}
.fancy{
fill: none;
/* 边框 */
stroke: black;
/* 边框宽度 3像素,注意是pt*/
stroke-width: 3pt;
}
</style>
-
<line>
标签
用来绘制直线
<svg width="300" height="180">
<line x1="0" y1="0" x2="200" y2="0" style="stroke: rgb(0, 0, 0);stroke-width:5"></line>
</svg>
上面代码中,x1 y1代表线段的起点的横坐标和纵坐标;x2 y2属性,表示线段终点的横坐标和纵坐标;style属性表示线段的样式
<style>
.line {
stroke: rgb(0, 0, 0);
stroke-width: 5;
transition: all 1s;
/* transform: rotate(45deg); */
/* 旋转中心点 */
transform-origin: center;
animation: xuanzhuan 2s linear infinite;
}
@keyframes xuanzhuan {
from{
transform: rotate(0deg);
}
to{
transform: rotate(360deg);
}
}
.line:hover{
stroke: yellow;
}
</style>
<svg width="400" height="400">
<line x1="50" y1="50" x2="350" y2="350" class="line"></line>
</svg>
鼠标移动到线上面 1s之内颜色变成黄色,然后自定义了一个动画,xuanzhuan,2s一次,无线循环从0度转到360度,效果就是以直线中间为圆心开始旋转
-
<polyline>
标签
用于绘制一根折线
<svg width="300" height="180">
<polyline points="3,3 30,28 3,53" fill="none" stroke="black"></polyline>
</svg>
<polyline>
的points属性指定了每个端点的坐标,横坐标和纵坐标之间与逗号隔开,点与点之间用空格分割。
-
<rect>
标签
用于绘制矩形
<svg width="400" height="400">
<rect x="50" y="50" width="250" height="250" fill="red"></rect>
</svg>
<rect>
的x和y属性,指定了矩形左上角端点的横坐标和纵坐标,width和height属性指定了矩形的宽度和高度(px)
-
<ellipse>
标签
用于绘制椭圆
<svg width="400" height="400">
<ellipse cx="60" cy="60" ry="40" rx="20" stroke="black" stroke-width="5"></ellipse>
</svg>
<ellipse>
的cx cy属性指定了椭圆圆心的横坐标和纵坐标;rx ry属性,指定了椭圆的横向轴和纵向轴的半径(px)
-
<polygon>
标签
用于绘制多边形
<svg width="400" height="400">
<polygon fill="green" stroke="orange" stroke-width="1" points="0,0 100,0 100,100 0,100 0,0"></polygon>
</svg>
如上其实就是绘制了一个绿色填充,橙色边框的正方形
<polygon>
的points属性指定了每个端点的坐标,横坐标和纵坐标之间与逗号分隔,点与点之间用空格分割
-
<path>
标签
用于绘制路径
<svg width="400" height="400">
<path
d="
M 18,3
L 46,3
L 46,40
L 61,40
L 32,68
L 3,40
L 18,40
Z
"
></path>
</svg>
<path>
的d属性表示绘制顺序,它的值是一个长字符串,每个字母表示一个绘制动作,后面跟着坐标。此时是箭头的形状
- M:移动到(moveto)
- L:画直线到(lineto)
- Z:闭合路径
-
<text>
标签
用于绘制文本
<svg width="400" height="400">
<text x="50" y="25">Hello World</text>
</svg>
x y属性,表示文本区块基线起点的横坐标和纵坐标,文字的样式可以用class或style属性.字体的颜色用fill填充而不是color
-
<use>
标签
用于复制一个形状
<svg width="400" height="400">
<text id="text" x="50" y="25">Hello World</text>
<use href="#text" x="10" y="0" fill="blue"></use>
</svg>
<use>
的href属性指定所要复制的节点,x和y属性是左上角的坐标。另外,还可以指定width和height坐标
-
<g>
标签
用于把多个形状组成一个组,方便复用
<svg width="400" height="400">
<g id="myg">
<text id="text" x="50" y="25">Hello World</text>
<circle cx="50" cy="50"></circle>
</g>
<use href="#myg" x="100" y="0" fill="blue"></use>
<use href="#myg" x="200" y="0" fill="white" stroke="blue"></use>
</svg>
-
<defs>
标签
用于自定义形状,它内部的代码不会显示,仅供引用
<svg width="400" height="400">
<defs>
<g id="myg">
<text id="text" x="50" y="25">Hello World</text>
<circle cx="50" cy="50"></circle>
</g>
</defs>
<use href="#myg" x="200" y="0" fill="white" stroke="blue"></use>
</svg>
这种方式无法再html页面直接通过css操作样式,但是可以通过js
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css" >
<![CDATA[
.water {
stroke-dasharray: 10, 5;
}
.runing {
stroke-dashoffset: -2000;
animation: run 4s linear infinite;
}
@keyframes run {
from {
stroke-dasharray: 10, 5;
}
to {
stroke-dasharray: 11, 5;
}
}
]]>
</style>
<defs>
<marker id="arrow-r" markerUnits="strokeWidth" markerWidth="12" markerHeight="12" viewBox="0 0 12 12"
refX="6" refY="6" orient="auto">
<path d="M2,2 L10,6 L2,10 L6,6 L2,2" style="fill: pink;" />
</marker>
</defs>
<defs>
<marker id="arrow-l" markerUnits="strokeWidth" markerWidth="12" markerHeight="12" viewBox="0 0 12 12"
refX="6" refY="6" orient="auto">
<path d="M10,2 L2,6 L10,10 L6,6 L10,2" style="fill: blue;" />
</marker>
</defs>
<polyline class="runing" points="10,10 410,10 " marker-start="url(#arrow-l)"
marker-end="url(#arrow-r)" style="stroke:green;stroke-width:2" stroke-linecap="round" />
</svg>
defs还可以添加css样式
-
<pattern>
标签
用于自定义一个形状,该形状可以被引用来平铺一个区域
<svg width="400" height="400">
<defs>
<pattern id="dots" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
<circle fill="#bee9e8" cx="50" cy="50" r="35"></circle>
</pattern>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="url(#dots)"></rect>
</svg>
上面代码,将一个原形定义为dots模式。patternUnits="userSpaceOnUse"表示<pattern>
的宽度和长度是实际的像素值。然后,指定这个模式去填充下面的矩形
-
<image>
标签
用于插入图片文件
<svg viewBox="0 0 100 100" width="100" height="100">
<image xlink:href="path/to/image.jpg" width="50%" height="50%">
</svg>
xlink:href属性表示图像的来源
-
<animate>
标签
用于产生动画效果
<svg width="400" height="400">
<rect x="0" y="0" width="100" height="100" fill="#feac5e">
<animate attributeName='x' from='0' to="500" dur="2s" repeatCount="indefinite"></animate>
</rect>
</svg>
上面代码中,矩形会不断移动,产生动画效果,循环往复。
- attributeName:发生动画效果的属性名
- from: 单次动画的初始值
- to: 单次动画的结束值
- dur: 单次动画的持续时间
- repeatCount:动画的循环模式
可以在多个属性上定义动画
<animate attributeName='x' from='0' to="500" dur="2s" repeatCount="indefinite"></animate>
<animate attributeName='width' to="500" dur="2s" repeatCount="indefinite"></animate>
-
<animateTransform>
标签
<animate>
标签对css的transform属性不起作用,如果需要变形,就要使用<animateTransform>
标签
<svg width="400" height="400">
<rect x="0" y="0" width="100" height="100" fill="#feac5e">
<animateTransform attributeName='transform' type='rotate'
begin='0s' dur='10s'
from='0 200 200' to="360 400 400" repeatCount="indefinite"></animateTransform>
</rect>
</svg>
上面代码中<animateTransform>
的效果为旋转(rotate),这时from和to属性值有三个数字,第一个数字是角度值,第二个值和第三个值是旋转中心的坐标。
from='0 200 200'表示开始的时候,角度为0,围绕(200,200)开始旋转,to='360 400 400'表示结束时,角度为360,围绕(400,400)旋转。
JS操作
- DOM操作
如果SVG代码直接写在html网页之中,它就成为网页DOM的一部分,可以直接用DOM操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
</style>
<body>
<!-- <svg width="500" height="500">
<rect x="0" y="0" width="100" height="100" fill="#feac5e">
<animateTransform attributeName='transform' type='rotate'
begin='0s' dur='10s'
from='0 200 200' to="360 400 400" repeatCount="indefinite"></animateTransform>
</rect>
</svg> -->
<svg width="400" height="400">
<rect x="50" y="50" width="250" height="250" fill="red"></rect>
</svg>
<button>点击放大</button>
</body>
</html>
<script>
const btn= document.querySelector('button');
btn.onclick=function(){
let svgRect=document.querySelector('rect');
//直接打印svgRect得到的dom字符串,如下形式才能展开内部属性
// console.log([svgRect]); 可发现很多属性都在attribute内部
svgRect.setAttribute("width",350);
svgRect.setAttribute("height","350");
// svgRect.style.fill="blue"; 等效
svgRect.setAttribute("fill","blue");
// svgRect.setAttribute("x",200);
//通过间隔函数:实现动画
let sudu=2;
let weizhi=50;
setInterval(()=>{
weizhi+=sudu;
svgRect.setAttribute("x",weizhi);
},10);
}
</script>
- 获取SVG DOM
使用<object> <embed>
标签插入SVG文件,可以获取SVG DOM
const svgObject = document.getElementById('object').contentDocument;
const svgIframe = document.getElementById('iframe').contentDocument;
const svgEmbed = document.getElementById('embed').getSVGDocument();
注意:如果使用<img>
标签插入SVG文件,就无法获取SVG DOM。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
</style>
<body>
<iframe src="a.svg" width="450" height="200" scrolling="none" style="border: none;" id="iframe"></iframe>
<button>点击放大</button>
</body>
</html>
<script>
// const svgObject = document.getElementById('object').contentDocument;
// const svgEmbed = document.getElementById('embed').getSVGDocument();
const svgIframe = document.getElementById('iframe');
// console.log([svgIframe]);
svgIframe.onload=function(){
//必须这样,否则js执行很快,但是dom可能还没加载完毕,导致打印的contentDocument可能出错
let doc= svgIframe.contentDocument;
//注意:针对继续的子查询的时候,可能很多时候没有id等,例如path一般都有pid,可以通过css的属性选择器获取"[p-id='9655']"
console.log(doc);
}
</script>
案例一
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css" >
<![CDATA[
<!--自定义css变量-->
:root{
--from-stroke-dasharray:10 5;
--to-stroke-dasharray:11, 5;
}
.water {
stroke-dasharray: 10, 5;
}
.runing {
stroke-dashoffset: -2000;
animation: run 4s linear infinite;
}
@keyframes run {
from {
stroke-dasharray: var(--from-stroke-dasharray);
}
to {
stroke-dasharray: var(--to-stroke-dasharray);
}
}
]]>
</style>
<defs>
<marker id="arrow-r" markerUnits="strokeWidth" markerWidth="12" markerHeight="12" viewBox="0 0 12 12"
refX="6" refY="6" orient="auto">
<path d="M2,2 L10,6 L2,10 L6,6 L2,2" style="fill: pink;" />
</marker>
</defs>
<defs>
<marker id="arrow-l" markerUnits="strokeWidth" markerWidth="12" markerHeight="12" viewBox="0 0 12 12"
refX="6" refY="6" orient="auto">
<path d="M10,2 L2,6 L10,10 L6,6 L10,2" style="fill: blue;" />
</marker>
</defs>
<polyline class="runing" points="10,10 410,10 " marker-start="url(#arrow-l)"
marker-end="url(#arrow-r)" style="stroke:green;stroke-width:2" stroke-linecap="round" />
</svg>
<!DOCTYPE HTML>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
</head>
<body>
<div id="contanid1">
<object type="image/svg+xml" data="a.svg" width="800" height="50"></object>
</div>
<div id="contanid2">
<object type="image/svg+xml" data="a.svg" width="800" height="50"></object>
</div>
<button id="btn1">虚线一切换流动</button>
<button id="btn2">虚线二切换流动</button>
<button id="btn3">虚线二左边箭头显示切换</button>
</body>
</html>
<script>
window.onload = function () {
const c1 = document.getElementById('contanid1');
const c2 = document.getElementById('contanid2');
const c1doc = c1.children[0].contentDocument;
const c1fline = c1doc.children[0].children[3];
c1doc.children[0].children[1].children[0].children[0].style.fill = 'yellow'
const c2doc = c2.children[0].contentDocument;
// console.log(111,[c2doc]); 注意这种打印方式
const c2fline = c2doc.children[0].children[3];
c2doc.children[0].children[1].children[0].children[0].style.fill = 'blue'
let btn1 = document.getElementById('btn1');
let btn2 = document.getElementById('btn2');
let btn3 = document.getElementById('btn3');
// console.log(1111, [c1fline]);
btn1.onclick = function () {
c1fline.attributes.class.value === 'water' ? c1fline.setAttribute('class', 'runing') : c1fline.setAttribute('class', 'water');
}
btn2.onclick = function () {
c2fline.attributes.class.value === 'water' ? c2fline.setAttribute('class', 'water runing') : c2fline.setAttribute('class', 'water');
}
btn3.onclick = function () {
c2doc.children[0].children[2].children[0].children[0].style.display === 'none' ? c2doc.children[0].children[2].children[0].children[0].style.display = '' : c2doc.children[0].children[2].children[0].children[0].style.display = 'none';
}
const rootStyles = getComputedStyle(c1doc.documentElement);
//获取自定义属性值
const value = rootStyles.getPropertyValue('--to-stroke-dasharray');
//修改自定义属性值
c1doc.documentElement.style.setProperty('--to-stroke-dasharray', '20 10');
c1doc.documentElement.style.setProperty('--from-stroke-dasharray', '21 10');
}
</script>
案例二
SVG绘制条形图
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
.axis{
stroke: #999;
stroke-width: 2px;
}
</style>
</head>
<body>
<!--
1/获取数据
2/创建SVG
3/创建坐标
4/绘制坐标文字
5/依据数据绘制矩形(条形)
-->
<svg width="1000" height="700">
<g id="zuobiao">
<!-- x轴-->
<line class="axis" x1="50" y1="600" x2="950" y2="600" ></line>
<path d="M 925,590 L 950,600 L 925,610"></path>
<text x="920" y="630">时间</text>
<!-- y轴-->
<line class="axis" x1="100" y1="650" x2="100" y2="50"></line>
<path d="M 90,75 L 100,50 L 110,75"></path>
<text x="920" y="630">时间</text>
</g>
<g id="xkedu">
<!-- <line class="axis" x1="170" y1="600" x2="170" y2="580"></line> -->
<text x="50" y="70">订单</text>
</g>
<g id="ykedu"></g>
<g id="barList">
</g>
</svg>
<script type="text/javascript">
var data = [{
data:"星期一",
order:"1000"
},
{
data:"星期二",
order:"500"
},
{
data:"星期三",
order:"600"
},
{
data:"星期四",
order:"1100"
},
{
data:"星期五",
order:"700"
},
{
data:"星期六",
order:"1200"
},
{
data:"星期日",
order:"1500"
}
]
console.log(data)
var xkedu = document.querySelector("#xkedu");
var ykedu = document.querySelector("#ykedu");
var barListDom = document.querySelector("#barList")
var jgLength = 700/data.length;
var yLength = 450/15;
for(var i= 1;i<=data.length;i++){
renderKedu(i)
console.log(i)
}
for(var j=1;j<=15;j++){
ykedu.innerHTML = ykedu.innerHTML + `<line class="axis" x1="100" y1="${600-yLength*j}" x2="120" y2="${600-yLength*j}"></line>` +
`<text x="50" y="${600-yLength*j}">${100*(j)}</text>`
}
function renderKedu(index){
var lineDom = document.createElement("line")
console.log(jgLength)
lineDom.className = "axis";
lineDom.setAttribute("x1",100+jgLength*index);
lineDom.setAttribute("y1","600");
lineDom.setAttribute("x2",100+jgLength*index);
lineDom.setAttribute("y2","580");
xkedu.innerHTML = xkedu.innerHTML + lineDom.outerHTML + `<text x="${75+jgLength*index}" y="620">${data[index-1].data}</text>`
var color = `rgb(${parseInt(Math.random()*255)},${parseInt(Math.random()*255)},${parseInt(Math.random()*255)})`;
barListDom.innerHTML = barListDom.innerHTML +
`<rect x="${75+jgLength*index}" y="${600-(yLength*(data[index-1].order/100))}" width="50" height="${yLength*(data[index-1].order/100)}" fill="${color}"></rect>`
}
</script>
</body>
</html>
CSS相关
自定义css属性
即css变量,这样可以做到类似于less的效果,一改全改
<!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>
:root {
--div-color: red;
}
#d1 {
width: 100px;
height: 100px;
background-color: var(--div-color);
}
#d2 {
width: 200px;
height: 200px;
background-color: var(--div-color);
}
</style>
</head>
<body>
<div id="d1">
d1
</div>
<div id="d2">
d2
</div>
<button>切换主题</button>
</body>
</html>
<script>
let btn = document.querySelector('button');
btn.onclick = function () {
//其实document.documentElement可以通过下面方式获取
//let root=document.querySelector(":root");
//其实都代表根的意思
const rootStyles = getComputedStyle(document.documentElement);
//获取自定义属性值
const value = rootStyles.getPropertyValue('--div-color');
//修改自定义属性值
document.documentElement.style.setProperty('--div-color', 'green');
}
</script>
注意:自定义css属性名称的时候,必须--开头,必须两个,这是规范
H5属性的操作
预定义属性
h5提供了classList属性获取class的属性值,没有这个api之前,需要一点点获取attribute属性然后通过复杂匹配获取。
<body>
<div id='test' class="zq zq1 zq2"></div>
</body>
</html>
<script>
var test=document.querySelector("#test");
console.log(test.classList)
test.classList.add('zq3');
console.log(test.classList);
test.classList.remove('zq');
console.log(test.classList);
test.classList.toggle('zq');//如果存在就删除,不存在就添加
</script>
输出结果
自定义属性
h5之前,都是通过setattribute操作自定义属性
<body>
<div id='test'name='info'></div>
</body>
</html>
<script>
var test=document.querySelector("#test");
test.setAttribute('name','info1');
var h=test.getAttribute('name');
console.log(h);//info1
</script>
H5案例:
如果是data-开头的自定义属性,可以通过
<body>
<div id='test' data-name='info' data-type-info='hehe'></div>
</body>
</html>
<script>
var test=document.querySelector("#test");
console.log(test.dataset.name);//info
// data-type-info:获取data-开头的属性名的属性值,
//后面的-的首字母大写
console.log(test.dataset.typeInfo);//hehe
</script>
H5可编辑属性
<body>
<div id='test' contenteditable="true">可编辑属性</div>
</body>
H5语义化标签
hgroup
代表网页或者section的标题,当元素有多个层级时,该元素可以将h1到h6元素放在其内,譬如文章主标题和副标题的组合。
使用细节:
如果只有一个h1-h6标签不要使用hgroup
如果有连续多个h1-h6标签就用hgroup
如果有连续多个标题和其他文章数据,h1-h6标签就用hgroup标签包裹住,然后和其他文章元素一起放进header标签
header
代表网页或secction的页眉,通常包含h1-h6或hgroup
使用细节:
可以是"网页"或者任意"section"的头部部分
没有个数限制
如果hgroup或h1-h6自己就能工作很好,不要使用header
nav
代表页面的导航链接区域,用于定义页面的主要导航部分
用在整个页面的主导航部分,不适合不要用nav元素
section
代表文档中的节或段,段可以是指一篇文章里按照字体的分段,节可以指一个页面的分组
使用细节:
section不是一般意义上的容器元素,如果想作为样式展示和脚本的便利,可以用div。
article、nav、aside可以理解为特殊的section,
所以如果可以用article、nav、aside就不要用section,没实际意义的就用div
<section>
<h1>section是啥?</h1>
<article>
<h2>关于section</h1>
<p>section的介绍</p>
<section>
<h3>关于其他</h3>
<p>关于其他section的介绍</p>
</section>
</article>
</section>
article
article元素最容易跟section和div容易混淆,其实article代表一个在文档,页面或者网站中自成一体的内容
使用细节:
独立文章:用article
单独的模块:用section
没有语义的:用div
<article>
<h1>一篇文章</h1>
<p>文章内容..</p>
<footer>
<p><small>版权:html5jscss网所属,作者:damu</small></p>
</footer>
</article>
aside
aside元素被包含在article元素中作为主要内容的附属信息部分,其中的内容可以是与当前文章有关的相关资料、标签、名次解释等.
在article元素之外使用作为页面或站点全局的附属信息部分。最典型的是侧边栏,其中的内容可以是日志串连,其他组的导航,甚至广告,这些内容相关的页面。
使用细节:
aside在article内表示主要内容的附属信息,
在article之外则可做侧边栏
如果是广告,其他日志链接或者其他分类导航也可以用
<article>
<p>内容</p>
<aside>
<h1>作者简介</h1>
<p>小北,前端一枚</p>
</aside>
</article>
footer
footer元素代表 网页 或 section 的页脚,通常含有该节的一些基本信息,譬如:作者,相关文档链接,版权资料。
使用细节:
footer使用注意:
可以是 网页 或任意 section 的底部部分;
没有个数限制,除了包裹的内容不一样,其他跟header类似。
<footer>
COPYRIGHT@damu
</footer>
Prop&&Attr
checkbox
<input type="checkbox" checked='checked'>
说明:多选框中的checked属性除非不写,不然不论赋什么值,还是不写值,都代表选中。
- attribute
html的预定义属性和自定义属性
input标签:针对的html
所以如上:checked是input标签的attribute
- property
js原生对象的直接属性
每一个预定义的attribute都会有一个property与之对应
input节点:针对的是js
所以如上:checked是input节点的property
- 布尔值属性和非布尔值属性
<body>
<input type="checkbox" checked='checked' name='zq'>
</body>
</html>
<script>
//此时checked属性就是布尔值属性
//name属性就是非布尔值属性
// 布尔值属性:property的属性值是布尔值类型
// 非布尔值属性:property的属性值是非布尔值类型
//非布尔值属性:不论什么情况property和attribute都会同步
//布尔值属性:1.改变property不会同步修改attribute
//2.在没有动过property时,attribute会同步property,一旦动过property
//attribute不会同步property
var zq=document.querySelector('input[type=checkbox]');
//修改attribute
zq.setAttribute("name","zq1");
//修改property
zq.name='zq2';
//浏览器只认property,用户操作的是property
</script>
-
属性使用attr还是prop的区分图
能使用attribute尽量使用attribute,性能高。
基本案例:
此时只有在第一次点击全选才有效,如果一旦第一次点击操作过property则全选无效,例如此时的水果三个全部手动选中,都是操作property,则在点击全选无效。如果在html全部给水果加上checked属性就相当于操作了property,此时即使第一次点击全选也无效。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>H5</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<input type="checkbox" >苹果
<input type="checkbox" >栗子
<input type="checkbox" >香蕉
<input type="button" id="CheckAll" value="全选">
</body>
</html>
<script>
$(function(){
$("#CheckAll").click(function(){
$(":checkbox[type=checkbox]").attr("checked",true);
})
})
</script>
对比案例:
checked属性是布尔值属性,如果使用操作property则一直有效
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>H5</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<input type="checkbox" >苹果
<input type="checkbox" >栗子
<input type="checkbox" >香蕉
<input type="button" id="CheckAll" value="全选">
</body>
</html>
<script>
$(function(){
$("#CheckAll").click(function(){
$(":checkbox[type=checkbox]").prop("checked",true);
})
})
</script>
Web Workers
- Worker:构造函数,加载分线程执行的js文件
- Worker.prototype.onmessage: 用于接收另一个线程的回调函数
- Worker.prototype.postMessage:向另一个线程发送消息
- 缺点:worker内代码不能更新UI,不能跨域加载js,浏览器支持问题
说明:必须在服务器环境下,不能本地file,加载的js不能跨域,使用webworker的代码不要使用
箭头函数,let等新特性。
案例
html中的js
var worker = new Worker('a.js');
worker.postMessage(8);
worker.onmessage = function (event) {
console.log("接收的数据",event.data);
}
a.js
// 斐波那契数列
function fibonacci(n){
return n<=2?1:fibonacci(n-1)+fibonacci(n-2);
}
var onmessage = function(event) {
let res=fibonacci(event.data);
postMessage(res);
}
说明:之所以webworker代码中不能更新UI,是因为this指向是[object DedicatedWorkerGlobalScope],
即a.js中上下文不是window,没有UI更新相关的接口。
浏览器渲染机制
浏览器,再内核控制下相互配合以保持同步。它至少三个常驻的线程,JS引擎线程,GUI渲染线程,浏览器事件触发线程。
- js引擎是基于事件驱动单线程执行的
- 渲染线程负责渲染浏览器界面,但是GUI渲染线程与JS引擎互斥的,当JS引擎执行时GUI会被挂起;
GUI的更新也会被保存在一个队列中,等到JS引擎空闲时候才有机会被执行。这就是JS阻塞页面加载 -
事件触发线程,当一个事件被触发时候该线程会把事件添加到任务队列的对尾,等待JS引擎的处理
浏览器内核
- Trident内核:IE
- Webkit内核:Chrome,Safari
- Gecko内核:FireFox
流程
-
引擎一开始会从网络获取请求文档的内容,然后进行如下所示的基本流程:
呈现引擎将开始解析HTML文档,并将各标记逐个转化成内容树上的DOM节点。同时也会将解析外部CSS文件以及样式元素种的样式数据。
HTML种这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树。
呈现树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。
呈现树构建完毕之后,进入"布局"处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制-呈现引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。
- 当用户访问页面时,浏览器需要获取用户请求内容,这个过程主要涉及浏览器网络模块:
- 用户在地址栏输入域名,如baidu.com,DNS服务器根据输入的域名查找对应的IP,然后向该IP地址发起请求
- 浏览器获取并解析服务器的返回内容
- 浏览器加载HTML文件及文件内包含的外部引用文件以及图片,多媒体等资源
这是一个渐进的过程。为了达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个HTML文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。
浏览器渲染过程
下半部分是上面的拆解;最后dispaly是显示;
简化版的渲染过程
重排重绘
重点:其中重排也就是回流;注意:修改颜色(样式变化)等,会引起重绘;而修改大小(几何变化)等会引起重排
从chrome性能监控上面可以发现,layout消耗了大量的CPU时间片。而Paint次之;所以能尽量避免重排就已经可以提供性能了;而下方会说明通过css3实现GPU加速避免重排重绘,提高性能,出让CPU,而使用GPU渲染。
Composite 合成线程
Paint阶段就是绘制,但并不是把页面绘制到显示器上。绘制的本质是填充像素的过程,包括绘制文字、颜色、图像、边框、阴影等效果,也就是一个DOM元素的所有可视效果。
而且,绘制过程一般是在多个图层上完成的,这些图层我们称之为渲染层。渲染层将保证页面中的元素以正确的顺序堆叠排列。
也就是说,我们的页面不是2D平面的,而是三维立体的!
前端所说的层叠上下文,其实正是因为页面的3D特性。
Composite阶段就是负责把所有图层,按照合理的顺序合并成一个图层。对于有元素位置重叠的页面,这个过程尤其重要。因为一旦图层的合并顺序出错,将会导致元素显示异常。
这个模型类似PhotoShop的图层模型。在PhotoShop中,每个设计元素都是一个独立的图层,多个图层以前挡的顺序在Z轴空间上叠加,最终构成完整的设计图。
合成层
合成层是一种特殊的渲染层,这也是我们讨论的重点。
Chrome浏览器提供了查看图层的工具:Layers,虽然很难用,但也可以看出其中的层级。
先来看看淘宝的渲染层和合成层
CSS3 跳过重排重绘,避开CPU,直接进入GPU硬件加速,执行动画,正是在合成层完成的。不影响其他渲染层。
淘宝首页的banner提升到合成层:transform: translate3d()
提升合成层的因素有很多,这里我们只演示 CSS3 transform
代码案例~~~~~
常用的还有will-change
#target {
will-change: transform, opacity;
}
渲染层自动提升合成层
合成层并不是一定要通过手动开启的,有些原因也会自动提升合成层,其中,重叠原因是最常见的。
层爆炸与层压缩
- 天下没有白吃的午餐!合成层虽好,但也是需要付出代价的!
- 过多的合成层,意味着更复杂的图层管理,意味着对资源的消耗。
- 这就是层爆炸。
- 而且,GPU是显卡的核心部件,显卡的配置往往是比较低的,内存有限,很容易因为层爆炸而崩溃。
- 当然,浏览器也想到了这一点,因此有了层压缩。
- 当多个渲染层与同一个合成层重叠时,这些渲染会被压缩到一个图层上。
- 但是,层压缩机制也不是万能的,也有很多原因不能进行层压缩,比如,不能进行会打破渲染顺序的压缩。
css3直接使用GPU加速小结
- 在实际开发中,我们常见一些CSS基础库会使用:box-sizing: border-box; 原因就是为了固定盒子大小,尽可能减少重排。
- 尽量使用CSS3动画替代JS模拟动画,不仅可以利用硬件加速,减少重排,还不会占用JS主线程。而且浏览器会对CSS动画做优化。
- transform: translateZ(0); 常用来欺骗浏览器,提升合成层,利用GPU硬件加速。
- 性能优化并没有所谓的“银弹”,transform: translateZ(0) 不是,本文列出的优化建议也不是。
- 抛开了对页面的具体分析,任何的性能优化都是站不住脚的,盲目的使用一些优化措施,结果可能会适得其反。
- 因此切实的去分析页面的实际性能表现,不断的改进测试,才是正确的优化途径。
什么是html的解析?
解析html/css文档是指将文档转化成为有意义的结构,也就是可让代码理解和使用的结构。
解析得到的结果通常是表示了文档结构的节点树,它被称为解析树或者语法树。
解析的过程可以分为两个子过程:词法分析和语法分析
。
- 词法分析
词法分析是将输入内容分割成大量标记的过程。
其实html本质上就是字符串,<html lang="en"><body></body></html>;
需要在词法分析过程转变成一个个的词
<html lang="en">
<body>
</body>
</html>
-语法分析
应用语言的语法规则的过程。在认类语言中,相当于字典中的单词。
根据(html or css等)语言的语法规则分析文档的结构,从而构建解析树。
例子:2+3-1,分析过程:
这种方式被称为,
移位规约解析器
,因为输入在向右移动(设想有一个指针从输入内容的开头移动到结尾)(这之前是词法分析), 并且逐渐归约到语法规则上。
HTML解析器
任务是将HTML标记解析成解析树。
HTML的定义采用DTD的格式。此格式可用于定义SGML族的语言。它包括所有允许使用的元素及其属性和层次结构的定义。
现在都是使用html5的规范来解析了,DTD不使用了。DOM解析器
作用是,输出"解析树"。是由DOM元素和属性节点构成的树结构。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<P></p>
</body>
</html>
首先浏览器从上到下依次解析文档构建的DOM树
-
CSS例子:
在上面的过程主要有两个阶段:
- 标记化
- 树的构建:
标记化是词法分析的过程,将输入内容解析程多个标记。例如:HTML标记包含起始标记,结束标记,属性名称和属性值。不断的解析,直到加载网页结束。
树的构建:在创建解析器的同时,也会创建Document对象。在树的构建阶段,以Document为根节点的DOM树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。
这些标记元素不仅会添加到DOM树种,还会添加到开放元素的堆栈种。此堆栈用于纠正嵌套错误和处理未关闭的标记。
其算法也可以用状态机
来描述,这些状态成为"插入模式"。
-
Dom树和渲染树
每一个渲染对象都对应着DOM节点
创建渲染树之后,下一步就是布局(Layout)这个过程就是通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸,将其安置在浏览器窗口的正确位置。
有些时候我们会在文档布局完成之后对DOM进行修改,这时候可能需要重新进行布局,也可以称其为回流(重绘)。
根据需要,或是全局重绘,或是局部重绘,最终触发"重新渲染页面"。
Object.create原理
Bell.prototyp=Object.create(EventEmitter.prototype);
//等效于下面三行
function Temp() {}
Temp.prototype=EventEmitter.prototype;
Bell.prototype=new Temp();
//等效于下面
let bell=new Bell();
bell.__proto__=EventEmitter.prototype;
事件捕获和冒泡
dom标准事件流的触发的先后顺序为:先捕获再冒泡。即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。
- addEventListener的第三个参数
element.addEventListener(event, function, useCapture);
第三个参数默认值是false,表示在事件冒泡
阶段调用事件处理函数;如果参数为true,则表示在事件捕获
阶段调用处理函数。
- 事件冒泡
<body>
<div id="parent">
父元素
<div id="child">
子元素
</div>
</div>
<script type="text/javascript">
var parent = document.getElementById("parent");
var child = document.getElementById("child");
document.body.addEventListener("click",function(e){
console.log("click-body");
},false);
parent.addEventListener("click",function(e){
console.log("click-parent");
},false);
child.addEventListener("click",function(e){
console.log("click-child");
},false);
</script>
</body>
事件触发顺序是由内到外的,这就是事件冒泡。如果点击子元素不想触发父元素的事件,可使用event.stopPropagation();方法:
child.addEventListener("click",function(e){
console.log("click-child");
//该句是事件是否继续冒泡
e.stopPropagation();
},false);
//false指的是在冒泡阶段执行,效果不同,配合起来使用可以精确控制
- 事件捕获
var parent = document.getElementById("parent");
var child = document.getElementById("child");
document.body.addEventListener("click",function(e){
console.log("click-body");
},false);
parent.addEventListener("click",function(e){
console.log("click-parent---事件传播");
},false);
//新增事件捕获事件代码
parent.addEventListener("click",function(e){
console.log("click-parent--事件捕获");
},true);
child.addEventListener("click",function(e){
console.log("click-child");
},false);
父元素通过事件捕获的方式注册了click事件,所以在事件捕获阶段就会触发,然后到了目标阶段,即事件源,之后进行事件冒泡,parent同时也用冒泡方式注册了click事件,所以这里会触发冒泡事件,最后到根节点(body)。这就是整个事件流程。