Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
京东前端
nutui-react
提交
3f6ea9c5
N
nutui-react
项目概览
京东前端
/
nutui-react
通知
0
Star
2
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
N
nutui-react
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
3f6ea9c5
编写于
1月 30, 2023
作者:
V
vickyYe
提交者:
GitHub
1月 30, 2023
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 虚拟列表 virtuallist 组件适配 taro (#602)
上级
487994af
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
290 addition
and
307 deletion
+290
-307
src/config.json
src/config.json
+2
-1
src/packages/textarea/demo.taro.tsx
src/packages/textarea/demo.taro.tsx
+1
-1
src/packages/virtuallist/demo.taro.tsx
src/packages/virtuallist/demo.taro.tsx
+45
-42
src/packages/virtuallist/doc.taro.md
src/packages/virtuallist/doc.taro.md
+70
-114
src/packages/virtuallist/virtuallist.scss
src/packages/virtuallist/virtuallist.scss
+24
-0
src/packages/virtuallist/virtuallist.taro.tsx
src/packages/virtuallist/virtuallist.taro.tsx
+147
-149
src/sites/mobile-taro/src/app.config.ts
src/sites/mobile-taro/src/app.config.ts
+1
-0
未找到文件。
src/config.json
浏览文件 @
3f6ea9c5
...
...
@@ -1039,8 +1039,9 @@
"cName"
:
"虚拟列表"
,
"desc"
:
"长列表渲染"
,
"sort"
:
20
,
"tarodoc"
:
true
,
"show"
:
true
,
"taro"
:
fals
e
,
"taro"
:
tru
e
,
"author"
:
"hx"
},
{
...
...
src/packages/textarea/demo.taro.tsx
浏览文件 @
3f6ea9c5
import
React
,
{
useEffect
,
useState
}
from
'
react
'
import
Taro
from
'
@tarojs/taro
'
import
{
useTranslate
}
from
'
@/sites/assets/locale/taro
'
import
{
TextArea
}
from
'
@/packages/nutui.react.taro
'
import
Header
from
'
@/sites/components/header
'
import
Taro
from
'
@tarojs/taro
'
interface
T
{
basic
:
string
...
...
src/packages/virtuallist/demo.taro.tsx
浏览文件 @
3f6ea9c5
import
React
,
{
use
Callback
,
useEffect
,
useState
}
from
'
react
'
import
React
,
{
use
State
,
useEffect
,
useCallback
}
from
'
react
'
import
{
useTranslate
}
from
'
@/sites/assets/locale/taro
'
import
{
Cell
,
CellGroup
,
Radio
}
from
'
@/packages/nutui.react.taro
'
import
{
Cell
,
CellGroup
,
Radio
,
VirtualList
,
}
from
'
@/packages/nutui.react.taro
'
import
Header
from
'
@/sites/components/header
'
import
Taro
from
'
@tarojs/taro
'
import
VirtualList
from
'
./index
'
const
{
RadioGroup
}
=
Radio
...
...
@@ -32,6 +36,25 @@ const ListDemo = () => {
const
[
pageNo
,
setPageNo
]
=
useState
(
1
)
const
[
radioVal
,
setRadioVal
]
=
useState
(
'
1
'
)
const
[
isLoading
,
setIsLoading
]
=
useState
(
false
)
const
itemStyle
=
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
width
:
'
100%
'
,
height
:
'
50px
'
,
background
:
'
#fff
'
,
borderRadius
:
'
10px
'
,
}
const
itemStyel2
=
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
width
:
'
100%
'
,
background
:
'
#fff
'
,
borderRadius
:
'
10px
'
,
}
const
handleChange
=
(
v
:
any
)
=>
{
setRadioVal
(
v
)
setPageNo
(
1
)
...
...
@@ -53,16 +76,26 @@ const ListDemo = () => {
})
}
},
[
pageNo
])
useEffect
(()
=>
{
getData
()
},
[
getData
])
const
ItemRender
=
({
data
}:
any
)
=>
{
return
<
p
>
{
data
}
</
p
>
return
<
div
style
=
{
itemStyle
}
>
{
data
}
</
div
>
}
const
ItemRenderMemo
=
React
.
memo
(
ItemRender
)
const
ItemVariable
=
({
data
,
index
}:
any
)
=>
{
return
(
<
p
className
=
{
index
%
2
===
0
?
''
:
'
nut-virtualList-demo-item
'
}
>
<
div
style
=
{
{
height
:
`
${
index
%
2
===
0
?
'
100px
'
:
'
50px
'
}
`
,
...
itemStyel2
,
}
}
>
{
data
}
</
p
>
</
div
>
)
}
const
ItemVariableDemo
=
React
.
memo
(
ItemVariable
)
...
...
@@ -74,16 +107,13 @@ const ListDemo = () => {
setIsLoading
(
false
)
},
30
)
}
useEffect
(()
=>
{
getData
()
},
[
getData
])
const
showNode
=
()
=>
{
switch
(
radioVal
)
{
case
'
1
'
:
return
(
<
VirtualList
itemSize
=
{
66
}
className
=
"heigh1"
itemSize
=
{
50
}
sourceData
=
{
sourceData
}
ItemRender
=
{
ItemRenderMemo
}
onScroll
=
{
onScroll
}
...
...
@@ -92,41 +122,18 @@ const ListDemo = () => {
case
'
2
'
:
return
(
<
VirtualList
itemSize
=
{
80
}
sourceData
=
{
sourceData
}
ItemRender
=
{
ItemVariableDemo
}
itemSize
=
{
128
}
containerSize
=
{
500
}
itemEqualSize
=
{
false
}
onScroll
=
{
onScroll
}
/>
)
case
'
3
'
:
return
(
<
VirtualList
sourceData
=
{
sourceData
}
ItemRender
=
{
ItemRenderMemo
}
itemSize
=
{
124
}
containerSize
=
{
341
}
onScroll
=
{
onScroll
}
horizontal
/>
)
case
'
4
'
:
return
(
<
VirtualList
sourceData
=
{
sourceData
}
itemSize
=
{
300
}
ItemRender
=
{
ItemVariableDemo
}
horizontal
itemEqualSize
=
{
false
}
onScroll
=
{
onScroll
}
containerSize
=
{
500
}
/>
)
default
:
return
(
<
VirtualList
itemSize
=
{
66
}
className
=
"heigh1"
itemSize
=
{
50
}
sourceData
=
{
sourceData
}
ItemRender
=
{
ItemRenderMemo
}
onScroll
=
{
onScroll
}
...
...
@@ -147,14 +154,10 @@ const ListDemo = () => {
>
<
Radio
value
=
"1"
>
{
translated
.
text1
}
</
Radio
>
<
Radio
value
=
"2"
>
{
translated
.
text2
}
</
Radio
>
<
Radio
value
=
"3"
>
{
translated
.
text3
}
</
Radio
>
<
Radio
value
=
"4"
>
{
translated
.
text4
}
</
Radio
>
</
RadioGroup
>
</
Cell
>
</
CellGroup
>
<
div
key
=
{
radioVal
}
className
=
"nut-virtualList-demo-box hideScrollbar"
>
{
showNode
()
}
</
div
>
<
div
style
=
{
{
height
:
'
100%
'
}
}
>
{
showNode
()
}
</
div
>
</
div
>
</>
)
...
...
src/packages/virtuallist/doc.taro.md
浏览文件 @
3f6ea9c5
...
...
@@ -15,83 +15,58 @@ import { VirtualList } from '@nutui/nutui-react-taro';
:::demo
```
tsx
import
React
,
{
FunctionComponent
,
useCallback
,
useEffect
,
useRef
,
useState
}
from
'
react
'
import
React
,
{
useState
,
useEffect
,
useCallback
}
from
'
react
'
import
{
VirtualList
}
from
'
@nutui/nutui-react-taro
'
;
const
App
=
()
=>
{
const
[
sourceData
,
setsourceData
]
=
useState
([])
const
[
pageNo
,
setPageNo
]
=
useState
(
1
)
const
[
isLoading
,
setIsLoading
]
=
useState
(
false
)
const
itemStyle
=
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
width
:
'
100%
'
,
height
:
'
50px
'
,
background
:
'
#fff
'
,
borderRadius
:
'
10px
'
,
}
const
getData
=
useCallback
(()
=>
{
const
datas
=
[]
const
pageSize
=
90
for
(
let
i
=
10
;
i
<
pageSize
;
i
++
)
{
datas
.
push
(
`
${
i
}
Item`
)
datas
.
push
(
`
${
i
}
Item`
)
}
setsourceData
((
sourceData
)
=>
{
return
[...
sourceData
,
...
datas
]
return
[...
sourceData
,
...
datas
]
})
},
[])
useEffect
(()
=>
{
getData
()
},
[
getData
])
const
ItemRender
=
({
data
,
index
})
=>
{
return
<
p
>
自定义-
{
data
}
-
{
index
}
</
p
>
const
ItemRender
=
({
data
}:
any
)
=>
{
return
<
div
style
=
{
itemStyle
}
>
{
data
}
</
div
>
}
const
ItemRenderMemo
=
React
.
memo
(
ItemRender
)
return
(
<
div
className
=
'nut-virtualList-demo-box hideScrollbar heigh1'
>
<
VirtualList
itemSize
=
{
66
}
sourceData
=
{
sourceData
}
ItemRender
=
{
ItemRenderMemo
}
/>
</
div
>
)
}
export
default
App
;
```
:::
### 2、垂直不等高&无限下滑
:::demo
```
tsx
import
React
,
{
FunctionComponent
,
useCallback
,
useEffect
,
useRef
,
useState
}
from
'
react
'
import
{
VirtualList
}
from
'
@nutui/nutui-react-taro
'
;
const
App
=
()
=>
{
const
[
sourceData
,
setsourceData
]
=
useState
([])
const
[
pageNo
,
setPageNo
]
=
useState
(
1
)
const
getData
=
useCallback
(()
=>
{
const
datas
=
[]
const
pageSize
=
90
for
(
let
i
=
10
;
i
<
pageSize
;
i
++
)
{
datas
.
push
(
`
${
i
}
Item`
)
}
setsourceData
((
sourceData
)
=>
{
return
[...
sourceData
,
...
datas
]
})
},
[])
const
onScroll
=
()
=>
{
if
(
pageNo
>
100
)
return
setPageNo
(
pageNo
+
1
)
}
useEffect
(()
=>
{
getData
()
},
[
getData
])
const
ItemVariable
=
({
data
,
index
})
=>
{
return
(
<
p
className
=
{
index
%
2
===
0
?
''
:
'
nut-virtualList-demo-item
'
}
>
可变大小隔行展示-
{
data
}
</
p
>
)
const
onScroll
=
()
=>
{
if
(
pageNo
>
50
||
isLoading
)
return
setIsLoading
(
true
)
setTimeout
(()
=>
{
setPageNo
(
pageNo
+
1
)
setIsLoading
(
false
)
},
30
)
}
/** itemSize为首屏最大元素大小 */
const
ItemVariableDemo
=
React
.
memo
(
ItemVariable
)
return
(
<
div
className
=
'nut-virtualList-demo-box hideScrollbar heigh1'
>
<
VirtualList
<
div
style
=
{
{
height
:
'
100%
'
}
}
>
<
VirtualList
itemSize
=
{
50
}
sourceData
=
{
sourceData
}
ItemRender
=
{
ItemVariableDemo
}
itemSize
=
{
128
}
itemEqualSize
=
{
false
}
ItemRender
=
{
ItemRenderMemo
}
onScroll
=
{
onScroll
}
/>
</
div
>
...
...
@@ -100,91 +75,72 @@ const App =() => {
export
default
App
;
```
:::
### 3、水平等宽
### 2、垂直不等高&无限下滑
:::demo
```
tsx
import
React
,
{
FunctionComponent
,
useCallback
,
useEffect
,
useRef
,
useState
}
from
'
react
'
import
React
,
{
useState
,
useEffect
,
useCallback
}
from
'
react
'
import
{
VirtualList
}
from
'
@nutui/nutui-react-taro
'
;
const
App
=
()
=>
{
const
[
sourceData
,
setsourceData
]
=
useState
([])
const
[
pageNo
,
setPageNo
]
=
useState
(
1
)
const
getData
=
useCallback
(()
=>
{
const
datas
=
[]
const
pageSize
=
90
for
(
let
i
=
10
;
i
<
pageSize
;
i
++
)
{
datas
.
push
(
`
${
i
}
Item`
)
}
setsourceData
((
sourceData
)
=>
{
return
[...
sourceData
,
...
datas
]
})
},
[])
useEffect
(()
=>
{
getData
()
},
[
getData
])
const
ItemRender
=
({
data
,
index
})
=>
{
return
<
p
>
自定义-
{
data
}
-
{
index
}
</
p
>
const
[
isLoading
,
setIsLoading
]
=
useState
(
false
)
const
itemStyel2
=
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
width
:
'
100%
'
,
background
:
'
#fff
'
,
borderRadius
:
'
10px
'
,
}
const
ItemRenderMemo
=
React
.
memo
(
ItemRender
)
return
(
<
div
className
=
'nut-virtualList-demo-box hideScrollbar'
>
<
VirtualList
sourceData
=
{
sourceData
}
ItemRender
=
{
ItemRenderMemo
}
itemSize
=
{
124
}
horizontal
/>
</
div
>
)
}
export
default
App
;
```
:::
### 4、水平不等宽&无限滑动
:::demo
```
tsx
import
React
,
{
FunctionComponent
,
useCallback
,
useEffect
,
useRef
,
useState
}
from
'
react
'
import
{
VirtualList
}
from
'
@nutui/nutui-react-taro
'
;
const
App
=
()
=>
{
const
[
sourceData
,
setsourceData
]
=
useState
([])
const
[
pageNo
,
setPageNo
]
=
useState
(
1
)
const
getData
=
useCallback
(()
=>
{
const
datas
=
[]
const
pageSize
=
90
for
(
let
i
=
10
;
i
<
pageSize
;
i
++
)
{
datas
.
push
(
`
${
i
}
Item`
)
datas
.
push
(
`
${
i
}
Item`
)
}
setsourceData
((
sourceData
)
=>
{
return
[...
sourceData
,
...
datas
]
return
[...
sourceData
,
...
datas
]
})
},
[])
const
onScroll
=
()
=>
{
if
(
pageNo
>
100
)
return
setPageNo
(
pageNo
+
1
)
}
useEffect
(()
=>
{
getData
()
},
[
getData
])
const
ItemVariable
=
({
data
,
index
})
=>
{
const
ItemVariable
=
({
data
,
index
}:
any
)
=>
{
return
(
<
p
className
=
{
index
%
2
===
0
?
''
:
'
nut-virtualList-demo-item
'
}
>
可变大小隔行展示-
{
data
}
</
p
>
<
div
style
=
{
{
height
:
`
${
index
%
2
===
0
?
'
100px
'
:
'
50px
'
}
`
,
...
itemStyel2
,
}
}
>
{
data
}
</
div
>
)
}
/** itemSize为首屏最大元素大小 */
const
ItemVariableDemo
=
React
.
memo
(
ItemVariable
)
const
onScroll
=
()
=>
{
if
(
pageNo
>
50
||
isLoading
)
return
setIsLoading
(
true
)
setTimeout
(()
=>
{
setPageNo
(
pageNo
+
1
)
setIsLoading
(
false
)
},
30
)
}
return
(
<
div
className
=
'nut-virtualList-demo-box hideScrollbar'
>
<
div
style
=
{
{
height
:
'
100%
'
}
}
>
<
VirtualList
itemSize
=
{
80
}
sourceData
=
{
sourceData
}
itemSize
=
{
300
}
ItemRender
=
{
ItemVariableDemo
}
horizontal
itemEqualSize
=
{
false
}
onScroll
=
{
onScroll
}
itemEqualSize
=
{
false
}
containerSize
=
{
500
}
/>
</
div
>
)
...
...
@@ -192,6 +148,7 @@ const App =() => {
export
default
App
;
```
:::
## API
### Props
...
...
@@ -199,17 +156,16 @@ export default App;
| 参数 | 说明 | 类型 | 默认值 |
|---------------|----------------------------------|----------|---------------------------------------|
| sourceData | 获取数据 | Array | - |
| containerSize | 容器高度 | Number | 获取元素的 offset
Width 或 offset
Height,需要 css 给出 |
| containerSize | 容器高度 | Number | 获取元素的 offsetHeight,需要 css 给出 |
| ItemRender | virtual 列表父节点渲染的函数 | React.FC | - |
| itemSize | item高度,如果不定高,则为首屏单个最大size | String | - |
| itemEqualSize | item大小是否一致 | Boolean | true |
| itemSize | item高度,如果不定高,会走默认高度 | String | 66 |
| overscan | 除了视窗里面默认的元素, 还需要额外渲染的item个数 | Number | 2 |
| key | 唯一值 ,Item(sourceData)具体的某个唯一值的字段 | string | index |
| horizontal | 决定列表是横向的还是纵向的 | Boolean | false |
## Events
| 方法名 | 说明 | 参数 | 返回值 |
|------------------|---------------------| --------------- | ---------- |
| handleScroll
`废弃`
| 滑动到底(右)的事件,可以实现无限滚动 | - | - |
| onScroll
`v1.3.8`
| 滑动到底(右)的事件,可以实现无限滚动 | - | - |
...
...
src/packages/virtuallist/virtuallist.scss
浏览文件 @
3f6ea9c5
...
...
@@ -61,3 +61,27 @@
}
}
}
.nut-virtuallist
{
width
:
100%
;
overflow
:
scroll
;
position
:
relative
;
-webkit-overflow-scrolling
:
touch
;
&
-phantom
{
position
:
absolute
;
left
:
0
;
top
:
0
;
right
:
0
;
z-index
:
-1
;
}
&
-container
{
left
:
0
;
right
:
0
;
top
:
0
;
position
:
absolute
;
}
&
-item
{
overflow
:
hidden
;
margin
:
$list-item-margin
;
}
}
src/packages/virtuallist/virtuallist.taro.tsx
浏览文件 @
3f6ea9c5
import
React
,
{
FunctionComponent
,
useCallback
,
useEffect
,
useRef
,
useState
,
useCallback
,
}
from
'
react
'
import
{
ScrollView
}
from
'
@tarojs/components
'
import
Taro
from
'
@tarojs/taro
'
import
{
useConfig
}
from
'
@/packages/configprovider/configprovider.taro
'
import
{
BasicVirtualListProps
,
VirtualListState
,
PositionType
}
from
'
./type
'
import
{
initPositinoCache
,
getListTotalSize
,
binarySearch
,
getEndIndex
,
updateItemSize
,
}
from
'
./utils
'
export
type
VirtualListProps
=
BasicVirtualListProps
const
defaultProps
=
{}
as
VirtualListProps
import
{
VirtualListState
,
PositionType
}
from
'
./type
'
import
{
initPositinoCache
,
binarySearch
,
updateItemSize
}
from
'
./utils
'
export
type
VirtualListProps
=
{
className
?:
string
|
any
style
?:
React
.
CSSProperties
sourceData
:
any
// 获取数据
containerSize
?:
number
// 容器大小
ItemRender
?:
any
// virtual 列表父节点渲染的函数,默认为 (items, ref) => <div ref={ref}>{items}</div>
itemEqualSize
?:
boolean
// item 固定大小,默认是true
itemSize
?:
number
// 预估元素高度
overscan
?:
number
// 除了视窗里面默认的元素, 还需要额外渲染的, 避免滚动过快, 渲染不及时,默认是2
onScroll
?:
(...
args
:
any
[])
=>
any
// 滑动到底部执行的函数
key
?:
any
// 遍历时生成item 的唯一key,默认是index,建议传入resources的数据具体的某个唯一值的字段
locale
?:
any
}
const
defaultProps
=
{
sourceData
:
[],
itemSize
:
66
,
itemEqualSize
:
true
,
overscan
:
2
,
}
as
VirtualListProps
const
clientHeight
=
Taro
.
getSystemInfoSync
().
windowHeight
-
5
||
667
const
clientWidth
=
Taro
.
getSystemInfoSync
().
windowWidth
||
375
export
const
VirtualList
:
FunctionComponent
<
VirtualListProps
&
React
.
HTMLAttributes
<
HTMLDivElement
>
...
...
@@ -24,27 +40,29 @@ export const VirtualList: FunctionComponent<
const
{
sourceData
=
[],
ItemRender
,
itemSize
=
66
,
itemEqualSize
=
true
,
itemSize
=
200
,
horizontal
=
false
,
overscan
=
2
,
key
,
handleScroll
,
onScroll
,
className
,
containerSize
,
containerSize
=
clientHeight
,
...
rest
}
=
props
const
sizeKey
=
horizontal
?
'
width
'
:
'
height
'
const
scrollKey
=
horizontal
?
'
scrollLeft
'
:
'
scrollTop
'
const
offsetKey
=
horizontal
?
'
left
'
:
'
top
'
// const sizeKey = horizontal ? 'width' : 'height'
// const scrollKey = horizontal ? 'scrollLeft' : 'scrollTop'
// const offsetKey = horizontal ? 'left' : 'top'
const
[
startOffset
,
setStartOffset
]
=
useState
(
0
)
const
[
start
,
setStart
]
=
useState
(
0
)
const
[
list
,
setList
]
=
useState
(
sourceData
.
slice
())
const
{
locale
}
=
useConfig
()
// 虚拟列表容器ref
const
scrollRef
=
useRef
<
HTMLDivElement
>
(
null
)
// 虚拟列表显示区域ref
const
itemsRef
=
useRef
<
HTML
UList
Element
>
(
null
)
const
firstItemRef
=
useRef
<
HTMLLIElement
>
(
null
)
const
itemsRef
=
useRef
<
HTML
Div
Element
>
(
null
)
const
firstItemRef
=
useRef
(
null
)
// 列表位置信息
const
[
positions
,
setPositions
]
=
useState
<
PositionType
[]
>
([
{
...
...
@@ -57,10 +75,7 @@ export const VirtualList: FunctionComponent<
right
:
0
,
},
])
// 列表总大小
const
[
listTotalSize
,
setListTotalSize
]
=
useState
<
number
>
(
99999999
)
// 可视区域条数
const
[
visibleCount
,
setVisibleCount
]
=
useState
<
number
>
(
0
)
const
[
offSetSize
,
setOffSetSize
]
=
useState
<
number
>
(
containerSize
||
0
)
const
[
options
,
setOptions
]
=
useState
<
VirtualListState
>
({
startOffset
:
0
,
// 可视区域距离顶部的偏移量
...
...
@@ -69,97 +84,88 @@ export const VirtualList: FunctionComponent<
endIndex
:
10
,
// 可视区域结束索引
})
// 列表位置信息
useEffect
(()
=>
{
const
pos
=
initPositinoCache
(
itemSize
,
sourceData
.
length
)
setPositions
(
pos
)
const
totalSize
=
getListTotalSize
(
pos
,
horizontal
)
setListTotalSize
(
totalSize
)
},
[
sourceData
,
itemSize
,
horizontal
])
const
getElement
=
useCallback
(()
=>
{
return
scrollRef
.
current
?.
parentElement
||
document
.
body
},
[])
if
(
sourceData
.
length
)
{
setList
(
sourceData
.
slice
())
}
},
[
sourceData
])
// 初始计算可视区域展示数量
useEffect
(()
=>
{
setPositions
((
options
)
=>
{
return
{
...
options
,
endIndex
:
visibleCount
()
}
})
},
[
itemSize
,
overscan
,
offSetSize
])
useEffect
(()
=>
{
if
(
containerSize
)
return
const
size
=
horizontal
?
getElement
().
offsetWidth
:
getElement
().
offsetHeight
setOffSetSize
(
size
)
},
[
getElement
,
horizontal
,
containerSize
])
setOffSetSize
(
getContainerHeight
())
},
[
containerSize
])
useEffect
(()
=>
{
// 初始-计算visibleCount
if
(
offSetSize
===
0
)
return
const
count
=
Math
.
ceil
(
offSetSize
/
itemSize
)
+
overscan
const
pos
=
initPositinoCache
(
itemSize
,
sourceData
.
length
)
setVisibleCount
(
count
)
setOptions
((
options
)
=>
{
return
{
...
options
,
endIndex
:
count
}
})
},
[
getElement
,
horizontal
,
itemSize
,
overscan
,
offSetSize
])
setPositions
(
pos
)
},
[
itemSize
,
sourceData
])
// 可视区域总高度
const
getContainerHeight
=
()
=>
{
//初始首页列表高度
const
initH
=
itemSize
*
sourceData
.
length
//未设置containerSize高度,判断首页高度小于设备高度时,滚动容器高度为首页数据高度,减5为分页触发的偏移量
let
containerH
=
initH
<
clientHeight
?
initH
+
overscan
*
itemSize
-
5
:
Math
.
min
(
containerSize
,
clientHeight
)
return
containerH
// Math.min(containerSize, clientHeight)
}
// 可视区域条数
const
visibleCount
=
()
=>
{
return
Math
.
ceil
(
getContainerHeight
()
/
itemSize
)
+
overscan
}
const
end
=
()
=>
{
return
start
+
visibleCount
()
}
const
listHeight
=
()
=>
{
return
list
.
length
*
itemSize
}
const
visibleData
=
()
=>
{
return
list
.
slice
(
start
,
Math
.
min
(
end
(),
list
.
length
))
}
const
updateTotalSize
=
useCallback
(()
=>
{
if
(
!
itemsRef
.
current
)
return
const
items
:
HTMLCollection
=
itemsRef
.
current
.
children
if
(
!
items
.
length
)
return
// 更新缓存
updateItemSize
(
positions
,
items
,
sizeKey
)
const
totalSize
=
getListTotalSize
(
positions
,
horizontal
)
setListTotalSize
(
totalSize
)
},
[
positions
,
sizeKey
,
horizontal
])
const
scroll
=
useCallback
(()
=>
{
requestAnimationFrame
((
e
)
=>
{
const
scrollSize
=
getElement
()[
scrollKey
]
const
startIndex
=
binarySearch
(
positions
,
scrollSize
,
horizontal
)
const
overStart
=
startIndex
-
overscan
>
-
1
?
startIndex
-
overscan
:
0
// const offSetSize = horizontal ? getElement().offsetWidth : getElement().offsetHeight
if
(
!
itemEqualSize
)
{
updateTotalSize
()
}
const
endIndex
=
getEndIndex
({
sourceData
,
startIndex
,
visibleCount
,
itemEqualSize
,
positions
,
offSetSize
,
sizeKey
,
overscan
,
})
const
startOffset
=
positions
[
startIndex
][
offsetKey
]
as
number
setOptions
({
startOffset
,
startIndex
,
overStart
,
endIndex
})
// 无限下滑
if
(
endIndex
>
sourceData
.
length
-
1
)
{
if
(
onScroll
)
{
onScroll
()
}
else
if
(
handleScroll
)
{
handleScroll
()
}
}
})
},
[
positions
,
getElement
,
sourceData
,
visibleCount
,
itemEqualSize
,
updateTotalSize
,
offsetKey
,
sizeKey
,
scrollKey
,
horizontal
,
overscan
,
handleScroll
,
offSetSize
,
])
updateItemSize
(
positions
,
items
,
'
height
'
)
},
[
positions
])
useEffect
(()
=>
{
const
element
=
getElement
()
element
.
addEventListener
(
'
scroll
'
,
scroll
,
false
)
return
()
=>
{
element
.
removeEventListener
(
'
scroll
'
,
scroll
,
false
)
// 滚动监听
const
listScroll
=
(
e
:
any
)
=>
{
const
scrollTop
=
e
.
detail
?
e
.
detail
.
scrollTop
:
e
.
target
.
scrollTop
const
scrollSize
=
Math
.
floor
(
scrollTop
)
const
startIndex
=
binarySearch
(
positions
,
scrollSize
,
false
)
const
overStart
=
startIndex
-
overscan
>
-
1
?
startIndex
-
overscan
:
0
const
endIndex
=
end
()
if
(
!
itemEqualSize
)
{
updateTotalSize
()
}
setStart
(
Math
.
floor
(
scrollTop
/
itemSize
))
setOptions
({
startOffset
,
startIndex
,
overStart
,
endIndex
})
if
(
end
()
>
list
.
length
-
1
)
{
onScroll
&&
onScroll
()
}
},
[
getElement
,
scroll
])
setStartOffset
(
scrollTop
-
(
scrollTop
%
itemSize
))
}
return
(
<
div
...
...
@@ -168,60 +174,52 @@ export const VirtualList: FunctionComponent<
}
{
...
rest
}
style
=
{
{
[
sizeKey
]
:
containerSize
?
`
${
offSetSize
}
px`
:
''
,
height
:
containerSize
?
`
${
offSetSize
}
px`
:
''
,
}
}
>
<
div
<
ScrollView
scrollTop
=
{
0
}
scrollY
ref
=
{
scrollRef
}
className
=
{
horizontal
?
'
nut-horizontal-box
'
:
'
nut-vertical-box
'
}
className
=
"nut-virtuallist"
style
=
{
{
position
:
'
relative
'
,
[
sizeKey
]:
`
${
listTotalSize
}
px`
,
height
:
`
${
getContainerHeight
()}
px`
,
}
}
onScroll
=
{
listScroll
}
>
<
ul
className
=
{
horizontal
?
'
nut-virtuallist-items nut-horizontal-items
'
:
'
nut-virtuallist-items nut-vertical-items
'
}
<
div
className
=
"nut-virtuallist-phantom"
style
=
{
{
height
:
`
${
listHeight
()}
px`
}
}
/>
<
div
className
=
"nut-virtuallist-container"
ref
=
{
itemsRef
}
style
=
{
{
transform
:
horizontal
?
`translate3d(
${
options
.
startOffset
}
px,0,0)`
:
`translate3d(0,
${
options
.
startOffset
}
px,0)`
,
}
}
style
=
{
{
transform
:
`translate3d(0,
${
startOffset
}
px, 0)`
}
}
>
{
sourceData
.
slice
(
options
.
overStart
,
options
.
endIndex
)
.
map
((
data
,
index
)
=>
{
const
{
startIndex
,
overStart
}
=
options
const
dataIndex
=
overStart
+
index
const
styleVal
=
dataIndex
<
startIndex
?
'
none
'
:
'
block
'
const
keyVal
=
key
&&
data
[
key
]
?
data
[
key
]
:
dataIndex
return
(
<
li
ref
=
{
dataIndex
===
0
?
firstItemRef
:
null
}
data
-
index
=
{
`
${
dataIndex
}
`
}
className
=
{
dataIndex
%
2
===
0
?
'
nut-virtuallist-item even
'
:
'
nut-virtuallist-item odd
'
}
key
=
{
`
${
keyVal
}
`
}
style
=
{
{
display
:
styleVal
}
}
>
{
ItemRender
?
(
<
ItemRender
data
=
{
data
}
index
=
{
`
${
dataIndex
}
`
}
/>
)
:
(
data
)
}
</
li
>
)
})
}
</
ul
>
</
div
>
{
visibleData
().
map
((
data
:
any
,
index
:
number
)
=>
{
const
{
overStart
}
=
options
const
dataIndex
=
overStart
+
index
const
keyVal
=
key
&&
data
[
key
]
?
data
[
key
]
:
dataIndex
return
(
<
div
ref
=
{
dataIndex
===
0
?
firstItemRef
:
null
}
data
-
index
=
{
`
${
dataIndex
}
`
}
className
=
"nut-virtuallist-item"
key
=
{
`
${
data
}
`
}
style
=
{
{
height
:
`
${
itemEqualSize
?
`
${
itemSize
}
px`
:
'
auto
'
}
`
,
}
}
>
{
ItemRender
?
(
<
ItemRender
data
=
{
data
}
index
=
{
`
${
index
}
`
}
/>
)
:
(
data
)
}
</
div
>
)
})
}
</
div
>
</
ScrollView
>
</
div
>
)
}
...
...
src/sites/mobile-taro/src/app.config.ts
浏览文件 @
3f6ea9c5
...
...
@@ -87,6 +87,7 @@ const subPackages = [
'
pages/table/index
'
,
'
pages/tag/index
'
,
'
pages/trendarrow/index
'
,
'
pages/virtuallist/index
'
,
'
pages/watermark/index
'
,
],
},
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录