虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术。虚拟列表是对长列表场景一种常见的优化,毕竟很少有人在列表中渲染上百个子元素,只需要在滚动条横向或纵向滚动时将可视区域内的元素渲染出即可。
开发中遇到的问题
1.长列表中的图片要保持原图片相同的比例,那纵向滚动在宽度不变的情况下,每张图片的高度就是动态的,当该列表项高度发生了变化,会影响该列表项及其之后所有列表项的位置信息。
2.图片width,height必须在图片加载完成后才能获得.
解决方案
我们使用react-virtualized中list组件,官方给出的例子
import React from 'react';
import ReactDOM from 'react-dom';
import {List} from 'react-virtualized';
// List data as an array of strings
const list = [
'Brian Vaughn',
// And so on...
];
function rowRenderer({
key, // Unique key within array of rows
index, // Index of row within collection
isScrolling, // The List is currently being scrolled
isVisible, // This row is visible within the List (eg it is not an overscanned row)
style, // Style object to be applied to row (to position it)
}) {
return (
<div key={key} style={style}>
{list[index]}
</div>
);
}
// Render your list
ReactDOM.render(
<List
width={300}
height={300}
rowCount={list.length}
rowHeight={20}
rowRenderer={rowRenderer}
/>,
document.getElementById('example'),
);
其中rowHeight是每一行的高度,可以传入固定高度也可以传入function。每次子元素高度改变需要调用recomputeRowHeights方法,指定索引后重新计算行高度和偏移量。
具体实现
const ImgHeightComponent = ({ imgUrl, onHeightReady, height, width }) => {
const [style, setStyle] = useState({
height,
width,
display: 'block',
})
const getImgWithAndHeight = (url) => {
return new Promise((resolve, reject) => {
var img = new Image()
// 改变图片的src
img.src = url
let set = null
const onload = () => {
if (img.width || img.height) {
//图片加载完成
clearInterval(set)
resolve({ width: img.width, height: img.height })
}
}
set = setInterval(onload, 40)
})
}
useEffect(() => {
getImgWithAndHeight(imgUrl).then((size) => {
const currentHeight = size.height * (width / size.width)
setStyle({
height: currentHeight,
width: width,
display: 'block',
})
onHeightReady(currentHeight)
})
}, [])
return <img src={imgUrl} alt='' style={style} />
}
先写一个获取图片高度的组件,通过定时循环检测获取并计算出高度传给父组件。
import React, { useState, useEffect, useRef } from 'react'
import styles from './index.scss'
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'
import { List } from 'react-virtualized/dist/commonjs/List'
export default class DocumentStudy extends React.Component {
constructor(props) {
super(props)
this.state = {
list: [],
heights: [],
autoWidth:900,
autoHeight: 300
}
}
handleHeightReady = (height, index) => {
this.setState(
(state) => {
const flag = state.heights.some((item) => item.index === index)
if (!flag) {
return {
heights: [
...state.heights,
{
index,
height,
},
],
}
}
return {
heights: state.heights,
}
},
() => {
this.listRef.recomputeRowHeights(index)
},
)
}
getRowHeight = ({ index }) => {
const row = this.state.heights.find((item) => item.index === index)
return row ? row.height : this.state.autoHeight
}
renderItem = ({ index, key, style }) => {
const { list, autoWidth, autoHeight } = this.state
if (this.state.heights.find((item) => item.index === index)) {
return (
<div key={key} style={style}>
<img src={list[index].imgUrl} alt='' style={{width: '100%'}}/>
</div>
)
}
return (
<div key={key} style={style}>
<ImgHeightComponent
imgUrl={list[index].imgUrl}
width={autoWidth}
height={autoHeight}
onHeightReady={(height) => {
this.handleHeightReady(height, index)
}}
/>
</div>
)
}
render() {
const { list } = this.state
return (
<>
<div style={{ height: 1000 }}>
<AutoSizer>
{({ width, height }) => (
<List
ref={(ref) => (this.listRef = ref)}
width={width}
height={height}
overscanRowCount={10}
rowCount={list.length}
rowRenderer={this.renderItem}
rowHeight={this.getRowHeight}
/>
)}
</AutoSizer>
</div>
</>
)
}
}
父组件通过handleHeightReady方法收集所有图片的高度,并在每一次高度改变调用List组件的recomputeRowHeights方法通知组件重新计算高度和偏移。到这里基本已经解决遇到的问题。
实际效果
小结
目前只是使用react-virtualized来完成图片长列表实现,具体react-virtualized内部实现还需要进一步研究。
以上就是用react-virtualized实现图片动态高度长列表的详细内容,更多关于react virtualized长列表的资料请关注极客世界其它相关文章! |
请发表评论