Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
京东前端
nutui
提交
74bdbe32
N
nutui
项目概览
京东前端
/
nutui
通知
37
Star
4
Fork
1
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
N
nutui
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
74bdbe32
编写于
2月 24, 2023
作者:
S
suzigang
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat(list): 优化虚拟列表,支持不固定高度 #1658 #1382
上级
55ad6083
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
417 addition
and
86 deletion
+417
-86
src/packages/__VUE/list/demo.vue
src/packages/__VUE/list/demo.vue
+6
-6
src/packages/__VUE/list/doc.en-US.md
src/packages/__VUE/list/doc.en-US.md
+11
-9
src/packages/__VUE/list/doc.md
src/packages/__VUE/list/doc.md
+10
-8
src/packages/__VUE/list/index.scss
src/packages/__VUE/list/index.scss
+8
-8
src/packages/__VUE/list/index.taro.vue
src/packages/__VUE/list/index.taro.vue
+187
-26
src/packages/__VUE/list/index.vue
src/packages/__VUE/list/index.vue
+158
-29
src/packages/__VUE/list/type.ts
src/packages/__VUE/list/type.ts
+37
-0
未找到文件。
src/packages/__VUE/list/demo.vue
浏览文件 @
74bdbe32
...
...
@@ -2,10 +2,10 @@
<div
class=
"demo"
>
<h2>
{{
translate
(
'
basic
'
)
}}
</h2>
<nut-cell>
<nut-list
:
height=
"50"
:
listData=
"count"
@
scroll-bottom=
"handleScroll"
>
<template
v-slot=
"
{ item }">
<nut-list
:listData=
"count"
@
scroll-bottom=
"handleScroll"
>
<template
v-slot=
"
{ item
, index
}">
<div
class=
"list-item"
>
{{
i
tem
}}
{{
i
ndex
}}
</div>
</
template
>
</nut-list>
...
...
@@ -48,18 +48,18 @@ export default createDemo({
}
});
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
>
.demo
{
.nut-cell
{
height
:
100%
;
}
.list-item
{
.
nut-
list-item
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
width
:
100%
;
height
:
50px
;
margin-bottom
:
10px
;
height
:
50px
;
background-color
:
#f4a8b6
;
border-radius
:
10px
;
}
...
...
src/packages/__VUE/list/doc.en-US.md
浏览文件 @
74bdbe32
...
...
@@ -27,10 +27,10 @@ app.use();
<div
class=
"demo"
>
<h2>
Basic Usage
</h2>
<nut-cell>
<nut-list
:
height=
"50"
:
listData=
"count"
@
scroll-bottom=
"handleScroll"
>
<template
v-slot=
"{ item }"
>
<nut-list
:listData=
"count"
@
scroll-bottom=
"handleScroll"
>
<template
v-slot=
"{ item
. index
}"
>
<div
class=
"list-item"
>
{{ i
tem
}}
{{ i
ndex
}}
</div>
</template>
</nut-list>
...
...
@@ -70,17 +70,16 @@ body {
height
:
100%
;
}
.demo
{
height
:
100%
;
.nut-cell
{
height
:
100%
;
}
.list-item
{
.
nut-
list-item
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
width
:
100%
;
height
:
50px
;
margin-bottom
:
10px
;
height
:
50px
;
background-color
:
#f4a8b6
;
border-radius
:
10px
;
}
...
...
@@ -96,9 +95,11 @@ body {
| Attribute | Description | Type | Default |
|--------------|----------------------------------|--------|------------------|
| height | The height
of the list item | Number |
`5
0`
|
| height | The height
/estimated height of the list item, supports unfixed height | Number |
`8
0`
|
| list-data | List data | any[] |
`[]`
|
| container-height
`v3.1.19`
| Container height(The maximum value cannot exceed the viewable area) | Number |
`Visual area height`
|
| buffer-size
`v3.3.5`
| data buffer length | Number |
`5`
|
| margin
`v3.3.5`
| The gap between the lists is consistent with the custom style | Number |
`10`
|
### Slots
...
...
@@ -111,5 +112,6 @@ body {
| Event | Description | Arguments |
|--------|----------------|--------------|
| scroll(Will be abandoned),
`scroll-bottom`
replaced | Triggered when scrolling to the bottom | - |
| scroll-bottom
`v3.1.21`
| Triggered when scrolling to the bottom | - |
\ No newline at end of file
| scroll-bottom
`v3.1.21`
| Triggered when scrolling to the bottom | - |
| scroll-up
`v3.3.5`
| scroll up | - |
| scroll-down
`v3.3.5`
| scroll down | - |
\ No newline at end of file
src/packages/__VUE/list/doc.md
浏览文件 @
74bdbe32
...
...
@@ -28,9 +28,9 @@ app.use();
<h2>
基础用法
</h2>
<nut-cell>
<nut-list
:height=
"50"
:listData=
"count"
@
scroll-bottom=
"handleScroll"
>
<template
v-slot=
"{ item }"
>
<template
v-slot=
"{ item
, index
}"
>
<div
class=
"list-item"
>
{{ i
tem
}}
{{ i
ndex
}}
</div>
</template>
</nut-list>
...
...
@@ -70,11 +70,10 @@ body {
height
:
100%
;
}
.demo
{
height
:
100%
;
.nut-cell
{
height
:
100%
;
}
.list-item
{
.
nut-
list-item
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
...
...
@@ -96,20 +95,23 @@ body {
| 参数 | 说明 | 类型 | 默认值 |
|--------------|----------------------------------|--------|------------------|
| height | 列表项的高度
| Number |
`5
0`
|
| height | 列表项的高度
/预估高度,支持不固定高度 | Number |
`8
0`
|
| list-data | 列表数据 | any[] |
`[]`
|
| container-height
`v3.1.19`
| 容器高度(最大值不能超过可视区) | Number |
`可视区高度`
|
| buffer-size
`v3.3.5`
| 数据缓冲区长度 | Number |
`5`
|
| margin
`v3.3.5`
| 列表之间的间隙,和自定义样式保持一致 | Number |
`10`
|
### Slots
| 参数 | 说明 | 类型 |
|--------------|----------------------------------|--------|
| item | 列表项数据 | Object |
| index | 索引 | Number |
| index |
列表
索引 | Number |
### Events
| 事件名 | 说明 | 回调参数 |
|--------|----------------|--------------|
| scroll(将被废弃),
`scroll-bottom`
代替 | 滚动到底部时触发 | - |
| scroll-bottom
`v3.1.21`
| 滚动到底部时触发 | - |
\ No newline at end of file
| scroll-bottom
`v3.1.21`
| 滚动到底部时触发 | - |
| scroll-up
`v3.3.5`
| 向上滚动 | - |
| scroll-down
`v3.3.5`
| 向下滚动 | - |
\ No newline at end of file
src/packages/__VUE/list/index.scss
浏览文件 @
74bdbe32
.nut-list
{
width
:
100%
;
overflow
:
scroll
;
position
:
relative
;
overflow-x
:
hidden
;
overflow-y
:
auto
;
-webkit-overflow-scrolling
:
touch
;
&
-phantom
{
position
:
absolute
;
left
:
0
;
top
:
0
;
right
:
0
;
position
:
relative
;
z-index
:
-1
;
}
&
-container
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
top
:
0
;
position
:
absolute
;
}
&
-item
{
overflow
:
hidden
;
margin
:
$list-item-margin
;
}
}
src/packages/__VUE/list/index.taro.vue
浏览文件 @
74bdbe32
...
...
@@ -6,22 +6,41 @@
scroll-top="0"
@scroll="handleScrollEvent"
ref="list"
:id="'list' + refRandomId"
>
<div
class=
"nut-list-phantom"
:style=
"
{ height: listHeight + 'px' }">
</div>
<div
class=
"nut-list-container"
:style=
"
{ transform: getTransform }">
<div
class=
"nut-list-item"
:style=
"
{ height: height + 'px' }" v-for="(item, index) in visibleData" :key="item">
<slot
:item=
"item"
:index=
"index"
></slot>
<div
class=
"nut-list-phantom"
:style=
"
{ height: phantomHeight + 'px' }"
ref="phantom"
:id="'phantom' + refRandomId"
>
</div>
<div
class=
"nut-list-container"
:style=
"
{ transform: getTransform() }"
ref="actualContent"
:id="'actualContent' + refRandomId"
>
<div
class=
"nut-list-item"
v-for=
"(item, index) in visibleData"
:key=
"item"
:id=
"'list-item-' + Number(index + start)"
>
<slot
:item=
"item"
:index=
"index + start"
></slot>
</div>
</div>
</Nut-Scroll-View>
</
template
>
<
script
lang=
"ts"
>
import
{
reactive
,
toRefs
,
computed
,
ref
,
Ref
,
watch
}
from
'
vue
'
;
import
{
reactive
,
toRefs
,
computed
,
ref
,
Ref
,
watch
,
ComputedRef
}
from
'
vue
'
;
import
{
createComponent
}
from
'
@/packages/utils/create
'
;
import
NutScrollView
from
'
../scrollView/index.taro.vue
'
;
import
Taro
from
'
@tarojs/taro
'
;
import
{
useTaroRect
}
from
'
@/packages/utils/useTaroRect
'
;
import
{
CachedPosition
,
CompareResult
,
binarySearch
}
from
'
./type
'
;
const
{
componentName
,
create
}
=
createComponent
(
'
list
'
);
const
clientHeight
=
Taro
.
getSystemInfoSync
().
windowHeight
||
667
;
export
default
create
({
components
:
{
NutScrollView
...
...
@@ -37,19 +56,37 @@ export default create({
return
[];
}
},
bufferSize
:
{
type
:
Number
,
default
:
5
},
containerHeight
:
{
type
:
[
Number
],
default
:
clientHeight
},
estimateRowHeight
:
{
type
:
Number
,
default
:
80
},
margin
:
{
type
:
Number
,
default
:
10
}
},
emits
:
[
'
scroll
'
,
'
scroll-bottom
'
],
emits
:
[
'
scroll
-up
'
,
'
scroll-down
'
,
'
scroll-bottom
'
],
setup
(
props
,
{
emit
})
{
const
list
=
ref
(
null
)
as
Ref
;
const
phantom
=
ref
(
null
)
as
Ref
;
const
actualContent
=
ref
(
null
)
as
Ref
;
const
refRandomId
=
Math
.
random
().
toString
(
36
).
slice
(
-
8
);
const
state
=
reactive
({
startOffset
:
0
,
start
:
0
,
list
:
props
.
listData
.
slice
()
originStartIndex
:
0
,
scrollTop
:
0
,
list
:
props
.
listData
.
slice
(),
cachePositions
:
[]
as
CachedPosition
[],
phantomHeight
:
props
.
estimateRowHeight
*
props
.
listData
.
length
});
const
getContainerHeight
=
computed
(()
=>
{
...
...
@@ -57,15 +94,11 @@ export default create({
});
const
visibleCount
=
computed
(()
=>
{
return
Math
.
ceil
(
getContainerHeight
.
value
/
props
.
h
eight
);
return
Math
.
ceil
(
getContainerHeight
.
value
/
props
.
estimateRowH
eight
);
});
const
end
=
computed
(()
=>
{
return
state
.
start
+
visibleCount
.
value
;
});
const
getTransform
=
computed
(()
=>
{
return
`translate3d(0,
${
state
.
startOffset
}
px, 0)`
;
return
Math
.
min
(
state
.
originStartIndex
+
visibleCount
.
value
+
props
.
bufferSize
,
state
.
list
.
length
-
1
);
});
const
classes
=
computed
(()
=>
{
...
...
@@ -75,38 +108,166 @@ export default create({
};
});
const
listHeight
=
computed
(()
=>
{
return
state
.
list
.
length
*
props
.
height
;
const
visibleData
:
ComputedRef
=
computed
(()
=>
{
return
state
.
list
.
slice
(
state
.
start
,
end
.
value
)
;
});
const
visibleData
=
computed
(()
=>
{
return
state
.
list
.
slice
(
state
.
start
,
Math
.
min
(
end
.
value
,
state
.
list
.
length
));
});
const
getTransform
=
()
=>
{
if
(
actualContent
.
value
)
{
return
`translate3d(0,
${
state
.
start
>=
1
?
state
.
cachePositions
[
state
.
start
-
1
].
bottom
:
0
}
px, 0)`
;
}
};
const
initCachedPosition
=
()
=>
{
state
.
cachePositions
=
[];
for
(
let
i
=
0
;
i
<
state
.
list
.
length
;
++
i
)
{
state
.
cachePositions
[
i
]
=
{
index
:
i
,
height
:
props
.
estimateRowHeight
,
top
:
i
*
props
.
estimateRowHeight
,
bottom
:
(
i
+
1
)
*
(
props
.
estimateRowHeight
+
props
.
margin
),
dValue
:
0
};
}
};
const
updateCachedPosition
=
()
=>
{
let
nodes
:
any
[]
=
actualContent
.
value
.
childNodes
;
nodes
=
Array
.
from
(
nodes
).
filter
((
node
:
HTMLDivElement
)
=>
node
.
nodeType
===
1
);
const
start
=
nodes
[
0
];
nodes
.
forEach
(
async
(
node
:
HTMLDivElement
,
index
:
number
)
=>
{
if
(
!
node
)
return
;
const
rect
=
await
useTaroRect
(
node
,
Taro
);
if
(
rect
&&
rect
.
height
)
{
const
{
height
}
=
rect
;
const
oldHeight
=
state
.
cachePositions
[
index
+
state
.
start
]
?
state
.
cachePositions
[
index
+
state
.
start
].
height
:
props
.
height
;
const
dValue
=
oldHeight
-
height
;
if
(
dValue
&&
state
.
cachePositions
[
index
+
state
.
start
])
{
state
.
cachePositions
[
index
+
state
.
start
].
bottom
-=
dValue
;
state
.
cachePositions
[
index
+
state
.
start
].
height
=
height
;
state
.
cachePositions
[
index
+
state
.
start
].
dValue
=
dValue
;
}
}
});
let
startIndex
=
0
;
if
(
start
)
{
startIndex
=
state
.
start
;
}
const
cachedPositionsLen
=
state
.
cachePositions
.
length
;
let
cumulativeDiffHeight
=
state
.
cachePositions
[
startIndex
].
dValue
;
state
.
cachePositions
[
startIndex
].
dValue
=
0
;
for
(
let
i
=
startIndex
+
1
;
i
<
cachedPositionsLen
;
++
i
)
{
const
item
=
state
.
cachePositions
[
i
];
state
.
cachePositions
[
i
].
top
=
state
.
cachePositions
[
i
-
1
].
bottom
;
state
.
cachePositions
[
i
].
bottom
=
state
.
cachePositions
[
i
].
bottom
-
cumulativeDiffHeight
;
if
(
item
.
dValue
!==
0
)
{
cumulativeDiffHeight
+=
item
.
dValue
;
item
.
dValue
=
0
;
}
}
const
height
=
state
.
cachePositions
[
cachedPositionsLen
-
1
].
bottom
;
state
.
phantomHeight
=
height
;
};
const
getStartIndex
=
(
scrollTop
=
0
)
=>
{
let
idx
=
binarySearch
<
CachedPosition
,
number
>
(
state
.
cachePositions
,
scrollTop
,
(
currentValue
:
CachedPosition
,
targetValue
:
number
)
=>
{
const
currentCompareValue
=
currentValue
.
bottom
;
if
(
currentCompareValue
===
targetValue
)
{
return
CompareResult
.
eq
;
}
if
(
currentCompareValue
<
targetValue
)
{
return
CompareResult
.
lt
;
}
return
CompareResult
.
gt
;
}
)
as
number
;
const
targetItem
=
state
.
cachePositions
[
idx
];
if
(
targetItem
.
bottom
<
scrollTop
)
{
idx
+=
1
;
}
return
idx
;
};
const
resetAllVirtualParam
=
()
=>
{
state
.
originStartIndex
=
0
;
state
.
start
=
0
;
state
.
scrollTop
=
0
;
list
.
value
.
scrollTop
=
0
;
initCachedPosition
();
state
.
phantomHeight
=
props
.
estimateRowHeight
*
state
.
list
.
length
;
};
const
handleScrollEvent
=
async
(
e
:
any
)
=>
{
const
scrollTop
=
Math
.
max
(
e
.
detail
?
e
.
detail
.
scrollTop
:
e
.
target
.
scrollTop
,
0.1
);
state
.
start
=
Math
.
floor
(
scrollTop
/
props
.
height
);
if
(
end
.
value
>
state
.
list
.
length
)
{
emit
(
'
scroll
'
);
emit
(
'
scroll-bottom
'
);
const
{
originStartIndex
}
=
state
;
const
currentIndex
=
getStartIndex
(
scrollTop
);
if
(
currentIndex
!==
originStartIndex
)
{
state
.
originStartIndex
=
currentIndex
;
state
.
start
=
Math
.
max
(
state
.
originStartIndex
-
props
.
bufferSize
,
0
);
if
(
end
.
value
>=
state
.
list
.
length
-
1
)
{
emit
(
'
scroll-bottom
'
);
}
}
state
.
startOffset
=
scrollTop
-
(
scrollTop
%
props
.
height
);
emit
(
scrollTop
>
state
.
scrollTop
?
'
scroll-up
'
:
'
scroll-down
'
,
scrollTop
);
state
.
scrollTop
=
scrollTop
;
};
watch
(
()
=>
props
.
listData
,
(
val
:
any
[])
=>
{
state
.
list
=
val
.
slice
();
if
(
state
.
list
.
length
===
val
.
length
)
{
setTimeout
(()
=>
{
initCachedPosition
();
updateCachedPosition
();
},
200
);
}
else
{
resetAllVirtualParam
();
return
;
}
}
);
watch
(
()
=>
state
.
start
,
()
=>
{
state
.
list
=
props
.
listData
.
slice
();
if
(
actualContent
.
value
&&
state
.
list
.
length
>
0
)
{
Taro
.
nextTick
(()
=>
{
setTimeout
(()
=>
{
updateCachedPosition
();
},
200
);
});
}
}
);
return
{
...
toRefs
(
state
),
list
,
phantom
,
actualContent
,
getTransform
,
listHeight
,
visibleData
,
classes
,
refRandomId
,
getContainerHeight
,
handleScrollEvent
};
...
...
src/packages/__VUE/list/index.vue
浏览文件 @
74bdbe32
<
template
>
<div
:class=
"classes"
:style=
"
{ height: `${getContainerHeight}px` }" @scroll.passive="handleScrollEvent" ref="list">
<div
class=
"nut-list-phantom"
:style=
"
{ height:
listHeight + 'px' }
">
</div>
<div
class=
"nut-list-container"
:style=
"
{ transform: getTransform
}
">
<div
class=
"nut-list-item"
:style=
"
{ height: height + 'px' }"
v-for="(item, index) in visibleData" :key="item">
<slot
:item=
"item"
:index=
"index"
></slot>
<div
class=
"nut-list-phantom"
:style=
"
{ height:
phantomHeight + 'px' }" ref="phantom
">
</div>
<div
class=
"nut-list-container"
:style=
"
{ transform: getTransform
() }" ref="actualContent
">
<div
class=
"nut-list-item"
v-for=
"(item, index) in visibleData"
:key=
"item"
>
<slot
:item=
"item"
:index=
"index
+ start
"
></slot>
</div>
</div>
</div>
</
template
>
<
script
lang=
"ts"
>
import
{
reactive
,
toRefs
,
computed
,
ref
,
Ref
,
watch
}
from
'
vue
'
;
import
{
reactive
,
toRefs
,
computed
,
ref
,
Ref
,
watch
,
ComputedRef
}
from
'
vue
'
;
import
{
createComponent
}
from
'
@/packages/utils/create
'
;
import
{
CachedPosition
,
CompareResult
,
binarySearch
}
from
'
./type
'
;
import
{
useRect
}
from
'
@/packages/utils/useRect
'
;
const
{
componentName
,
create
}
=
createComponent
(
'
list
'
);
const
clientHeight
=
document
.
documentElement
.
clientHeight
||
document
.
body
.
clientHeight
||
667
;
export
default
create
({
props
:
{
height
:
{
type
:
[
Number
],
default
:
50
},
listData
:
{
type
:
Array
,
default
:
()
=>
{
return
[];
}
},
bufferSize
:
{
type
:
Number
,
default
:
5
},
containerHeight
:
{
type
:
[
Number
],
default
:
clientHeight
},
height
:
{
type
:
Number
,
default
:
80
},
margin
:
{
type
:
Number
,
default
:
10
}
},
emits
:
[
'
scroll
'
,
'
scroll-bottom
'
],
emits
:
[
'
scroll
-up
'
,
'
scroll-down
'
,
'
scroll-bottom
'
],
setup
(
props
,
{
emit
})
{
const
list
=
ref
(
null
)
as
Ref
;
const
phantom
=
ref
(
null
)
as
Ref
;
const
actualContent
=
ref
(
null
)
as
Ref
;
const
state
=
reactive
({
startOffset
:
0
,
start
:
0
,
list
:
props
.
listData
.
slice
()
originStartIndex
:
0
,
scrollTop
:
0
,
list
:
props
.
listData
.
slice
(),
cachePositions
:
[]
as
CachedPosition
[],
phantomHeight
:
props
.
height
*
props
.
listData
.
length
});
const
getContainerHeight
=
computed
(()
=>
{
...
...
@@ -49,11 +65,7 @@ export default create({
});
const
end
=
computed
(()
=>
{
return
state
.
start
+
visibleCount
.
value
;
});
const
getTransform
=
computed
(()
=>
{
return
`translate3d(0,
${
state
.
startOffset
}
px, 0)`
;
return
Math
.
min
(
state
.
originStartIndex
+
visibleCount
.
value
+
props
.
bufferSize
,
state
.
list
.
length
-
1
);
});
const
classes
=
computed
(()
=>
{
...
...
@@ -63,36 +75,153 @@ export default create({
};
});
const
listHeight
=
computed
(()
=>
{
return
state
.
list
.
length
*
props
.
height
;
const
visibleData
:
ComputedRef
=
computed
(()
=>
{
return
state
.
list
.
slice
(
state
.
start
,
end
.
value
)
;
});
const
visibleData
=
computed
(()
=>
{
return
state
.
list
.
slice
(
state
.
start
,
Math
.
min
(
end
.
value
,
state
.
list
.
length
));
});
const
getTransform
=
()
=>
{
if
(
actualContent
.
value
)
{
return
`translate3d(0,
${
state
.
start
>=
1
?
state
.
cachePositions
[
state
.
start
-
1
].
bottom
:
0
}
px, 0)`
;
}
};
const
initCachedPosition
=
()
=>
{
state
.
cachePositions
=
[];
for
(
let
i
=
0
;
i
<
state
.
list
.
length
;
++
i
)
{
state
.
cachePositions
[
i
]
=
{
index
:
i
,
height
:
props
.
height
,
top
:
i
*
props
.
height
,
bottom
:
(
i
+
1
)
*
(
props
.
height
+
props
.
margin
),
dValue
:
0
};
}
};
const
updateCachedPosition
=
()
=>
{
let
nodes
:
any
[]
=
actualContent
.
value
.
childNodes
;
nodes
=
Array
.
from
(
nodes
).
filter
((
node
:
HTMLDivElement
)
=>
node
.
nodeType
===
1
);
const
start
=
nodes
[
0
];
nodes
.
forEach
((
node
:
HTMLDivElement
,
index
:
number
)
=>
{
if
(
!
node
)
return
;
const
rect
=
useRect
(
node
);
const
{
height
}
=
rect
;
const
oldHeight
=
state
.
cachePositions
[
index
+
state
.
start
].
height
;
const
dValue
=
oldHeight
-
height
;
if
(
dValue
)
{
state
.
cachePositions
[
index
+
state
.
start
].
bottom
-=
dValue
;
state
.
cachePositions
[
index
+
state
.
start
].
height
=
height
;
state
.
cachePositions
[
index
+
state
.
start
].
dValue
=
dValue
;
}
});
let
startIndex
=
0
;
if
(
start
)
{
startIndex
=
state
.
start
;
}
const
cachedPositionsLen
=
state
.
cachePositions
.
length
;
let
cumulativeDiffHeight
=
state
.
cachePositions
[
startIndex
].
dValue
;
state
.
cachePositions
[
startIndex
].
dValue
=
0
;
for
(
let
i
=
startIndex
+
1
;
i
<
cachedPositionsLen
;
++
i
)
{
const
item
=
state
.
cachePositions
[
i
];
state
.
cachePositions
[
i
].
top
=
state
.
cachePositions
[
i
-
1
].
bottom
;
state
.
cachePositions
[
i
].
bottom
=
state
.
cachePositions
[
i
].
bottom
-
cumulativeDiffHeight
;
if
(
item
.
dValue
!==
0
)
{
cumulativeDiffHeight
+=
item
.
dValue
;
item
.
dValue
=
0
;
}
}
const
height
=
state
.
cachePositions
[
cachedPositionsLen
-
1
].
bottom
;
state
.
phantomHeight
=
height
;
};
const
getStartIndex
=
(
scrollTop
=
0
)
=>
{
let
idx
=
binarySearch
<
CachedPosition
,
number
>
(
state
.
cachePositions
,
scrollTop
,
(
currentValue
:
CachedPosition
,
targetValue
:
number
)
=>
{
const
currentCompareValue
=
currentValue
.
bottom
;
if
(
currentCompareValue
===
targetValue
)
{
return
CompareResult
.
eq
;
}
if
(
currentCompareValue
<
targetValue
)
{
return
CompareResult
.
lt
;
}
return
CompareResult
.
gt
;
}
)
as
number
;
const
targetItem
=
state
.
cachePositions
[
idx
];
if
(
targetItem
.
bottom
<
scrollTop
)
{
idx
+=
1
;
}
return
idx
;
};
const
resetAllVirtualParam
=
()
=>
{
state
.
originStartIndex
=
0
;
state
.
start
=
0
;
state
.
scrollTop
=
0
;
list
.
value
.
scrollTop
=
0
;
initCachedPosition
();
state
.
phantomHeight
=
props
.
height
*
state
.
list
.
length
;
};
const
handleScrollEvent
=
()
=>
{
const
scrollTop
=
list
.
value
?.
scrollTop
as
number
;
state
.
start
=
Math
.
floor
(
scrollTop
/
props
.
height
);
if
(
end
.
value
>
state
.
list
.
length
)
{
emit
(
'
scroll
'
);
emit
(
'
scroll-bottom
'
);
const
{
originStartIndex
}
=
state
;
const
currentIndex
=
getStartIndex
(
scrollTop
);
if
(
currentIndex
!==
originStartIndex
)
{
state
.
originStartIndex
=
currentIndex
;
state
.
start
=
Math
.
max
(
state
.
originStartIndex
-
props
.
bufferSize
,
0
);
if
(
end
.
value
>=
state
.
list
.
length
-
1
)
{
emit
(
'
scroll-bottom
'
);
}
}
state
.
startOffset
=
scrollTop
-
(
scrollTop
%
props
.
height
);
emit
(
scrollTop
>
state
.
scrollTop
?
'
scroll-up
'
:
'
scroll-down
'
,
scrollTop
);
state
.
scrollTop
=
scrollTop
;
};
watch
(
()
=>
props
.
listData
,
(
val
:
any
[])
=>
{
state
.
list
=
val
.
slice
();
if
(
state
.
list
.
length
===
val
.
length
)
{
initCachedPosition
();
updateCachedPosition
();
}
else
{
resetAllVirtualParam
();
return
;
}
}
);
watch
(
()
=>
state
.
start
,
()
=>
{
state
.
list
=
props
.
listData
.
slice
();
if
(
actualContent
.
value
&&
state
.
list
.
length
>
0
)
{
updateCachedPosition
();
}
}
);
return
{
...
toRefs
(
state
),
list
,
phantom
,
actualContent
,
getTransform
,
listHeight
,
visibleData
,
classes
,
getContainerHeight
,
...
...
src/packages/__VUE/list/type.ts
0 → 100644
浏览文件 @
74bdbe32
export
interface
CachedPosition
{
index
:
number
;
top
:
number
;
bottom
:
number
;
height
:
number
;
dValue
:
number
;
}
export
enum
CompareResult
{
eq
=
1
,
lt
,
gt
}
export
function
binarySearch
<
T
,
VT
>
(
list
:
T
[],
value
:
VT
,
compareFunc
:
(
current
:
T
,
value
:
VT
)
=>
CompareResult
)
{
let
start
=
0
;
let
end
=
list
.
length
-
1
;
let
tempIndex
=
null
;
while
(
start
<=
end
)
{
tempIndex
=
Math
.
floor
((
start
+
end
)
/
2
);
const
midValue
=
list
[
tempIndex
];
const
compareRes
:
CompareResult
=
compareFunc
(
midValue
,
value
);
if
(
compareRes
===
CompareResult
.
eq
)
{
return
tempIndex
;
}
if
(
compareRes
===
CompareResult
.
lt
)
{
start
=
tempIndex
+
1
;
}
else
if
(
compareRes
===
CompareResult
.
gt
)
{
end
=
tempIndex
-
1
;
}
}
return
tempIndex
;
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录