当前位置:   article > 正文

vue2.0实现瀑布流布局+虚拟列表,附带源码及效果预览视频_vue 瀑布流

vue 瀑布流

效果预览

vue2.0瀑布流+虚拟列表效果预览

目录

效果预览

一、什么是瀑布流布局

二、什么是虚拟列表

三、需求前提

四、源码


一、什么是瀑布流布局

瀑布流布局是现代浏览器常见布局之一,是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。

瀑布流布局通常用于电商、视频、图片网站等,例如抖音、花瓣、小红书等。其优点本文不做介绍。

二、什么是虚拟列表

虚拟列表是一种优化长列表性能的手段。

它能够做到节省内存、提升页面流畅性、提升用户体验等,通俗来讲就是只在你能看见的地方渲染元素,你看不见的地方部分渲染或者不渲染。

三、需求前提

实现瀑布流布局+虚拟列表,首先我的这个案例有一个大前提,即拿到的图片数据均为已知宽高!

1. 纯css还是js?

纯css实现瀑布流有点麻烦,不管是用column-count也好grid也好flex也好反正都行,但是它无法实现虚拟列表啊,所以毙掉这种方式,另外每一个元素都有自己的位置,那就需要绝对定位,因此肯定是选择js+绝对定位的方式。

还有,用原生js实现不会受到框架的限制,方便进行拓展。

2. translate还是left top?

translate不会引起重排而left top会,还能开启硬件加速,性能肯定强于left top,translate赢麻了。

3.什么时候才渲染真实dom?

当不存在虚拟列表时,dom元素排列如下图所示:

上刻线与下刻线, 是决定元素是否被渲染的重要参照,根据元素与参照的位置,我们得出以下结论:

  • 情况①元素处于上刻线之上,见图索引0 1 2 3 4 6
  • 情况②元素与上刻线交叉,见图索引7 5
  • 情况③元素处于上刻线与下刻线之间,见图索引8 9 10 11 12 13
  • 情况④元素与下刻线交叉,见图索引14 15 16
  • 情况⑤元素处于下刻线之下,见图索引17 18 19

使用虚拟列表后,上述所列情况中的①⑤不会进行渲染,其余情况均为渲染。

4. 需要对虚拟列表设置startIdx和endIdx?

需要,生成位置表(下文均有介绍)后,比起直接循环整个位置表,当位置表中记录了1000条甚至更多条记录时,startIdx和endIdx的存在明确了循环区间,极大的缩短循环次数,减少页面留白时间,提升性能。

5. 上下滚动时,如何进行添加、删除dom?

不管怎么滚动,都要做两件事情。

第一件事:

根据不同的滚动方向,添加dom元素

  • 滚动方向向下时,从endIdx + 1 处开始循环位置表到位置表尾,不断的添加dom,直到找到一个元素的位置属于情况⑤时停止添加。
  • 滚动方向向上时,从startIdx - 1 处倒序循环到索引0,不断的添加dom,直到找到一个元素的位置属于情况①时停止添加。

第二件事:

循环位置表,从startIdx至endIdx(如图6-1中的5与16),将对应索引元素的位置与上述第6点中提到的情况①⑤进行比较, 此时会出现:

  • 属于情况①⑤,检查已渲染表中是否 含有 该项,有即删除。
  • 不属于情况①⑤,检查已渲染表中是否 没有 该项,无则添加。

循环结束后更新startIdx、endIdx、已渲染表,下次发生滚动事件时继续重复这套逻辑。

解释下为何不直接循环已渲染表进行元素的删除?如果是这样,那就会出现一个bug:如图8-2所示,虚线框为上次视口位置,实线框为当前视口位置,如果按照直接循环已渲染表的方式,那么就只有删除dom这一种情况,于是图中索引6的dom应被删除,结束后更新startIdx、endIdx、已渲染表,那么如果此刻向上滚动,回到上次视口位置,startIdx将从5开始0结束进行寻找,索引为6的dom明明满足,却没有执行添加dom,造成了该位置缺失dom,从而形成了bug。

流程图:

四、源码

