前端图片分层加载详解
# 一、引言
# 1.1 编写目的
介绍图片分层加载系统架构及技术方案,结合懒加载后的总流程,梳理常见问题及相应解决方案。
# 1.2 项目背景
# 1.2.1 背景
前端优化站的功能模块之一,属于图像优化范畴。
# 1.2.2 业界客户现状
大部分客户在图片加载上没做优化,页面直接加载原图,浏览器会发起大量原图请求,导致页面加载时间过长,网络带宽消耗大。
因此,我们提出了一整套的图片优化解决方案,实现页面图片懒加载,加快客户页面加载速度,节省流量。
# 1.2.3 友商技术方案
Akamai 在图像优化上提出了三个解决方案:
- 自适应压缩
基于网络条件分层加载,提供多种压缩级别的图像资源
- 懒加载
仅加载当前浏览器视区可见的图像,当用户向下滑动时加载新图像
- 特定图像格式优化
webp、jpeg2000等图像格式可以降低负载,而不影响图片质量,但只有特定浏览器兼容
目前本方案能实现以上功能,并在懒加载方案继续优化,结合了预加载的思想,当pageload后且scroll一定时间没有触发,会触发图像加载机制,用于满足某些客户需求。
# 1.3 术语定义
小图: 分辨率不变,图像质量较低的图片资源-模糊图 小小图: 后台又对小图做了压缩处理
# 1.4 参考资料
# 二、系统架构
本套系统分为前端和后台,本文侧重于介绍前端部分。
当浏览器发起html文档请求时,后台会对该文档进行修改并返回。
先介绍下后台的html文档改写规则(仅针对图像优化功能):
在文档底部插入图片优化的js代码,配置项通过设置script节点的自定义属性来实现。
主要配置项:散列域名,是否开启懒加载,懒加载策略,是否处理
background-image
前端这边收到html文档之后,与后台交互流程如下:
# 流程图
graph TB
st(浏览器dom解析)-->op[浏览器发起图片请求]
op-->op2[后台处理并返回模糊图]
op2-->co{ pageload完成? }
co--no-->co
co--yes-->op3[扫描文档的图像资源]
op3-->op4[修改图像url地址的域名部分并作为图像新的src]
op4-->op5[浏览器发起新的图片请求]
op5-->co2{ 后台判断是否有缓存? }
co2--no-->op6[回源获取并缓存]
co2--yes-->op7[进行响应]
op6-->op7
2
3
4
5
6
7
8
9
10
11
12
# 时序图
sequenceDiagram
participant 浏览器
participant shark
participant 源站
浏览器 ->> shark:请求图像资源
Note right of shark:没有缓存,请求源站
shark ->> 源站:请求图像资源
Note right of shark:缓存图像
shark ->> shark:图像模糊化
shark -->> 浏览器:返回模糊图
Note right of 浏览器:dom解析完毕<br/>扫描文档
浏览器 ->> shark:请求图片质量标识wsq对应图像
shark ->> shark:根据wsq对原图做相应的压缩
shark -->>浏览器:返回wsq对应图片
2
3
4
5
6
7
8
9
10
11
12
13
14
或者采用另外一个方案,修改html文档中img的src,原图放在data-src中,这样可以控制哪些图片需要做分层,可解决后面提到的 根据媒体属性的渲染
问题,但是改写文档的工作量和难度都比较大
# 前端流程
原图请求通过url替换的方式,不采用ajax
# 总流程
graph TB
st(开始)-->init((初始化))
init-->obs[添加DOMContentLoaded,Load事件监听器]
obs-->DOMContentLoaded[DOMContentLoaded事件触发]
DOMContentLoaded-->cal[利用html文档下载速度来进行简单测速]
cal-->firstScreenPicSpeed[参照网速压缩比对照表得到首屏图片压缩比]
firstScreenPicSpeed-->firstScreen((扫描文档,获取图片节点))
firstScreen-->quicksort[按视距快排图片节点]
quicksort-->judgeCache[任取一模糊图的name,通过transferSize是否为0来判断本次加载是否为无缓存加载]
judgeCache-->firstScreenCal[获取视距处于首屏的图片节点]
firstScreenCal-->oripicload((进行原图加载))
oripicload-->load[Load事件触发]
load-->speedtest[采用测速算法测速,得到相应图像压缩比]
speedtest-->islazyLoad{是否懒加载}
islazyLoad--是-->lazyload[启动懒加载策略]
islazyLoad--否-->replaceOther[找到其他未进行原图加载的节点]
replaceOther-->oripicload1((进行原图加载))
lazyload-->islazycompleted{懒加载完毕?}
islazycompleted--是-->ed(结束)
islazycompleted--否-->scall{是否进行窗口滚动}
scall--是-->debounceCul[节流处理,获取满足视距条件的未进行原图加载的节点]
debounceCul-->oripicload2((进行原图加载))
oripicload2-->islazycompleted
scall--否-->mousetimeout{鼠标事件10s未触发}
mousetimeout--是-->replaceOther
mousetimeout--否-->scall
oripicload1-->ed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 初始化具体流程
graph LR
init(初始化开始)-->evalElement(解析script标签中自定义属性)
evalElement-->getHost[获取可替换图片域名]
evalElement-->getHashHost[获取散列域名]
evalElement-->getLazy[获取懒加载策略]
evalElement-->getSpeedZip[获取网速压缩比参照表]
evalElement-->getTTL[获取图片缓存时间]
getHost-->initStop(初始化结束)
getHashHost-->initStop
getLazy-->initStop
getSpeedZip-->initStop
getTTL-->initStop
2
3
4
5
6
7
8
9
10
11
12
# 原图加载具体流程
graph TB
oripicstart(原图加载开始)-->judgeEle{判断节点类型}
judgeEle--picture节点-->getpic[获取其子节点中source节点和img节点中src和srcset属性中的图片url]
judgeEle--img节点-->getsrcset[获取节点的src和srcset属性中的图片url]
judgeEle--video节点-->getposter[获取节点的poster属性中的图片url]
judgeEle--其他节点-->getother[获取节点background-image的图片url]
getpic-->getfinalurl((url替换))
getsrcset-->getfinalurl1((url替换))
getposter-->getfinalurl1((url替换))
getother-->getfinalurl1((url替换))
getfinalurl1-->newImg[new Image的方式加载资源,其url为替换后的url,可能需要设置srcset属性]
newImg-->onload[Image的onload触发]
onload-->updateEle
getfinalurl-.-hashurl[将模糊图url地址通过一定算法映射到固定的散列域名,得到新的图片url-newUrl]
hashurl-->findstorage{localstorage中是否有newUrl且压缩比一样或更低的记录}
findstorage--是-->isExpired{是否过期}
findstorage--否-->carrycur
isExpired--否-->carryhigh[取未过期的压缩比最低的记录,newUrl后带上压缩比参数]
isExpired--是-->carrycur[图片url后带上当前的压缩比]
carryhigh-->replaceEnd(url替换完毕)
carrycur-->replaceEnd
getfinalurl-->updateEle[将节点属性的图片url进行替换]
updateEle-->updatelocal[更新localstorage]
updatelocal-->isnocache{无缓存加载或localstorage中没有命中缓存?}
isnocache--否-->oripicstop[原图加载完毕]
isnocache--是-->update[更新 url-压缩比-ttl 记录]
update-->oripicstop
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 扫描文档,获取图片节点具体流程
graph LR
init(初始化节点列表)-->scanOver{文档扫描完毕?}
scanOver--否-->scan[扫描文档]
scan-->getPicEle[获取picture节点]
getPicEle-->getchild[获取子节点的src和srcset中所有图片的url]
getchild-->ishost{节点图片url的域名存在可替换图片域名?}
ishost--否-->skip[跳过该节点]
ishost--是-->addList[节点列表加入该节点]
scan-->getImgEle[获取父节点非picture的img节点]
getImgEle-->getsrc[获取节点的src或srcset属性]
getsrc-->ishost
scan-->getstyleEle[获取style中含有background-image属性且值为图片url的节点]
getstyleEle-->ishost
scan-->getcss[扫描css文档,获取含有background-image属性且值为图片url的css规则]
getcss-->getcssDom[匹配css规则得到相应节点]
getcssDom-->ishost
scan-->getvideo[获取含poster属性且值为图片url的video节点]
getvideo-->ishost
skip-->scanOver
addList-->scanOver
scanOver--是-->scanEnd(节点获取完毕)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 三、常见问题及解决方案
# 3.1 源站有做懒加载策略的
默认情况下,会出现后续请求原图时,后台返回小图的问题。
因为访问的域名没有做更改,后台默认小图处理。
此外,还会发起更多的请求
- img中没有src
pageload后扫描文档并拿不到src 故不会发起获取原图的异步请求
- img中src为源站的小图
初次访问小图,后台会返回一张小小图,pageload之后我们扫描文档会去拿小图,加上懒加载策略触发又请求的图片,我们会发三次请求
这个由客户配置,客户自己有做懒加载可以不选择我们的分层加载功能,所以这边我们无需考虑
# 3.2 css和style中background-image是否进行处理
目前前端这边可以做到扫描文档获取到图片地址,对于伪类css(如 div:hover{background:xxx})还得做额外处理
需要测试下伪类
对于 background-image:inherit
(使用父节点的bg-image)不做处理
# 3.3 根据媒体属性的渲染
<img>
的srcset属性和css的image-set()<picture>
source、img子节点
初次返回小图,pageload之后前端这边不知道应该请求哪张图片,不知道有没有办法获取渲染的是哪张图
可以通过img节点的currentSrc拿到值。(注:picture的也是去拿img的currentSrc)
但还是有新的问题,这个是响应式的,界面变化又会去拿新的图片地址了
故需要做img节点currentSrc值变化监听
目前没有找到直接监听currentSrc的方法,但是一般currentSrc的变化都是由窗口大小变化引起的,故需要节流监听下window.resize,判断currentSrc有变化没。
后面发现,即使知道图片地址变化了,也并不能通过img改src的方式替换图片,因为srcset存在的时候src是无效的。
故解法只有在pageload的时候,把srcset中图片的地址做替换
# 3.4 和懒加载结合的策略
加载全部小图,pageload后加载全部大图,不做懒加载
加载全部小图,滑动懒加载大图
加载首屏小图,滑动懒加载大图
目前是加载全部小图和首屏大图
- loaded时开启懒加载,滑动懒加载大图,load前滑动不做处理
- domcontentloaded时开启懒加载,未load前都是加载的原图。
# 3.5 原始图片较小,第二次不做加载
采用ajax请求,根据响应值长度判断,响应值长度小于某个值 说明是错误请求或者不做加载
最后通过 window.URL.createObjectURL() 插入图片。但是该方案不能解决问题3
新方案:第一次发过来若是原图,设置缓存策略,后续请求,返回307重定向到原来地址,这时候本地有缓存就不会再发请求了
# 3.6 根据网络速度,在第二次按照一定比例加载高清图
如何测速?
利用Resource Timing API
,现代浏览器大部分支持,详见caniuse,
若不支持(safari11以下),则采用navigation timing
该api必须在page load
后才能使用。
具体网络带宽估计算法需要一定策略来评判
//计算浏览器加载该文档的时间
var nav = performance.getEntriesByType('navigation')[0]
console.log(nav.responseEnd-nav.responseStart,nav.transferSize,nav.transferSize/(nav.responseEnd-nav.responseStart)+"KB/s")
2
3
facebook的测速算法:
前面资源还有做一定的过滤和排序(按responseEnd),
performance.getEntriesByType('resource').filter(v=>v.responseEnd-v.responseStart>30).reduce((sum,cur)=>0.5*(cur.transferSize / (cur.responseEnd - cur.responseStart || 1))+0.5*sum,0)
facebook带宽利用率:所有响应时间和及响应间隔和/所有响应时间
我们假设网络速度恒定(不抖动),给出以下数据(响应开始时间,响应结束时间,资源大小)。我们会计算每一段的响应速度,最后取最大值。这样解决了之前只计算html文档存在的问题,并且该方案的结果也兼容了html文档计算。
这边假设了网络不抖动。当出现网络抖动情况,会出现前面下载速度和后面不一致情况,但是由于我们后面也会做计算,在数据量足够的情况下是趋于稳定的。
推荐数据结构:线段树+扫描线 O(nlogn)
暴力解法:O(n²)
# 算法流程
- 数据结构体
{
name: v.name,//标识而已
start: v.responseStart,
end: v.responseEnd,
size: v.transferSize,
//KB/S 新算法中不用该参数
speed: v.transferSize / (v.responseEnd - v.responseStart || 1)
}
2
3
4
5
6
7
8
- 过滤、按start值升序排序
剔除小数据,保留满足以下条件数据--》小数据会导致出现过大的网速计算结果,剔除小数据后对总体结果影响小,
v.transferSize > 100 && (v.responseEnd - v.responseStart) > 10 && v.responseStart < loadTime
分组,将响应时间无连续的分组
计算每组的每条响应的速度(具体计算见如下),并计算整组均值、方差、最大值
- 3.1 将每条响应的start,end放入numArr
- 3.2 numArr去重,升序排序
- 3.3 将每条响应的时间区间按numArr值分割,当其他响应有重复的区间,该条响应的该时间区间会乘以重复的数量(包括自己)
- 3.4 将每段时间区间相加,形成该响应的实际响应时间,用size处于该时间则为该响应的实际速度
- 3.5 计算每条响应的速度
- 根据size做加权平均,得到每组带宽估算值。
- 比较每组数据的结果,取最大值
目前仍存在问题:小数据短时间,当×上一定并发时,会使数据过大。
size加权平均,弱化小数据的影响
算法效率过低O(n³):需要改进
目前O(n²),仍可以剪枝优化
# 3.7 图片合并,第一次是模糊图片,第二次是补充信息,合并后变成原图
较难,目前不研究
# 3.8 存在缓存,还是会分层加载,体验不好
简单的将过程放在domcontentloaded时并不能解决问题,会导致初次加载或者无缓存加载,可能不触发分层机制(模糊的src未下完又直接被替换了)。
分层加载逻辑修改为:
- html的原始文档,img仅做占位
<!-- 初次加载的文档 -->
<figure>
<noscript>
<img src="https://pic4.zhimg.com/50/1355440786fffad694ed56130dbbc0bc_hd.jpg" data-rawheight="667" data-rawwidth="1000" class="origin_image zh-lightbox-thumb"
width="1000" data-original="https://pic4.zhimg.com/1355440786fffad694ed56130dbbc0bc_r.jpg" />
</noscript>
<img src="data:image/svg+xml;utf8,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='1000'%20height='667'>< /svg>"data-rawheight="667" data-rawwidth="1000" class="origin_image zh-lightbox-thumb lazy" width="1000" data-original="https://pic4.zhimg.com/1355440786fffad694ed56130dbbc0bc_r.jpg"
data-actualsrc="https://pic4.zhimg.com/50/1355440786fffad694ed56130dbbc0bc_hd.jpg" />
</figure>
2
3
4
5
6
7
8
9
- domcontentload后,js将img用div替换,并开始模糊和高清图片的加载
<figure>
<noscript>
<img src="https://pic4.zhimg.com/50/1355440786fffad694ed56130dbbc0bc_hd.jpg" data-rawheight="667" data-rawwidth="1000" class="origin_image zh-lightbox-thumb"
width="1000" data-original="https://pic4.zhimg.com/1355440786fffad694ed56130dbbc0bc_r.jpg" />
</noscript>
<span>
<!--VagurImage中设置了背景色-->
<div class="VagurImage" data-src="https://pic4.zhimg.com/50/1355440786fffad694ed56130dbbc0bc_hd.jpg" style="width:654px;height:980.51px">
<!--设置了高斯模糊和透明度-->
<div class="VagueImage-mask is-active">
::before
::after
</div>
</div>
</span>
</figure>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 模糊图片下载完毕,设置background-image
<!-- 文档其他地方不变不变-->
<div class="VagueImage-mask is-active" background-image="url('https://pic4.zhimg.com/50/1355440786fffad694ed56130dbbc0bc_60w.jpg')">
::before
::after
</div>
2
3
4
5
- 高清图下载完毕
<figure>
<noscript>
<img src="https://pic4.zhimg.com/50/1355440786fffad694ed56130dbbc0bc_hd.jpg" data-rawheight="667" data-rawwidth="1000" class="origin_image zh-lightbox-thumb"
width="1000" data-original="https://pic4.zhimg.com/1355440786fffad694ed56130dbbc0bc_r.jpg" />
</noscript>
<img src="https://pic4.zhimg.com/1355440786fffad694ed56130dbbc0bc_hd.jpg" data-rawheight="667" data-rawwidth="1000" class="origin_image zh-lightbox-thumb lazy" width="1000" data-original="https://pic4.zhimg.com/1355440786fffad694ed56130dbbc0bc_r.jpg"
data-actualsrc="https://pic4.zhimg.com/50/1355440786fffad694ed56130dbbc0bc_hd.jpg" />
</figure>
2
3
4
5
6
7
8
js代码
//还是放在domcontentloaded执行
document.addEventListener("DOMContentLoaded",func,false)
//func
//采用两次Image,若img直接加载模糊图的话,只需imgLarge即可
let smallImage = document.getElementById('small-img')
let img = new Image()
img.src=smallImage.src
//由于smallImage的已经load了,这边我们利用缓存再加载同个图,会去拿缓存触发onload
img.onload = function(){
//下图加载完毕
}
let imgLarge = new Image();
imgLarge.src = smallImage.getAttribute("originSrc")
imgLarge.onload = function(){
smallImage.src = imgLarge.src
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
但是目前该做法有个问题,就是原图获取是在domcontentloaded的时候进行,此时还未进行测速,或者说此时进行测速用我们的测速算法是不准的(资源下载过程中
performance.getEntries
是拿不到该资源的)。 一个想法是对于未测速时,都加载原图
# 3.9 带网络参数,不会命中原图已有本地缓存
问题详细描述:本地已缓存原图a.jpg
,本地通过网络计算需要去请求低清图片a.jpg?q=50
,这样就浪费了原来的原图缓存了。
解法:不同比例图片load后,将url-TTL
放到localstorage中。每次网络计算完后,先查询缓存中有没有更高清的且未过期的图片,有的话选更高清的进行请求,不对localstorage做处理 ;否则请求相应网络状态的图片,onload后保存或更新localstorage。
注1:每种清晰度图片的TTL可配置,通过script节点自定义属性设置。
注2:本地只有低清缓存不考虑请求是因为我们当前网络状态算还行了,不需要用低清的,否则多次清晰度切换会对用户造成视觉上影响。
当前这个问题也可以通过blur+动画
解决,需要产品评估下。
存在问题:强刷页面或者disable cache
,请求图片得到响应后,cache-control
本地缓存时间会重新计算。相应的我们localstorage的TTL也要进行修改
解决方案:这边我们主要就是判断图片是否为无缓存请求,当为无缓存请求,img.onload后需要对localstorage进行更新。
至于怎么判断是否为无缓存,就用performance.getEntriesByName('当前加载图片的url')
结果是否满足某些规则来判断
# 3.10 原图访问会做url替换,当前不是用h2 而是用域分片,当两次图片请求不是同个域时会不走缓存
方法1: 将图片链接与域名做map映射
url->host
,保存在localstorage,下次访问会先查一遍,没有的话就随机访问并保存在localstorage
方法2: 利用算法将图片url映射到某个MOD值(length为散列域名列表大小) 需要保证每个计算结果值不变且映射每个域名的概率一致 js 名称的每个字符的ascii码值相加再mod length
# 3.11 JavaScript被禁用 分层加载拿不到原图
目前不考虑
# 3.12 透明遮罩,动画让图片切换变得自然
.VagueImage-mask {
z-index: 1;
opacity: 0;
background-size: cover;
background-position: 50%;
background-repeat: no-repeat;
transition: opacity .3s ease-in
}
2
3
4
5
6
7
8
不过 由于我们不是提供的小分辨率图再放大,而是提供同分辨率的模糊图,这边就不进行透明遮罩处理了。
# 3.13 图片节点扫描完毕后,后续通过js动态增加的Image节点,若图片src满足分层规则,后台会返回模糊图片
- 定时扫描 无任何优化时,jd 3k多节点 每次扫描耗时20ms左右,后面可能可以做增量优化。 但有个问题就是需要一直去做定时扫描,对客户网页性能有影响
技术难度低
- 元素监听
# 通过Object.defineProperty
去监听src的set
try {
Object.defineProperty(HTMLImageElement.prototype, 'src', {
enumerable: false,
configurable: true,
get: function () {
return this.getAttribute('src')
},
set: function (newval) {
//属于可替换域的
if (newval.indexOf("data:image") === -1 && Util.inlist(newval, settings.imgHostList)) {
this.setAttribute('src', Util.getNewUrlFromCache(newval, MAX_QUALITY).url);
} else {
this.setAttribute('src', newval);
}
}
});
//TODO srcset的defineProperty
} catch (error) {
console.error('Image Element set error', error)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
兼容IE8,IE8支持对html元素对象进行 defineProperty ,enumerable 需要设置为false
# document.write方法重载
注:要么必须支持js异步加载让document.write延后执行,要么把图片优化脚本放在head中
以下步骤仅测试图片优化功能用,暂不兼容js异步加载功能,document.write方法也是等图片优化脚本加载完毕后执行
- 获得当前执行脚本节点-currentScript
作为标志位,内容将插入在该节点之前,此处命中为节点 CUR
- 找到CUR的父节点CURP,创建一个CURP.tagName的节点CURC,其innerHTML为所插入内容
- 遍历节点,找到满足条件的图片节点并进行host替换
- 将CURC的所有子节点insertBefore(CUR)
该做法获取不到css渲染
技术难度较高
# 3.14 图片节点寻找完毕后,需要进行一个视距排序,这样当没开启懒加载时才会优先加载首屏图像
有空写
# 四、详细设计
# 4.1 获取script自定义数据
<script data-host="xxx" id="speed">
let ele = document.currentScript
console.log(ele.attributes["data-host"].nodeValue)
</script>
2
3
4
这样可以获取到本script节点的自定义数据
# 4.2 注册window.load事件监听器
function addLoadEvent(func) {
if (window.addEventListener) {
window.addEventListener("load", func, false)
} else {
//低版ie兼容
if (window.attachEvent) {
window.attachEvent("onload", func)
}
}
}
addLoadEvent(streamFunc);
2
3
4
5
6
7
8
9
10
11
# 4.3 计算用户网络状况
//计算浏览器加载该文档的时间
var nav = performance.getEntriesByType('navigation')[0]
console.log(nav.responseEnd-nav.responseStart,nav.transferSize,nav.transferSize/(nav.responseEnd-nav.responseStart)+"KB/s")
2
3
# 4.4 节点获取
# 4.4.1 获取img元素
let imgDOMList = document.getElementsByTagName("img");
[...imgDOMList].forEach((dom) => {
//其他处理
//含srcset属性做额外标识
})
2
3
4
5
# 4.4.2 获取style中的background
let styleDOMList = document.querySelectorAll("[style*=background]");
[...styleDOMList].filter(v=>v.nodeType == 1 && v.style.backgroundImage).forEach((dom) => {
//其他处理
})
2
3
4
# 4.4.3 获取css中的background
//获取css中的background,
document.styleSheets.filter(v=>{
//根据v.href过滤第三方css
return
}).forEach(v=>{
let rules = v.cssRules || v.rules
rules.forEach(v=>{
let sel = v.selectorText
let domList = document.querySelectorAll(sel);
if(v.style.backgroundImage){
//满足条件的操作
}
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
完整版本
queryImgNodeList: function () {
var list = []
//获取picture节点
; (function () {
var pictureList = document.getElementsByTagName("picture");
for (var i = 0; i < pictureList.length; i++) {
//仅img子节点存在时进行下一步判断,一般都会有img子节点
if (pictureList[i].getElementsByTagName("img").length) {
var urlList = []
var nodeList = pictureList[i].querySelectorAll("source,img")
for (var j = 0; j < nodeList.length; j++) {
var srcset = nodeList[j].getAttribute("srcset")
var src = nodeList[j].getAttribute("src")
if (srcset) {
urlList = urlList.concat(getUrlListInSrcset(srcset))
} else if (src && src.indexOf("data:image") === -1) {
urlList.push(src)
}
}
if (urlList.length > 0 && inlist(urlList, settings.imgHostList)) {
list.push(new WsNode('picture', pictureList[i]))
}
}
}
})()
// 获取video节点
; (function () {
var videoList = document.getElementsByTagName("video");
for (var i = 0; i < videoList.length; i++) {
var poster = videoList[i].getAttribute("poster")
if (poster && poster.indexOf("data:image") === -1 && inlist(poster, settings.imgHostList)) {
list.push(new WsNode('video', videoList[i]))
}
}
})()
// 获取img节点
; (function () {
var imgList = document.getElementsByTagName("img");
for (var i = 0; i < imgList.length; i++) {
if (imgList[i].parentNode.nodeName !== "PICTURE") {
var urlList = []
var srcset = imgList[i].getAttribute("srcset")
var src = imgList[i].getAttribute("src")
if (srcset) {
urlList = getUrlListInSrcset(srcset)
} else if (src && src.indexOf("data:image") === -1) {
urlList.push(src)
}
if (urlList.length > 0 && inlist(urlList, settings.imgHostList)) {
list.push(new WsNode('img', imgList[i]))
}
}
}
})()
/*
//获取style*background-image
; (function () {
var styleList = document.querySelectorAll("[style]");
//ie8 not support [style*=background]
for (var i = 0; i < styleList.length; i++) {
var cur = styleList[i];
if (cur.nodeType == 1 && cur.style.backgroundImage) {
var url = getBackgroundImageUrl(cur.style.backgroundImage);
if (url && inlist(url, settings.imgHostList)) {
list.push(new WsNode('style', cur))
}
}
}
})()
//获取css background-image
; (function () {
for (var i = 0; i < document.styleSheets.length; i++) {
var href = document.styleSheets[i].href;
//过滤不满足规则的css外链
if (href && !inlist(href, settings.cssHostList)) {
continue
}
var rules = document.styleSheets[i].cssRules || document.styleSheets[i].rules
//遍历每条规则
for (var j = 0; j < rules.length; j++) {
var url = getBackgroundImageUrl(rules[j].style.backgroundImage)
if (url && inlist(url, settings.imgHostList)) {
var eles = document.querySelectorAll(rules[j].selectorText)
for (var k = 0; k < eles.length; k++) {
list.push(new WsNode('css', eles[k], url))
}
}
}
}
})()*/
//获取background-image的节点
; (function () {
var bglist = document.querySelectorAll("*")
for (var i = 0; i < bglist.length; i++) {
if (bglist[i].nodeType === 1) {
var url = getBackgroundImageUrl(getStyleBackgroundImage(bglist[i]))
if (url && inlist(url, settings.imgHostList)) {
list.push(new WsNode('bg', bglist[i], url))
}
}
}
})()
return list
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# 4.4.4 获取picture节点
let imgDOMList = document.getElementsByTagName("picture");
imgDOMList.forEach(v=>v.childList.forEach(c=>c))
2
为避免重复计算,先扫描picture
,再扫描img
# 4.5 懒加载
# 方法1 IntersectionObserver
兼容性差,开发量低
# 方法2 scroll+window.requestAnimationFrame()
兼容性高,开发量还行
# 4.6 图片地址替换
// 判断是否为可替换域名
function judgeHost(url){
}
// 相对路径转绝对路径(IE6, IE7)无效
function directlink(url){
var a = document.createElement('a');
a.href = url;
return a.href;
};
// 根据散列域名、网速替换url
function replace(originUrl,hashHost,networkSpeed){
}
2
3
4
5
6
7
8
9
10
11
12
13
14