本文在 github 做了收录 https://github.com/Michael-lz…
MutationObserver
MutationObserver 是一个能够监听 DOM 构造变动的接口。当 DOM 对象树产生任何变动时,MutationObserver 会失去告诉。
API
MutationObserver 是一个结构器,承受一个 callback 参数,用来解决节点变动的回调函数,返回两个参数:
- mutations:节点变动记录列表
(sequence<MutationRecord>)
- observer:结构 MutationObserver 对象。
MutationObserver 对象有三个办法,别离如下:
- observe:设置察看指标,承受两个参数,target:察看指标,options:通过对象成员来设置察看选项
- disconnect:阻止观察者察看任何扭转
- takeRecords:清空记录队列并返回外面的内容
// 抉择一个须要察看的节点
var targetNode = document.getElementById('root')
// 设置 observer 的配置选项
var config = {attributes: true, childList: true, subtree: true}
// 当节点发生变化时的须要执行的函数
var callback = function (mutationsList, observer) {for (var mutation of mutationsList) {if (mutation.type == 'childList') {console.log('A child node has been added or removed.')
} else if (mutation.type == 'attributes') {console.log('The' + mutation.attributeName + 'attribute was modified.')
}
}
}
// 创立一个 observer 示例与回调函数相关联
var observer = new MutationObserver(callback)
// 应用配置文件对指标节点进行观测
observer.observe(targetNode, config)
// 进行观测
observer.disconnect()
observe 办法中 options 参数有已下几个选项:
- childList:设置 true,示意察看指标子节点的变动,比方增加或者删除指标子节点,不包含批改子节点以及子节点后辈的变动
- attributes:设置 true,示意察看指标属性的扭转
- characterData:设置 true,示意察看指标数据的扭转
- subtree:设置为 true,指标以及指标的后辈扭转都会察看
- attributeOldValue:如果属性为 true 或者省略,则相当于设置为 true,示意须要记录扭转前的指标属性值,设置了 attributeOldValue 能够省略 attributes 设置
- characterDataOldValue:如果 characterData 为 true 或省略,则相当于设置为 true, 示意须要记录扭转之前的指标数据,设置了 characterDataOldValue 能够省略 characterData 设置
- attributeFilter:如果不是所有的属性扭转都须要被察看,并且 attributes 设置为 true 或者被疏忽,那么设置一个须要察看的属性本地名称(不须要命名空间)的列表
特点
MutationObserver 有以下特点:
- 它期待所有脚本工作实现后才会运行,即采纳异步形式
- 它把 DOM 变动记录封装成一个数组进行解决,而不是一条条地个别解决 DOM 变动。
- 它即能够察看产生在 DOM 节点的所有变动,也能够察看某一类变动
当 DOM 产生变动会触发 MutationObserver 事件。然而,它与事件有一个实质不同:事件是同步触发,也就是说 DOM 产生变动立即会触发相应的事件;MutationObserver 则是异步触发,DOM 产生变动当前,并不会马上触发,而是要等到以后所有 DOM 操作都完结后才触发。
举例来说,如果在文档中间断插入 1000 个段落(p 元素),会间断触发 1000 个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 MutationObserver 齐全不同,只在 1000 个段落都插入完结后才会触发,而且只触发一次,这样较少了 DOM 的频繁变动,大大有利于性能。
IntersectionObserver
网页开发时,经常须要理解某个元素是否进入了 ” 视口 ”(viewport),即用户能不能看到它。
传统的实现办法是,监听到 scroll 事件后,调用指标元素的 getBoundingClientRect()办法,失去它对应于视口左上角的坐标,再判断是否在视口之内。这种办法的毛病是,因为 scroll 事件密集产生,计算量很大,容易造成性能问题。
目前有一个新的 IntersectionObserver API,能够主动 ” 察看 ” 元素是否可见,Chrome 51+ 曾经反对。因为可见(visible)的实质是,指标元素与视口产生一个穿插区,所以这个 API 叫做 ” 穿插观察器 ”。
API
IntersectionObserver 是浏览器原生提供的构造函数,承受两个参数:callback 是可见性变动时的回调函数,option 是配置对象(该参数可选)。
var io = new IntersectionObserver(callback, option)
// 开始察看
io.observe(document.getElementById('example'))
// 进行察看
io.unobserve(element)
// 敞开观察器
io.disconnect()
如果要察看多个节点,就要屡次调用这个办法。
io.observe(elementA)
io.observe(elementB)
指标元素的可见性变动时,就会调用观察器的回调函数 callback。callback 个别会触发两次。一次是指标元素刚刚进入视口(开始可见),另一次是齐全来到视口(开始不可见)。
var io = new IntersectionObserver((entries) => {console.log(entries)
})
callback 函数的参数(entries)是一个数组,每个成员都是一个 IntersectionObserverEntry 对象。举例来说,如果同时有两个被察看的对象的可见性发生变化,entries 数组就会有两个成员。
- time:可见性发生变化的工夫,是一个高精度工夫戳,单位为毫秒
- target:被察看的指标元素,是一个 DOM 节点对象
- isIntersecting: 指标是否可见
- rootBounds:根元素的矩形区域的信息,getBoundingClientRect()办法的返回值,如果没有根元素(即间接绝对于视口滚动),则返回 null
- boundingClientRect:指标元素的矩形区域的信息
- intersectionRect:指标元素与视口(或根元素)的穿插区域的信息
- intersectionRatio:指标元素的可见比例,即 intersectionRect 占 boundingClientRect 的比例,齐全可见时为 1,齐全不可见时小于等于 0
举个例子
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<style>
#div1 {
position: sticky;
top: 0;
height: 50px;
line-height: 50px;
text-align: center;
background: black;
color: #ffffff;
font-size: 18px;
}
</style>
</head>
<body>
<div id="div1"> 首页 </div>
<div style="height: 1000px;"></div>
<div id="div2" style="height: 100px; background: red;"></div>
<script>
var div2 = document.getElementById('div2')
let observer = new IntersectionObserver(function (entries) {entries.forEach(function (element, index) {console.log(element)
if (element.isIntersecting) {div1.innerText = '我进去了'} else {div1.innerText = '首页'}
})
},
{
root: null,
threshold: [0, 1]
}
)
observer.observe(div2)
</script>
</body>
</html>
相比于 getBoundingClientRect,它的长处是不会引起重绘回流。兼容性如下
图片懒加载
图片懒加载的原理次要是判断以后图片是否到了可视区域这一外围逻辑实现的。这样能够节俭带宽,进步网页性能。传统的冲破懒加载是通过监听 scroll 事件实现的,然而 scroll 事件会在很短的工夫内触发很屡次,重大影响页面性能。为进步页面性能,咱们能够应用 IntersectionObserver 来实现图片懒加载。
const imgs = document.querySelectorAll('img[data-src]')
const config = {
rootMargin: '0px',
threshold: 0
}
let observer = new IntersectionObserver((entries, self) => {entries.forEach((entry) => {if (entry.isIntersecting) {
let img = entry.target
let src = img.dataset.src
if (src) {
img.src = src
img.removeAttribute('data-src')
}
// 解除察看
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) => {observer.observe(image)
})
有限滚动
有限滚动(infinite scroll)的实现也很简略。
var intersectionObserver = new IntersectionObserver(function (entries) {
// 如果不可见,就返回
if (entries[0].intersectionRatio <= 0) return
loadItems(10)
console.log('Loaded new items')
})
// 开始察看
intersectionObserver.observe(document.querySelector('.scrollerFooter'))
getComputedStyle()
DOM2 Style 在 document.defaultView
上减少了 getComputedStyle()办法,该办法返回一个 CSSStyleDeclaration
对象(与 style 属性的类型一样),蕴含元素的计算款式。
API
document.defaultView.getComputedStyle(element[,pseudo-element])
// or
window.getComputedStyle(element[,pseudo-element])
这个办法接管两个参数:要获得计算款式的元素和伪元素字符串(如 ”:after”)。如果不须要查问伪元素,则第二个参数能够传 null。
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#myDiv {
background-color: blue;
width: 100px;
height: 200px;
}
</style>
</head>
<body>
<div id="myDiv" style="background-color: red; border: 1px solid black"></div>
</body>
<script>
function getStyleByAttr(obj, name) {return window.getComputedStyle ? window.getComputedStyle(obj, null)[name] : obj.currentStyle[name]
}
let node = document.getElementById('myDiv')
console.log(getStyleByAttr(node, 'backgroundColor'))
console.log(getStyleByAttr(node, 'width'))
console.log(getStyleByAttr(node, 'height'))
console.log(getStyleByAttr(node, 'border'))
</script>
</html>
和 style 的异同
getComputedStyle 和 element.style 的相同点就是二者返回的都是 CSSStyleDeclaration 对象。而不同点就是:
- element.style 读取的只是元素的内联款式,即写在元素的 style 属性上的款式;而 getComputedStyle 读取的款式是最终款式,包含了内联款式、嵌入款式和内部款式。
- element.style 既反对读也反对写,咱们通过 element.style 即可改写元素的款式。而 getComputedStyle 仅反对读并不反对写入。咱们能够通过应用 getComputedStyle 读取款式,通过 element.style 批改款式
getBoundingClientRect
getBoundingClientRect() 办法返回元素的大小及其绝对于视口的地位。
API
let DOMRect = object.getBoundingClientRect()
它的返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 办法返回的一组矩形的汇合,就是该元素的 CSS 边框大小。返回的后果是蕴含残缺元素的最小矩形,并且领有 left, top, right, bottom, x, y, width, 和 height 这几个以像素为单位的只读属性用于形容整个边框。除了 width 和 height 以外的属性是绝对于视图窗口的左上角来计算的。
利用场景
1、获取 dom 元素绝对于网页左上角定位的间隔
以前的写法是通过 offsetParent 找到元素到定位父级元素,直至递归到顶级元素 body 或 html。
// 获取 dom 元素绝对于网页左上角定位的间隔
function offset(el) {
var top = 0
var left = 0
do {
top += el.offsetTop
left += el.offsetLeft
} while ((el = el.offsetParent)) // 存在兼容性问题,须要兼容
return {
top: top,
left: left
}
}
var odiv = document.getElementsByClassName('markdown-body')
offset(a[0]) // {top: 271, left: 136}
当初依据 getBoundingClientRect 这个 api,能够写成这样:
var positionX = this.getBoundingClientRect().left + document.documentElement.scrollLeft
var positionY = this.getBoundingClientRect().top + document.documentElement.scrollTop
2、判断元素是否在可视区域内
function isElView(el) {var top = el.getBoundingClientRect().top // 元素顶端到可见区域顶端的间隔
var bottom = el.getBoundingClientRect().bottom // 元素底部端到可见区域顶端的间隔
var se = document.documentElement.clientHeight // 浏览器可见区域高度。if (top < se && bottom > 0) {return true} else if (top >= se || bottom <= 0) {// 不可见}
return false
}
requestAnimationFrame
window.requestAnimationFrame() 通知浏览器——你心愿执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
API
该办法须要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
window.requestAnimationFrame(callback)
兼容性解决
window._requestAnimationFrame = (function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {window.setTimeout(callback, 1000 / 60)
}
)
})()
完结动画
var globalID
function animate() {// done(); 始终运行
globalID = requestAnimationFrame(animate) // Do something animate
}
globalID = requestAnimationFrame(animate) // 开始
cancelAnimationFrame(globalID) // 完结
与 setTimeout 相比,requestAnimationFrame 最大的劣势是由零碎来决定回调函数的执行机会。具体一点讲,如果屏幕刷新率是 60Hz, 那么回调函数就每 16.7ms 被执行一次,如果刷新率是 75Hz,那么这个工夫距离就变成了 1000/75=13.3ms,换句话说就是,requestAnimationFrame 的步调跟着零碎的刷新步调走。它能保障回调函数在屏幕每一次的刷新距离中只被执行一次,这样就不会引起丢帧景象,也不会导致动画呈现卡顿的问题。这个 API 的调用很简略,如下所示:
var progress = 0
// 回调函数
function render() {
progress += 1 // 批改图像的地位
if (progress < 100) {
// 在动画没有完结前,递归渲染
window.requestAnimationFrame(render)
}
}
// 第一帧渲染
window.requestAnimationFrame(render)
长处:
- CPU 节能:应用 setTimeout 实现的动画,当页面被暗藏或最小化时,setTimeout 依然在后盾执行动画工作,因为此时页面处于不可见或不可用状态,刷新动画是没有意义的,齐全是节约 CPU 资源。而 requestAnimationFrame 则齐全不同,当页面解决未激活的状态下,该页面的屏幕刷新工作也会被零碎暂停,因而跟着零碎步调走的 requestAnimationFrame 也会进行渲染,当页面被激活时,动画就从上次停留的中央继续执行,无效节俭了 CPU 开销。
- 函数节流:在高频率事件 (resize,scroll 等) 中,为了避免在一个刷新距离内产生屡次函数执行,应用 requestAnimationFrame 可保障每个刷新距离内,函数只被执行一次,这样既能保障流畅性,也能更好的节俭函数执行的开销。一个刷新距离内函数执行屡次时没有意义的,因为显示器每 16.7ms 刷新一次,屡次绘制并不会在屏幕上体现进去。
利用场景
1、监听 scroll 函数
页面滚动事件(scroll)的监听函数,就很适宜用这个 api,推延到下一次从新渲染。
$(window).on('scroll', function () {window.requestAnimationFrame(scrollHandler)
})
平滑滚动到页面顶部
const scrollToTop = () => {
const c = document.documentElement.scrollTop || document.body.scrollTop
if (c > 0) {window.requestAnimationFrame(scrollToTop)
window.scrollTo(0, c - c / 8)
}
}
scrollToTop()
2、大量数据渲染
比方对十万条数据进行渲染,次要由以下几种办法:
(1)应用定时器
// 须要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
// 总页数
let page = total / once
// 每条记录的索引
let index = 0
// 循环加载数据
function loop(curTotal, curIndex) {if (curTotal <= 0) {return false}
// 每页多少条
let pageCount = Math.min(curTotal, once)
setTimeout(() => {for (let i = 0; i < pageCount; i++) {let li = document.createElement('li')
li.innerText = curIndex + i + ':' + ~~(Math.random() * total)
ul.appendChild(li)
}
loop(curTotal - pageCount, curIndex + pageCount)
}, 0)
}
loop(total, index)
(2)应用 requestAnimationFrame
// 须要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
// 总页数
let page = total / once
// 每条记录的索引
let index = 0
// 循环加载数据
function loop(curTotal, curIndex) {if (curTotal <= 0) {return false}
// 每页多少条
let pageCount = Math.min(curTotal, once)
window.requestAnimationFrame(function () {for (let i = 0; i < pageCount; i++) {let li = document.createElement('li')
li.innerText = curIndex + i + ':' + ~~(Math.random() * total)
ul.appendChild(li)
}
loop(curTotal - pageCount, curIndex + pageCount)
})
}
loop(total, index)
监控卡顿办法
每秒中计算一次网页的 FPS,取得一列数据,而后剖析。艰深地解释就是,通过 requestAnimationFrame API 来定时执行一些 JS 代码,如果浏览器卡顿,无奈很好地保障渲染的频率,1s 中 frame 无奈达到 60 帧,即可间接地反映浏览器的渲染帧率。
var lastTime = performance.now()
var frame = 0
var lastFameTime = performance.now()
var loop = function (time) {var now = performance.now()
var fs = now - lastFameTime
lastFameTime = now
var fps = Math.round(1000 / fs)
frame++
if (now > 1000 + lastTime) {var fps = Math.round((frame * 1000) / (now - lastTime))
frame = 0
lastTime = now
}
window.requestAnimationFrame(loop)
}
咱们能够定义一些边界值,比方间断呈现 3 个低于 20 的 FPS 即可认为网页存在卡顿。
举荐文章
你必须晓得的 webpack 插件原理剖析
webpack 的异步加载原理及分包策略
总结 18 个 webpack 插件,总会有你想要的!
搭建一个 vue-cli4+webpack 挪动端框架(开箱即用)
从零构建到优化一个相似 vue-cli 的脚手架
封装一个 toast 和 dialog 组件并公布到 npm
从零开始构建一个 webpack 我的项目
总结几个 webpack 打包优化的办法
总结 vue 常识体系之高级利用篇
总结 vue 常识体系之实用技巧
总结 vue 常识体系之根底入门篇
总结挪动端 H5 开发罕用技巧(干货满满哦!)