1、IntersectionObserver API
IntersectionObserver
可以用来自动监听元素是否进入了设备的可视区域之内,而不需要频繁的计算来做这个判断。由于可见(visible)的本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"
javascript
const observer = new IntersectionObserver(callback, option);
IntersectionObserver
是浏览器原生提供的构造函数,接受两个参数
- callback:当可见性发生变化时的回调函数
- option:配置对象(可选性)
构造函数的返回值生成的实例有四个方法:
- observe:开始监听特定元素
- unobserve:停止监听特定元素
- disconnect:关闭监听工作
- takeRecords:返回所有观察目标的对象数组
1.1、observe 方法
该方法需要接收一个target参数,值为Element类型,指定被监听
的目标元素
javascript
// 获取dom元素
const target = document.getElementById("app")
// 开始观察 实例.observe(dom元素)
observer.observe(target);
1.2、unobserve 方法
该方法需要接收一个target参数,值是Element类型,用来指定停止监听
的目标元素
javascript
// 获取dom元素
const target = document.getElementById("app")
// 开始观察 实例.observe(dom元素)
observer.unobserve(target);
1.3、disconnect 方法
该方法不需要接收参数,用来关闭观察器
javascript
// 关闭观察器
observer.disconnect();
1.4、takeRecords 方法
该方法不需要接收参数,返回所有被观察的对象,返回值是一个数组
javascript
// 关闭观察器
const observerList = observer.takeRecords();
1.5、callback 参数
目标元素的可见性发生变化时,就会调用观察器的回调函数callback
可见性变化:目标元素进入视口,目标元素完全离开视口
javascript
const observer = new IntersectionObserver((changes, observer) => {
console.log(changes); // 包括的目标元素
console.log(observer); // 当前实例对象
});
1.6、 options
可以在1.5中的 console.log(observer)
看到
threshold
: 决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。root
: 用于观察的根元素,默认是浏览器的视口,也可以指定具体元素,指定元素的时候用于观察的元素必须是指定元素的子元素rootMargin
: 用来扩大或者缩小视窗的的大小,使用css的定义方法,10px 10px 30px 20px表示top、right、bottom 和 left的值
2、IntersectionObserverEntry 对象
changes
数组中每一项都是一个IntersectionObserverEntry
对象
boundingClientRect
:目标元素的矩形区域的信息intersectionRatio
:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0intersectionRect
:目标元素与视口(或根元素)的交叉区域的信息isIntersecting
: 布尔值,目标元素与交集观察者的根节点是否相交(常用)isVisible
: 布尔值,目标元素是否可见(不建议在生产环境中使用)rootBounds
:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回nulltarget
:被观察的目标元素,是一个 DOM 节点对象(常用)time
:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
3、相关效果
我们可以通过IntersectionObserver
来做到与滚动相关的效果
3.1、图片懒加载
首先需要观察懒加载元素,然后等元素进入可视化区域后设置图片的spc。还可以结合 IntersectionObserver.rootMargin
实现提前加载图片,一般可以设置为 1~2倍浏览器窗口的视口高度
HTML部分
html
<body>
<h1>JavaScript 的歷史及現況</h1>
<p>1990 年,科學家 Tim Berners-Lee 在互聯網的基礎上發明了萬維網 (World Wide Web),World Wide Web
這個詞在互聯網早期經常提到,還一直影響至今,大家記得網址開端一般都是以 www 起首的嗎?就是 World Wide Web 的簡寫。發明了
www,從此我們可以在網上互傳檔案。但這個時候的只能通過命令行模式 (Command Mode) 存取網絡,而且只能顯示文字,顯然是不方便的。</p>
<p>1992 年史上第一個圖形化的網頁瀏覽器 Mosaic 誕生,1994 年改名為 Netscape Navigator (網景) 並推出 1.0 版本,市場佔有率超過
90%。由於當時的互聯網速度很慢,而且上網費昂貴,Netscape
公司為了優化瀏覽體驗,覺得有必要研發一套腳本語言,是在瀏覽器端運行的。例如我們在一個網站登入會員,忘了填寫用戶名稱就點擊了「登入」,這是候網頁再次載入,才提醒需要填寫用戶名稱,這顯然晚了一點。瀏覽器應該可以在用戶點擊「登入」時就進行初步檢查,在檢查到錯誤時及時彈出提示訊息讓用戶修正。
</p>
<p>Netscape 公司對於這種基於瀏覽器的腳本語言的想法是:功能不需要太強,語法較簡單,容易學習和應用。公司聘請了 Brendan Eich 研發這個腳本語言,據說他只用了 10
天,就設計好這個語言的第一版,最初命名為 Mocha,及後改名為 LiveScript。</p>
<p>而據說當時 Sun 公司的 Java 語言相當受歡迎,出於市場推廣的原因,固意將這門語言與 Java 在名義上扯上關係(而實際上幾乎沒有)。Netscape 與 Sun 公司達成「合作協議」允許將 LiveScript 命名為
JavaScript。大家還記得當時有 JavaApplet 這種瀏覽器插件嗎?JavaApplet 是 Sun 公司發展出來,真正使用 Java 語言編寫,在瀏覽器端運行的應用程式,當時期望 JavaScript
的角色是像膠水一樣將各部份連接起來。後來 JavaApplet 失敗了,JavaScript 卻發揚光大了。</p>
<p>1996 年 JavaScript 隨著 Netscape 2.0 正式推出。</p>
<p>1996 年 8 月 Microsoft 仿效 Netscape,於自己的 IE 瀏覽器發展出一門與 JavaScript 相近的腳本語言,取命 JScript。一山不能藏二虎,Netscape 公司將
JavaScript 提交給 ECMA International(歐洲電腦製造商協會)進行標準化,它就是 ECMAScript。不過基於歷史原因及市場原因,大家都習慣用 JavaScript
這個名字而非 ECMAScript。我們現在說的 JavaScript ES5 / ES6 等等,ES 就是 ECMAScript 版本的簡寫,後面的是版本號。</p>
<p>ECMAScript 一直持續發展,但由於瀏覽器的支援滯後,在好長一段時間內一直流行 ECMAScript 3。直至 2012 年出現轉捩點,各大網站開始停止對舊版 IE 瀏覽器的支援,以及
Chrome 與 Firefox 等瀏覽器的佔有率開始提升,使得 ECMAScript 5 流行起來。</p>
<p>在 2018 年的今天,即使 ECMAScript 6 在瀏覽器端還未正式普及,但有轉譯工具可以幫助我們今天起就編寫 ES6 的 JavaScript,而 Node.js 陣型(如 React
Native)使用 ES6 已非常普遍了。</p>
<h1>1、定义</h1>
<p> 编译语言:编译语言程序执行之前,需要先通篇翻译,编译后生产编译文件,系统执行编译文件(即计算机识别的二进制文件,不同的操作系统计算机识别的二进制文件是不同的)</p>
<p>如:源程序=》编译器=》机器</p>
<p>解释性语言:解释不需要先编译,执行时才翻译执行。程序每执行一次就要翻译一遍。</p>
<p>如:源程序=》(逐条读取、转换、执行)=》机器</p>
<h1>2、优缺点</h1>
<p>编译语言:执行效率高,移植性不好(跨平台能力弱)不便调试。</p>
<p>解释性语言:跨平台能力强,易于调,执行速度稍慢。</p>
<!-- 放置图片 -->
<p><img style="height: 500px;" data-src="./img1.png"></p>
<p><img style="height: 500px;" data-src="./img2.png"></p>
<p><img style="height: 500px;" data-src="./img3.png"></p>
<p><img style="height: 500px;" data-src="./img4.png"></p>
<h1>3、语言</h1>
<p>编译语言:C、C++</p>
<p></p>
<p>解释性语言:javascript、php、Python、Ruby</p>
<p></p>
<p>java:是oak语言,是特殊的语言,即包含了编译的过程,又包含了解释性</p>
<p></p>
<p>执行过程:java ---》javac--》编译--》.class--->jvm---解释执行</p>
</body>
JS部分
javascript
// 定义回调函数
const callback = entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const image = entry.target
// 获取图片路由
const data_src = image.getAttribute('data-src')
// 将我们的图片路由放置给image节点的src属性
image.setAttribute('src', data_src)
// 取消观察该标签
observer.unobserve(image)
}
})
}
// 进行实例化
const observer = new IntersectionObserver(callback);
// 获取所有图片的dom元素
images = document.querySelectorAll('img')
// 遍历图片dom元素数组进行观察
images.forEach(image => {
observer.observe(image)
})
3.2、 封装图片懒加载函数
javascript
/**
* @method lazyLoad
* @param {NodeList} $imgList 图片元素集合
* @param {number} preloadHeight 预加载高度
*/
export function lazyLoad($imgList, preloadHeight = 1000) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) { // 目标元素出现在 root 可视区,返回 true
const $target = entry.target
const src = $target.getAttribute('lazyload')
if (src) {
$target.setAttribute('src', src) // 真正加载图片
}
observer.unobserve($target) // 解除观察
}
})
}, {
rootMargin: `0px 0px ${preloadHeight}px 0px`,
})
Array.prototype.forEach.call($imgList, ($item) => {
if ($item.getAttribute('src')) return // 过滤已经加载过的图片
observer.observe($item) // 开始观察
})
}
使用方式:
javascript
// 图片元素设置 lazyload 属性
<img lazyload="图片链接" alt="图片说明">
javascript
// 观察图片元素
lazyLoad(document.querySelectorAll("[元素]"))
3.3、无限滚动
html
<body style="font-size: 24px;">
<ul id="container"></ul>
<div id="loadMore">加载中...</div>
</body>
<script>
// 获取展示区 和 loding区的dom元素
const container = document.querySelector('#container');
const loadMore = document.querySelector('#loadMore');
let index = 0;
const loadItems = (count) => {
[...Array(count).keys()].forEach((key) => {
console.log(key + index);
const p = document.createElement('Li');
p.innerHTML = `${key + index}`;
container.appendChild(p)
})
index += count;
}
const observer = new IntersectionObserver((entries) => {
entries.forEach(({ isIntersecting }) => {
if (isIntersecting) {
console.log('loadMore');
loadItems(30);
}
})
});
observer.observe(loadMore)
</script>
3.4、加载更多
通过先有的数据数组进行loding加载渲染
html
<body style="font-size: 24px;">
<ul id="container"></ul>
<div id="loadMore">加载中...</div>
</body>
<script>
// 定义数据数组,并进行赋值
let dataList = new Array(100)
for (let i = 0; i < 100; i++) {
dataList[i] = i
}
// 获取内容区 和 loding区的dom元素
const container = document.querySelector('#container');
const loadMore = document.querySelector('#loadMore');
// 初始化索引 和 获取数据索引最大值
let index = 0;
let maxLen = dataList.length;
// 定义填充页面数据的函数
const loadItems = (count) => {
if ((index + count) >= maxLen) {
dataList.slice(index, maxLen).forEach((key, index) => {
const p = document.createElement('Li');
p.innerHTML = `${key}`;
container.appendChild(p)
})
loadMore.innerHTML = '没有更多了'
observer.unobserve(loadMore)
return
} else {
dataList.slice(index, index + count).forEach((key) => {
const p = document.createElement('Li');
p.innerHTML = `${key}`;
container.appendChild(p)
})
}
index += count;
}
// 进行实例化
const observer = new IntersectionObserver((entries) => {
entries.forEach(({ isIntersecting }) => {
// 当loding区的dom元素为可视化时,调用加载函数
if (isIntersecting) {
// 想要延迟,可以添加定时器,延迟渲染出数据
loadItems(30);
}
})
});
observer.observe(loadMore)
</script>