// 定制詳情頁(yè)之選擇欄目
$(document).off('click','.js-nav-headerwrap>a').on('click','.js-nav-headerwrap>a',function(){
var _$this=$(this);
_$this.addClass('active').siblings().removeClass('active');
$('.cs-custommade').removeClass('isactive');
if (_$this.attr('data-set')) {
$('.'+ _$this.attr('data-set')).addClass('isactive');
}
//如果固定
if ($('.js-nav-headerwrap').hasClass('fixed')) {
var scrollTop=$(window).scrollTop();
var numTop=$('.apply-main').height() + $('.courser-info-second').height()+ $('.course-studytime-new22').height();
console.log(numTop);
// 距離
$("body,html").animate({
scrollTop: numTop
}, 300);
}
});
可視區(qū)域即我們?yōu)g覽網(wǎng)頁(yè)的設(shè)備肉眼可見(jiàn)的區(qū)域,如下圖
在日常開(kāi)發(fā)中,我們經(jīng)常需要判斷目標(biāo)元素是否在視窗之內(nèi)或者和視窗的距離小于一個(gè)值(例如 100 px),從而實(shí)現(xiàn)一些常用的功能,例如:
判斷一個(gè)元素是否在可視區(qū)域,我們常用的有三種辦法:
offsetTop,元素的上外邊框至包含元素的上內(nèi)邊框之間的像素距離,其他offset屬性如下圖所示:
下面再來(lái)了解下clientWidth、clientHeight:
這里可以看到client元素都不包括外邊距
最后,關(guān)于scroll系列的屬性如下:
下面再看看如何實(shí)現(xiàn)判斷:
公式如下:
el.offsetTop - document.documentElement.scrollTop <=viewPortHeight
代碼實(shí)現(xiàn):
function isInViewPortOfOne (el) {
// viewPortHeight 兼容所有瀏覽器寫法
const viewPortHeight=window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const offsetTop=el.offsetTop
const scrollTop=document.documentElement.scrollTop
const top=offsetTop - scrollTop
return top <=viewPortHeight
}
rectObject.top:元素上邊到視窗上邊的距離;
rectObject.right:元素右邊到視窗左邊的距離;
rectObject.bottom:元素下邊到視窗上邊的距離;
rectObject.left:元素左邊到視窗左邊的距離;
rectObject.width:是元素自身的寬度
rectObject.height是元素自身的高度
getBoundingClientRect()獲取元素位置,這個(gè)方法沒(méi)有參數(shù)
getBoundingClientRect用于獲得頁(yè)面中某個(gè)元素的左,上,右和下分別相對(duì)瀏覽器視窗的位置。
getBoundingClientRect是DOM元素到瀏覽器可視范圍的距離(不包含文檔卷起的部分)。
該函數(shù)返回一個(gè)Object對(duì)象,該對(duì)象有6個(gè)屬性:top,lef,right,bottom,width,height;
document.body.getBoundingClientRect().width
返回值是一個(gè) DOMRect對(duì)象,擁有left, top, right, bottom, x, y, width, 和 height屬性
const target=document.querySelector('.target');
const clientRect=target.getBoundingClientRect();
console.log(clientRect);
// {
// bottom: 556.21875,
// height: 393.59375,
// left: 333,
// right: 1017,
// top: 162.625,
// width: 684
// }
屬性對(duì)應(yīng)的關(guān)系圖如下所示:
當(dāng)頁(yè)面發(fā)生滾動(dòng)的時(shí)候,top與left屬性值都會(huì)隨之改變
如果一個(gè)元素在視窗之內(nèi)的話,那么它一定滿足下面四個(gè)條件:
實(shí)現(xiàn)代碼如下:
function isInViewPort(element) {
const viewWidth=window.innerWidth || document.documentElement.clientWidth;
const viewHeight=window.innerHeight || document.documentElement.clientHeight;
const {
top,
right,
bottom,
left,
}=element.getBoundingClientRect();
return (
top >=0 &&
left >=0 &&
right <=viewWidth &&
bottom <=viewHeight
);
}
我們需要觀察的元素被稱為 目標(biāo)元素(target),設(shè)備視窗或者其他指定的元素視口的邊界框我們稱它為 根元素(root),或者簡(jiǎn)稱為 根 。
Interp Observer API 翻譯過(guò)來(lái)叫做 “交叉觀察器”,因?yàn)榕袛嘣厥欠窨梢?jiàn)(通常情況下)的本質(zhì)就是判斷目標(biāo)元素和根元素是不是產(chǎn)生了 交叉區(qū)域 。
為什么是通常情況下,因?yàn)楫?dāng)我們 css 設(shè)置了 opacity: 0,visibility: hidden 或者 用其他的元素覆蓋目標(biāo)元素 的時(shí)候,對(duì)于視圖來(lái)說(shuō)是不可見(jiàn)的,但對(duì)于交叉觀察器來(lái)說(shuō)是可見(jiàn)的。這里可能有點(diǎn)抽象,大家只需記住,交叉觀察器只關(guān)心 目標(biāo)元素 和 根元素 是否有 交叉區(qū)域, 而不管視覺(jué)上能不能看見(jiàn)這個(gè)元素。當(dāng)然如果設(shè)置了 display:none,那么交叉觀察器就不會(huì)生效了,其實(shí)也很好理解,因?yàn)樵匾呀?jīng)不存在了,那么也就監(jiān)測(cè)不到了。
一句話總結(jié):Interp Observer API 提供了一種異步檢測(cè)目標(biāo)元素與祖先元素或 viewport 相交情況變化的方法。 -- MDN
現(xiàn)在不懂沒(méi)關(guān)系,咱們接著往下看,看完自然就明白了。
// 調(diào)用構(gòu)造函數(shù) InterpObserver 生成觀察器
const myObserver=new InterpObserver(callback, options);
首先調(diào)用瀏覽器原生構(gòu)造函數(shù) InterpObserver ,構(gòu)造函數(shù)的返回值是一個(gè) 觀察器實(shí)例 。
構(gòu)造函數(shù) InterpObserver 接收兩個(gè)參數(shù)
使用步驟主要分為兩步:創(chuàng)建觀察者和傳入被觀察者
為了方便理解,我們先看第二個(gè)參數(shù) options 。一個(gè)可以用來(lái)配置觀察器實(shí)例的對(duì)象,那么這個(gè)配置對(duì)象都包含哪些屬性呢?
const options={
// 表示重疊面積占被觀察者的比例,從 0 - 1 取值,
// 1 表示完全被包含
threshold: 1.0,
root:document.querySelector('#scrollArea') // 必須是目標(biāo)元素的父級(jí)元素
};
const callback=(entries, observer)=> { ....}
const observer=new IntersectionObserver(callback, options);
通過(guò)new IntersectionObserver創(chuàng)建了觀察者 observer,傳入的參數(shù) callback 在重疊比例超過(guò) threshold 時(shí)會(huì)被執(zhí)行`
關(guān)于callback回調(diào)函數(shù)常用屬性如下:
// 上段代碼中被省略的 callback
const callback=function(entries, observer) {
entries.forEach(entry=> {
entry.time; // 觸發(fā)的時(shí)間
entry.rootBounds; // 根元素的位置矩形,這種情況下為視窗位置
entry.boundingClientRect; // 被觀察者的位置舉行
entry.intersectionRect; // 重疊區(qū)域的位置矩形
entry.intersectionRatio; // 重疊區(qū)域占被觀察者面積的比例(被觀察者不是矩形時(shí)也按照矩形計(jì)算)
entry.target; // 被觀察者
});
};
通過(guò) observer.observe(target) 這一行代碼即可簡(jiǎn)單的注冊(cè)被觀察者
const target=document.querySelector('.target');
observer.observe(target);
實(shí)現(xiàn):創(chuàng)建了一個(gè)十萬(wàn)個(gè)節(jié)點(diǎn)的長(zhǎng)列表,當(dāng)節(jié)點(diǎn)滾入到視窗中時(shí),背景就會(huì)從紅色變?yōu)辄S色
Html結(jié)構(gòu)如下:
<div class="container"></div>
css樣式如下:
.container {
display: flex;
flex-wrap: wrap;
}
.target {
margin: 5px;
width: 20px;
height: 20px;
background: red;
}
往container插入1000個(gè)元素
const $container=$(".container");
// 插入 100000 個(gè) <div class="target"></div>
function createTargets() {
const htmlString=new Array(100000)
.fill('<div class="target"></div>')
.join("");
$container.html(htmlString);
}
這里,首先使用getBoundingClientRect方法進(jìn)行判斷元素是否在可視區(qū)域
function isInViewPort(element) {
const viewWidth=window.innerWidth || document.documentElement.clientWidth;
const viewHeight= window.innerHeight || document.documentElement.clientHeight;
const { top, right, bottom, left }=element.getBoundingClientRect();
return top >=0 && left >=0 && right <=viewWidth && bottom <=viewHeight;
}
然后開(kāi)始監(jiān)聽(tīng)scroll事件,判斷頁(yè)面上哪些元素在可視區(qū)域中,如果在可視區(qū)域中則將背景顏色設(shè)置為yellow
$(window).on("scroll", ()=> {
console.log("scroll !");
$targets.each((index, element)=> {
if (isInViewPort(element)) {
$(element).css("background-color", "yellow");
}
});
});
通過(guò)上述方式,可以看到可視區(qū)域顏色會(huì)變成黃色了,但是可以明顯看到有卡頓的現(xiàn)象,原因在于我們綁定了scroll事件,scroll事件伴隨了大量的計(jì)算,會(huì)造成資源方面的浪費(fèi)
下面通過(guò)Intersection Observer的形式同樣實(shí)現(xiàn)相同的功能
首先創(chuàng)建一個(gè)觀察者
const observer=new IntersectionObserver(getYellow, { threshold: 1.0 });
getYellow回調(diào)函數(shù)實(shí)現(xiàn)對(duì)背景顏色改變,如下:
function getYellow(entries, observer) {
entries.forEach(entry=> {
$(entry.target).css("background-color", "yellow");
});
}
最后傳入觀察者,即.target元素
$targets.each((index, element)=> {
observer.observe(element);
});
可以看到功能同樣完成,并且頁(yè)面不會(huì)出現(xiàn)卡頓的情況
給大家分享我收集整理的各種學(xué)習(xí)資料,前端小白交學(xué)習(xí)流程,入門教程等回答-下面是學(xué)習(xí)資料參考。
前端學(xué)習(xí)交流、自學(xué)、學(xué)習(xí)資料等推薦 - 知乎
懶加載其實(shí)就是延遲加載,是一種對(duì)網(wǎng)頁(yè)性能優(yōu)化可方式,比如當(dāng)訪問(wèn)一個(gè)頁(yè)面的時(shí)候,優(yōu)先顯示可視區(qū)域的圖片而不一次性加載所有圖片,當(dāng)需要顯示的時(shí)候再發(fā)送圖片請(qǐng)求,避免打開(kāi)網(wǎng)頁(yè)時(shí)加載過(guò)多資源。
當(dāng)頁(yè)面中需要一次性載入很多圖片的時(shí)候,往往都是需要用懶加載的。
我們都知道HTML中的 <img>標(biāo)簽是代表文檔中的一個(gè)圖像。。說(shuō)了個(gè)廢話。。
<img>標(biāo)簽有一個(gè)屬性是 src,用來(lái)表示圖像的URL,當(dāng)這個(gè)屬性的值不為空時(shí),瀏覽器就會(huì)根據(jù)這個(gè)值發(fā)送請(qǐng)求。如果沒(méi)有 src屬性,就不會(huì)發(fā)送請(qǐng)求。
嗯?貌似這點(diǎn)可以利用一下?
我先不設(shè)置 src,需要的時(shí)候再設(shè)置?
nice,就是這樣。
我們先不給 <img>設(shè)置 src,把圖片真正的URL放在另一個(gè)屬性 data-src中,在需要的時(shí)候也就是圖片進(jìn)入可視區(qū)域的之前,將URL取出放到 src中。
<div class="container">
<div class="img-area">
<img class="my-photo" alt="loading" data-src="./img/img1.png">
</div>
<div class="img-area">
<img class="my-photo" alt="loading" data-src="./img/img2.png">
</div>
<div class="img-area">
<img class="my-photo" alt="loading" data-src="./img/img3.png">
</div>
<div class="img-area">
<img class="my-photo" alt="loading" data-src="./img/img4.png">
</div>
<div class="img-area">
<img class="my-photo" alt="loading" data-src="./img/img5.png">
</div>
</div>
仔細(xì)觀察一下, <img>標(biāo)簽此時(shí)是沒(méi)有 src屬性的,只有 alt和 data-src屬性。
alt 屬性是一個(gè)必需的屬性,它規(guī)定在圖像無(wú)法顯示時(shí)的替代文本。 data-* 全局屬性:構(gòu)成一類名稱為自定義數(shù)據(jù)屬性的屬性,可以通過(guò) HTMLElement.dataset來(lái)訪問(wèn)。
方法一
網(wǎng)上看到好多這種方法,稍微記錄一下。
然后判斷②-③<①是否成立,如果成立,元素就在可視區(qū)域內(nèi)。
方法二(推薦)
通過(guò) getBoundingClientRect()方法來(lái)獲取元素的大小以及位置,MDN上是這樣描述的:
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.
這個(gè)方法返回一個(gè)名為 ClientRect的 DOMRect對(duì)象,包含了 top、 right、 botton、 left、 width、 height這些值。
MDN上有這樣一張圖:
可以看出返回的元素位置是相對(duì)于左上角而言的,而不是邊距。
我們思考一下,什么情況下圖片進(jìn)入可視區(qū)域。
假設(shè) constbound=el.getBoundingClientRect();來(lái)表示圖片到可視區(qū)域頂部距離; 并設(shè) constclientHeight=window.innerHeight;來(lái)表示可視區(qū)域的高度。
隨著滾動(dòng)條的向下滾動(dòng), bound.top會(huì)越來(lái)越小,也就是圖片到可視區(qū)域頂部的距離越來(lái)越小,當(dāng) bound.top===clientHeight時(shí),圖片的上沿應(yīng)該是位于可視區(qū)域下沿的位置的臨界點(diǎn),再滾動(dòng)一點(diǎn)點(diǎn),圖片就會(huì)進(jìn)入可視區(qū)域。
也就是說(shuō),在 bound.top<=clientHeight時(shí),圖片是在可視區(qū)域內(nèi)的。
我們這樣判斷:
function isInSight(el) {
const bound=el.getBoundingClientRect();
const clientHeight=window.innerHeight;
//如果只考慮向下滾動(dòng)加載
//const clientWidth=window.innerWeight;
return bound.top <=clientHeight + 100;
}
這里有個(gè)+100是為了提前加載。
頁(yè)面打開(kāi)時(shí)需要對(duì)所有圖片進(jìn)行檢查,是否在可視區(qū)域內(nèi),如果是就加載。
function checkImgs() {
const imgs=document.querySelectorAll('.my-photo');
Array.from(imgs).forEach(el=> {
if (isInSight(el)) {
loadImg(el);
}
})
}
function loadImg(el) {
if (!el.src) {
const source=el.dataset.src;
el.src=source;
}
}
這里應(yīng)該是有一個(gè)優(yōu)化的地方,設(shè)一個(gè)標(biāo)識(shí)符標(biāo)識(shí)已經(jīng)加載圖片的index,當(dāng)滾動(dòng)條滾動(dòng)時(shí)就不需要遍歷所有的圖片,只需要遍歷未加載的圖片即可。
在類似于滾動(dòng)條滾動(dòng)等頻繁的DOM操作時(shí),總會(huì)提到“函數(shù)節(jié)流、函數(shù)去抖”。
所謂的函數(shù)節(jié)流,也就是讓一個(gè)函數(shù)不要執(zhí)行的太頻繁,減少一些過(guò)快的調(diào)用來(lái)節(jié)流。
基本步驟:
function throttle(fn, mustRun=500) {
const timer=null;
let previous=null;
return function() {
const now=new Date();
const context=this;
const args=arguments;
if (!previous){
previous=now;
}
const remaining=now - previous;
if (mustRun && remaining >=mustRun) {
fn.apply(context, args);
previous=now;
}
}
}
這里的 mustRun就是調(diào)用函數(shù)的時(shí)間間隔,無(wú)論多么頻繁的調(diào)用 fn,只有 remaining>=mustRun時(shí) fn才能被執(zhí)行。
可以看出此時(shí)僅僅是加載了img1和img2,其它的img都沒(méi)發(fā)送請(qǐng)求,看看此時(shí)的瀏覽器
第一張圖片是完整的呈現(xiàn)了,第二張圖片剛進(jìn)入可視區(qū)域,后面的就看不到了~
當(dāng)我向下滾動(dòng),此時(shí)瀏覽器是這樣
此時(shí)第二張圖片完全顯示了,而第三張圖片顯示了一點(diǎn)點(diǎn),這時(shí)候我們看看請(qǐng)求情況
img3的請(qǐng)求發(fā)出來(lái),而后面的請(qǐng)求還是沒(méi)發(fā)出~
當(dāng)滾動(dòng)條滾到最底下時(shí),全部請(qǐng)求都應(yīng)該是發(fā)出的,如圖
方法三 IntersectionObserver
經(jīng)大佬提醒,發(fā)現(xiàn)了這個(gè)方法
先附上鏈接:
jjc大大:
https://github.com/justjavac/the-front-end-knowledge-you-may-dont-know/issues/10
阮一峰大大:
http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html
API Sketch for Intersection Observers:
https://github.com/WICG/IntersectionObserver
IntersectionObserver可以自動(dòng)觀察元素是否在視口內(nèi)。
var io=new IntersectionObserver(callback, option);
// 開(kāi)始觀察
io.observe(document.getElementById('example'));
// 停止觀察
io.unobserve(element);
// 關(guān)閉觀察器
io.disconnect();
callback的參數(shù)是一個(gè)數(shù)組,每個(gè)數(shù)組都是一個(gè) IntersectionObserverEntry對(duì)象,包括以下屬性:
屬性描述time可見(jiàn)性發(fā)生變化的時(shí)間,單位為毫秒rootBounds與getBoundingClientRect()方法的返回值一樣boundingClientRect目標(biāo)元素的矩形區(qū)域的信息intersectionRect目標(biāo)元素與視口(或根元素)的交叉區(qū)域的信息intersectionRatio目標(biāo)元素的可見(jiàn)比例,即intersectionRect占boundingClientRect的比例,完全可見(jiàn)時(shí)為1,完全不可見(jiàn)時(shí)小于等于0target被觀察的目標(biāo)元素,是一個(gè) DOM 節(jié)點(diǎn)對(duì)象
我們需要用到 intersectionRatio來(lái)判斷是否在可視區(qū)域內(nèi),當(dāng) intersectionRatio>0&&intersectionRatio<=1即在可視區(qū)域內(nèi)。
代碼
function checkImgs() {
const imgs=Array.from(document.querySelectorAll(".my-photo"));
imgs.forEach(item=> io.observe(item));
}
function loadImg(el) {
if (!el.src) {
const source=el.dataset.src;
el.src=source;
}
}
const io=new IntersectionObserver(ioes=> {
ioes.forEach(ioe=> {
const el=ioe.target;
const intersectionRatio=ioe.intersectionRatio;
if (intersectionRatio > 0 && intersectionRatio <=1) {
loadImg(el);
}
el.onload=el.onerror=()=> io.unobserve(el);
});
});
源自:segmentfault
聲明:文章著作權(quán)歸作者所有,如有侵權(quán),請(qǐng)聯(lián)系小編刪除。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。