页面vue文件代码如下:

  1. <template>
  2. <div class="container">
  3. <div class="water-fall-container">
  4. <div class="box">
  5. <div class="loading">加载中...</div>
  6. </div>
  7. </div>
  8. <div class="to-top" @click="onclick"></div>
  9. </div>
  10. </template>
  11. <script>
  12. import WHList from './data'
  13. import './debounce'
  14. import'./throttle'
  15. export default {
  16. name:'WaterfallVirtual',
  17. data(){
  18. return{
  19. waterfallContainerDom:'',
  20. containerDom:'',
  21. loadingDom:'',
  22. canvas:'',
  23. getTextLineHeightCtx:'',
  24. list:[],
  25. page:1,
  26. pageSize:50,
  27. hasNextPage:true,
  28. gap:16,
  29. columnWidth:0,
  30. containerTop:0,
  31. domDataList:[],
  32. positionList:[],
  33. renderMap:{},
  34. startIdx:0,
  35. endIdx:0,
  36. screenOffset:'',// 偏移量
  37. isLoadNextPage:false,// 是否加载下一页数据
  38. testList:[
  39. '《蜡笔小新》是一部于1992年出品的日本家庭搞笑动画片,该片主要由本乡满、原惠一、武藤裕治导演,日本朝日电视台于1992年4月13日播映了第一集。至今仍在播出。',
  40. '看过蜡笔小新的人,都知道,他有一个很逗的老爸——野原广志。 这位胡须浓密、面条脸的野原广志先生是一名普通的上班族,在车上享受着和周围女子相互挤攘的感觉(偶尔旁边是大叔也很囧)。',
  41. '脚臭的广志,小气的美伢,淘气的小新……',
  42. ],
  43. imgList:[
  44. "https://img2.baidu.com/it/u=3600821550,221281285&fm=253&fmt=auto&app=120&f=JPEG?w=889&h=500",
  45. "https://img0.baidu.com/it/u=2506471502,1373494428&fm=253&fmt=auto&app=120&f=JPEG?w=530&h=500",
  46. "https://img2.baidu.com/it/u=824566914,3863846826&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=500"
  47. ],
  48. resizeCallback:null,
  49. lastOffsetWidth:'',
  50. lastScrollNumY:0, // 上次滚动距离Y
  51. lastScrollNumX :0,// 上次滚动距离X
  52. scrollDirection:1,// 上次滚动方向 向下 为 1,向上为 -1
  53. }
  54. },
  55. methods:{
  56. getList(){// 获取数据
  57. return new Promise(resolve => {
  58. const start = (this.page - 1) * this.pageSize
  59. const nextList = WHList.slice(start, start + this.pageSize)
  60. this.hasNextPage = !!nextList.length
  61. this.list = this.page === 1 ? nextList : this.list.concat(nextList)
  62. setTimeout(() => {
  63. resolve(nextList)
  64. }, this.page === 1 ? 0 : 2000) // 模拟发送请求
  65. })
  66. },
  67. computeDomData(list, startRenderIdx = 0){// 计算数据形成 排序表
  68. const tempDomDataList = []
  69. for (let i = 0; i < list.length; i++) {
  70. const param = {
  71. idx: startRenderIdx + i,
  72. img:this.imgList[Math.trunc(Math.random() * 3)],
  73. columnIdx: 0,
  74. width: this.columnWidth,
  75. height: list[i].h * this.columnWidth / list[i].w,
  76. left: 0,
  77. top: 0,
  78. text: this.testList[Math.trunc(Math.random() * 3)],
  79. lineHeight: 74,// 根据css设置的值计算得到
  80. }
  81. // 排序,第一项必定是长度最短的一列
  82. this.positionList.sort((a, b) => a.columnHeight - b.columnHeight)
  83. param.columnIdx = this.positionList[0].columnIdx
  84. param.left = (param.columnIdx - 1) * (this.gap + this.columnWidth)
  85. param.top = this.positionList[0].columnHeight
  86. const canvas = document.createElement('canvas')
  87. this.getTextLineHeightCtx = canvas.getContext('2d')
  88. this.getTextLineHeightCtx.font = '16px sans-serif'
  89. // css 样式表设置了 纵坐标的12px内边距,要加上
  90. param.lineHeight = this.getTextLineHeightCtx.measureText(param.text).width + 24 > this.columnWidth ? 98 : 78
  91. param.height += param.lineHeight
  92. this.positionList[0].columnHeight += param.height + this.gap
  93. tempDomDataList.push(param)
  94. }
  95. this.domDataList = this.domDataList.concat(tempDomDataList)
  96. // 设置容器高度
  97. this.positionList.sort((a, b) => a.columnHeight - b.columnHeight)
  98. this.containerDom.style.height = this.positionList[this.positionList.length - 1].columnHeight + 32 + 'px'
  99. },
  100. renderDomByDomDataList(startRenderIdx = 0){// 根据元素列表进行渲染
  101. if (!this.domDataList.length) return
  102. const tempRenderMap = {}
  103. let topIdx = startRenderIdx
  104. let bottomIdx = startRenderIdx
  105. // 处于这两条线之间的元素将被渲染进容器
  106. for (let i = startRenderIdx; i < this.domDataList.length; i++) {
  107. const { idx } = this.domDataList[i]
  108. const { overTopLine, underBottomLine } = this.checkIsRender(this.domDataList[i])
  109. const dom = this.containerDom.querySelector(`#item_${idx}`)
  110. if (overTopLine || underBottomLine) {
  111. dom?.remove()
  112. continue
  113. }
  114. topIdx = topIdx < idx ? topIdx : idx
  115. bottomIdx = bottomIdx < idx ? idx : bottomIdx
  116. if (dom) {
  117. tempRenderMap[idx] = this.createDom(dom, this.domDataList[i])
  118. } else {
  119. tempRenderMap[idx] = this.createDom(document.createElement('div'), this.domDataList[i])
  120. this.containerDom.append(tempRenderMap[idx])
  121. }
  122. }
  123. const keys = Object.keys(Object.assign(this.renderMap, tempRenderMap))
  124. this.startIdx = +keys[0]
  125. this.endIdx = +keys[keys.length - 1]
  126. },
  127. checkIsRender(params){// 计算元素是否符合渲染条件
  128. const { top, height } = params
  129. const y = top + height + this.containerTop
  130. // 1个视口的数据再快速滚动滚动条时大概率会有加载项,不妨扩大到上下各0.5个视口,共2个视口内的数据,这样就比较丝滑了,这里也是自由发挥
  131. const topLine = this.waterfallContainerDom.scrollTop - this.screenOffset
  132. const bottomLine = this.waterfallContainerDom.scrollTop + this.waterfallContainerDom.offsetHeight + this.screenOffset
  133. // 是否在上线之上
  134. const overTopLine = topLine > y
  135. // 是否在下线之下
  136. const underBottomLine = top > bottomLine
  137. return{
  138. overTopLine,
  139. underBottomLine,
  140. }
  141. },
  142. createDom(dom, param){// 创建瀑布流每一项 dom元素
  143. dom.classList.add('waterfall-item')
  144. dom.style.width = param.width + 'px'
  145. dom.style.height = param.height + 'px'
  146. dom.style.transform = `translate(${param.left}px, ${param.top}px)`
  147. dom.id = `item_${param.idx}`
  148. // <div class="main">${param.idx}</div>
  149. // <div class="main">${param.idx}</div>
  150. dom.innerHTML = `
  151. <image class="main" src="${param.img}" alt=""/>
  152. <div class="footer" style="height: ${param.lineHeight}px">
  153. <div class="text">${param.idx}--${param.text}</div>
  154. <div class="info">@脆脆土豆条 -《蜡笔小新》</div>
  155. </div>`
  156. return dom
  157. },
  158. getColumnNum(boxWidth){// 根据容器宽度获取显示列数(自由发挥)
  159. if (boxWidth >= 1600) return 5
  160. else if (boxWidth >= 1200) return 4
  161. else if (boxWidth >= 768 && boxWidth < 1200) return 3
  162. else return 2
  163. },
  164. computeColumnWidth(){// 计算瀑布流每一列列宽
  165. // 首先计算应呈现的列数
  166. const columnNum = this.getColumnNum(window.innerWidth)
  167. const allGapLength = this.gap * (columnNum - 1)
  168. this.columnWidth = (this.containerDom.offsetWidth - allGapLength) / columnNum
  169. },
  170. initPositionList(){// 重置瀑布流每一列数据
  171. this.positionList = []
  172. // 首先计算应呈现的列数
  173. for (let i = 0; i < this.getColumnNum(window.innerWidth); i++) {
  174. this.positionList.push({
  175. columnIdx: i + 1,
  176. columnHeight: 0
  177. })
  178. }
  179. },
  180. updateDomPosition(direction = 1){// 当滚动条滚动时,更新容器内的 每一项 元素是 插入 还是 删除
  181. const tempRenderMap = {}
  182. console.log(this,'updateDomPosition',this.endIdx)
  183. for (let i = this.startIdx; i <= this.endIdx; i++) {// 检查已渲染列表中的元素,不符合条件删除元素,反之插入元素
  184. if(!this.domDataList[i]) return
  185. const { overTopLine, underBottomLine } = this.checkIsRender(this.domDataList[i])
  186. if (overTopLine || underBottomLine) {
  187. this.renderMap[i]?.remove()
  188. } else if (this.renderMap[i]) {
  189. tempRenderMap[i] = this.renderMap[i]
  190. } else {
  191. tempRenderMap[i] = this.createDom(document.createElement('div'), this.domDataList[i])
  192. this.containerDom.append(tempRenderMap[i])
  193. }
  194. }
  195. // 向上
  196. if (direction < 0) {
  197. for (let i = this.startIdx - 1; i >= 0; i--) {
  198. const { overTopLine } = this.checkIsRender(this.domDataList[i])
  199. if (overTopLine) break
  200. tempRenderMap[i] = this.createDom(document.createElement('div'), this.domDataList[i])
  201. this.containerDom.append(tempRenderMap[i])
  202. }
  203. } else { // 向下
  204. for(let i = this.endIdx + 1; i < this.domDataList.length; i++) {
  205. const { underBottomLine } = this.checkIsRender(this.domDataList[i])
  206. // 只要找到Bottom在下线之下的立即停止
  207. if (underBottomLine) break
  208. tempRenderMap[i] = this.createDom(document.createElement('div'), this.domDataList[i])
  209. this.containerDom.append(tempRenderMap[i])
  210. }
  211. }
  212. this.renderMap = tempRenderMap
  213. const keys = Object.keys(this.renderMap)
  214. this.startIdx = +keys[0]
  215. this.endIdx = +keys[keys.length - 1]
  216. },
  217. resizeFn(){
  218. this.computeColumnWidth()
  219. // 如果宽度发生变化时,若列宽是一致的不用处理
  220. if (this.lastOffsetWidth !== window.innerWidth && this.columnWidth === this.domDataList[0]?.width) return
  221. this.lastOffsetWidth = window.innerWidth
  222. this.initPositionList()
  223. this.domDataList = []
  224. this.renderMap = {}
  225. this.computeDomData(this.list, 0)
  226. this.renderDomByDomDataList(0)
  227. },
  228. resize:window.debounce(function(){// 窗口变化事件
  229. console.log('resize')
  230. if (this.isLoadNextPage) {// 加载数据时发生了视口变化,保存回调
  231. this.resizeCallback = this.resizeFn()
  232. return
  233. }
  234. this.resizeFn()
  235. }, 150),
  236. handleScroll:window.throttle(async function(){// 窗口滚动事件
  237. this.waterfallContainerDom.scrollTop >= window.innerHeight ? this.gotoTopDom.classList.add('active') : this.gotoTopDom.classList.remove('active')
  238. this.scrollDirection = this.waterfallContainerDom.scrollTop - this.lastScrollNumY >= 0 ? 1 : -1
  239. this.lastScrollNumY = this.waterfallContainerDom.scrollTop
  240. this.updateDomPosition(this.scrollDirection)
  241. if (this.isLoadNextPage || !this.hasNextPage) return false
  242. if (this.waterfallContainerDom.scrollTop + this.waterfallContainerDom.offsetHeight >= this.waterfallContainerDom.scrollHeight * 0.85) {
  243. this.isLoadNextPage = true
  244. this.loadingDom.classList.add('active')
  245. this.page += 1
  246. const list = await this.getList()
  247. this.isLoadNextPage = false
  248. this.loadingDom.classList.remove('active')
  249. // 加载数据期间发生了视口变化时,执行一次回调
  250. if (this.resizeCallback) {
  251. this.resizeCallback()
  252. this.resizeCallback = null
  253. } else {
  254. // 节点信息排列完毕后进行渲染
  255. const startIdx = (this.page - 1) * this.pageSize
  256. this.computeDomData(list, startIdx)
  257. this.renderDomByDomDataList(startIdx)
  258. }
  259. }
  260. }, 150),
  261. onclick(){// 渠道顶部
  262. this.waterfallContainerDom.scrollTo({
  263. left: 0,
  264. top: 0,
  265. behavior: "smooth"
  266. })
  267. },
  268. async getData(){
  269. this.computeDomData(await this.getList(), 0)
  270. this.renderDomByDomDataList(0)// 节点信息排列完毕后进行渲染
  271. }
  272. },
  273. mounted() {
  274. this.waterfallContainerDom = document.querySelector('.water-fall-container')
  275. this.screenOffset =this.waterfallContainerDom.offsetHeight / 2
  276. this.containerDom = document.querySelector('.box')
  277. this.loadingDom = document.querySelector('.loading')
  278. this.gotoTopDom = document.querySelector('.to-top')
  279. this.lastOffsetWidth = window.innerWidth
  280. this.waterfallContainerDom.addEventListener('scroll', ()=>{// 添加滚动事件监听器
  281. console.log('滚动事件触发');
  282. this.handleScroll()
  283. });
  284. window.addEventListener('resize', ()=>{// 添加滚动事件监听器
  285. console.log('视窗大小变化');
  286. this.resize()
  287. });
  288. this.computeColumnWidth()
  289. this.initPositionList()
  290. this.getData()
  291. },
  292. created(){
  293. this.$bus.emit('title', '虚拟列表+瀑布流');
  294. },
  295. }
  296. </script>
  297. <style lang="less">
  298. #app{
  299. width: 100%;
  300. height: 100vh;
  301. display: flex;
  302. }
  303. html{
  304. overflow: hidden;
  305. }
  306. .container{
  307. height: 100%;
  308. flex-grow: 1;
  309. flex-shrink: 0;
  310. padding-top: 0px;
  311. }
  312. .to-top{
  313. position: fixed;
  314. right: 40px;
  315. bottom: 40px;
  316. cursor: pointer;
  317. transform: scale(0);
  318. transition: transform .15s;
  319. width: 60px;
  320. height: 60px;
  321. display: flex;
  322. align-items: center;
  323. justify-content: center;
  324. border-radius: 50%;
  325. background-color: #f8f8f8;
  326. color: tomato;
  327. font-size: 32px;
  328. }
  329. .to-top.active{
  330. transform: scale(1);
  331. }
  332. .loading{
  333. height: 32px;
  334. position: absolute;
  335. bottom: 0;
  336. left: 0;
  337. width: 100%;
  338. display: flex;
  339. align-items: center;
  340. justify-content: center;
  341. font-size: 20px;
  342. opacity: 0;
  343. transition: all .15s;
  344. }
  345. .loading.active{
  346. opacity: 1;
  347. }
  348. .header{
  349. height: 80px;
  350. background-color: #aaa;
  351. }
  352. .water-fall-container::-webkit-scrollbar{
  353. width: 8px;
  354. background-color: #eee;
  355. }
  356. .water-fall-container::-webkit-scrollbar-thumb{
  357. background-color: #bbb;
  358. border-radius: 4px;
  359. }
  360. .water-fall-container::-webkit-scrollbar-thumb:hover{
  361. background-color: #aaa;
  362. }
  363. .water-fall-container{
  364. padding: 20px;
  365. height: calc(100% - 130px);
  366. overflow-y: scroll;
  367. overflow-x: hidden;
  368. }
  369. .box{
  370. position: relative;
  371. width: 100%;
  372. }
  373. .waterfall-item{
  374. position: absolute;
  375. transition: all .12s;
  376. font-family: sans-serif;
  377. display: flex;
  378. flex-direction: column;
  379. }
  380. .main{
  381. flex-grow: 1;
  382. flex-shrink: 0;
  383. background-color: pink;
  384. border-top-left-radius: 8px;
  385. border-top-right-radius: 8px;
  386. object-fit: contain;
  387. }
  388. .footer{
  389. box-sizing: border-box;
  390. padding: 12px;
  391. background-color: darksalmon;
  392. border-bottom-left-radius: 8px;
  393. border-bottom-right-radius: 8px;
  394. }
  395. .info{
  396. font-size: 14px;
  397. overflow: hidden;
  398. text-overflow: ellipsis;
  399. white-space: nowrap;
  400. }
  401. .text{
  402. overflow: hidden;
  403. text-overflow: ellipsis;
  404. display: -webkit-box;
  405. -webkit-line-clamp: 2;
  406. line-clamp: 2;
  407. -webkit-box-orient: vertical;
  408. font-size: 16px;
  409. line-height: 24px;
  410. margin-bottom: 10px;
  411. letter-spacing: 0;
  412. }
  413. </style>

debounce文件内容如下 

  1. var FUNC_ERROR_TEXT = 'Expected a function';
  2. var NAN = 0 / 0;
  3. var symbolTag = '[object Symbol]';
  4. var reTrim = /^\s+|\s+$/g;
  5. var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
  6. var reIsBinary = /^0b[01]+$/i;
  7. var reIsOctal = /^0o[0-7]+$/i;
  8. var freeParseInt = parseInt;
  9. var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
  10. var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
  11. var root = freeGlobal || freeSelf || Function('return this')();
  12. var objectProto = Object.prototype;
  13. var objectToString = objectProto.toString;
  14. var nativeMax = Math.max,
  15. nativeMin = Math.min;
  16. var now = function() {
  17. return root.Date.now();
  18. };
  19. function debounce(func, wait, options) {
  20. var lastArgs,
  21. lastThis,
  22. maxWait,
  23. result,
  24. timerId,
  25. lastCallTime,
  26. lastInvokeTime = 0,
  27. leading = false,
  28. maxing = false,
  29. trailing = true;
  30. if (typeof func != 'function') {
  31. throw new TypeError(FUNC_ERROR_TEXT);
  32. }
  33. wait = toNumber(wait) || 0;
  34. if (isObject(options)) {
  35. leading = !!options.leading;
  36. maxing = 'maxWait' in options;
  37. maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
  38. trailing = 'trailing' in options ? !!options.trailing : trailing;
  39. }
  40. function invokeFunc(time) {
  41. var args = lastArgs,
  42. thisArg = lastThis;
  43. lastArgs = lastThis = undefined;
  44. lastInvokeTime = time;
  45. result = func.apply(thisArg, args);
  46. return result;
  47. }
  48. function leadingEdge(time) {
  49. // Reset any `maxWait` timer.
  50. lastInvokeTime = time;
  51. // Start the timer for the trailing edge.
  52. timerId = setTimeout(timerExpired, wait);
  53. // Invoke the leading edge.
  54. return leading ? invokeFunc(time) : result;
  55. }
  56. function remainingWait(time) {
  57. var timeSinceLastCall = time - lastCallTime,
  58. timeSinceLastInvoke = time - lastInvokeTime,
  59. result = wait - timeSinceLastCall;
  60. return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
  61. }
  62. function shouldInvoke(time) {
  63. var timeSinceLastCall = time - lastCallTime,
  64. timeSinceLastInvoke = time - lastInvokeTime;
  65. // Either this is the first call, activity has stopped and we're at the
  66. // trailing edge, the system time has gone backwards and we're treating
  67. // it as the trailing edge, or we've hit the `maxWait` limit.
  68. return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
  69. (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
  70. }
  71. function timerExpired() {
  72. var time = now();
  73. if (shouldInvoke(time)) {
  74. return trailingEdge(time);
  75. }
  76. // Restart the timer.
  77. timerId = setTimeout(timerExpired, remainingWait(time));
  78. }
  79. function trailingEdge(time) {
  80. timerId = undefined;
  81. // Only invoke if we have `lastArgs` which means `func` has been
  82. // debounced at least once.
  83. if (trailing && lastArgs) {
  84. return invokeFunc(time);
  85. }
  86. lastArgs = lastThis = undefined;
  87. return result;
  88. }
  89. function cancel() {
  90. if (timerId !== undefined) {
  91. clearTimeout(timerId);
  92. }
  93. lastInvokeTime = 0;
  94. lastArgs = lastCallTime = lastThis = timerId = undefined;
  95. }
  96. function flush() {
  97. return timerId === undefined ? result : trailingEdge(now());
  98. }
  99. function debounced() {
  100. var time = now(),
  101. isInvoking = shouldInvoke(time);
  102. lastArgs = arguments;
  103. lastThis = this;
  104. lastCallTime = time;
  105. if (isInvoking) {
  106. if (timerId === undefined) {
  107. return leadingEdge(lastCallTime);
  108. }
  109. if (maxing) {
  110. // Handle invocations in a tight loop.
  111. timerId = setTimeout(timerExpired, wait);
  112. return invokeFunc(lastCallTime);
  113. }
  114. }
  115. if (timerId === undefined) {
  116. timerId = setTimeout(timerExpired, wait);
  117. }
  118. return result;
  119. }
  120. debounced.cancel = cancel;
  121. debounced.flush = flush;
  122. return debounced;
  123. }
  124. function isObject(value) {
  125. var type = typeof value;
  126. return !!value && (type == 'object' || type == 'function');
  127. }
  128. function isObjectLike(value) {
  129. return !!value && typeof value == 'object';
  130. }
  131. function isSymbol(value) {
  132. return typeof value == 'symbol' ||
  133. (isObjectLike(value) && objectToString.call(value) == symbolTag);
  134. }
  135. function toNumber(value) {
  136. if (typeof value == 'number') {
  137. return value;
  138. }
  139. if (isSymbol(value)) {
  140. return NAN;
  141. }
  142. if (isObject(value)) {
  143. var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
  144. value = isObject(other) ? (other + '') : other;
  145. }
  146. if (typeof value != 'string') {
  147. return value === 0 ? value : +value;
  148. }
  149. value = value.replace(reTrim, '');
  150. var isBinary = reIsBinary.test(value);
  151. return (isBinary || reIsOctal.test(value))
  152. ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
  153. : (reIsBadHex.test(value) ? NAN : +value);
  154. }
  155. window.debounce = debounce;

thorttle文件代码如下

  1. var FUNC_ERROR_TEXT = 'Expected a function';
  2. function throttle(func, wait, options) {
  3. var leading = true,
  4. trailing = true;
  5. if (typeof func != 'function') {
  6. throw new TypeError(FUNC_ERROR_TEXT);
  7. }
  8. if (isObject(options)) {
  9. leading = 'leading' in options ? !!options.leading : leading;
  10. trailing = 'trailing' in options ? !!options.trailing : trailing;
  11. }
  12. return window.debounce(func, wait, {
  13. 'leading': leading,
  14. 'maxWait': wait,
  15. 'trailing': trailing
  16. });
  17. }
  18. function isObject(value) {
  19. return typeof value === 'object' && value!== null;
  20. }
  21. window.throttle = throttle;

data文件代码如下(这里我的是随机生成的,宽高可自行修改)

  1. const WHList1 = [
  2. {
  3. "w": 600,
  4. "h": 600
  5. },
  6. {
  7. "w": 600,
  8. "h": 1067
  9. },
  10. {
  11. "w": 600,
  12. "h": 600
  13. },
  14. {
  15. "w": 600,
  16. "h": 1067
  17. },
  18. {
  19. "w": 600,
  20. "h": 800
  21. },
  22. {
  23. "w": 600,
  24. "h": 1067
  25. },
  26. {
  27. "w": 600,
  28. "h": 800
  29. },
  30. {
  31. "w": 600,
  32. "h": 600
  33. },
  34. {
  35. "w": 600,
  36. "h": 700
  37. },
  38. {
  39. "w": 600,
  40. "h": 600
  41. },
  42. {
  43. "w": 600,
  44. "h": 1067
  45. },
  46. {
  47. "w": 600,
  48. "h": 700
  49. },
  50. {
  51. "w": 600,
  52. "h": 700
  53. },
  54. ],
  55. let WHList = WHList1.concat(WHList2)
  56. export default WHList

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号