A virtual list is a technique for rendering a portion of a long list of data based on the visible area of a scrolling container element. Virtual lists are a common optimization for long list scenes, since few people would render hundreds of child elements in a list, simply rendering the elements in the visible area while scrolling horizontally or vertically on the scroll bar.

Problems encountered in development

1. The images in the long list should keep the same proportion as the original ones. If the width of the longitudinal scroll is unchanged, the height of each image is dynamic. 2. The image width and height must be obtained after the image is loaded.

The solution

We are using the React-Virtualized list as the official example

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'));Copy the code

RowHeight is the height of each row, which can be passed in either a fixed height or function. The recomputeRowHeights method is called each time the child element height changes, specifying the index and recalculating the row height and offset.

The specific implementation

const ImgHeightComponent = ({ imgUrl, onHeightReady, height, width }) = > {
  const [style, setStyle] = useState({
    height,
    width,
    display: 'block',})const getImgWithAndHeight = (url) = > {
    return new Promise((resolve, reject) = > {
      const img = new Image()
      // Change the SRC of the image
      img.src = url
      let set = null
      const onload = () = > {
        if (img.width || img.height) {
          // The image is loaded
          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} />
}
Copy the code

First write a component to get the height of the image, through the periodic loop detection to get and calculate the height to the parent component.

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> </> ) } }Copy the code

The parent heightready method, handleHeightReady, collects the heights of all images, and calls the List component’s recomputeRowHeights method to notify the component to recalcalculate the heights and offsets each time the heights change. So far we have basically solved the problems we have encountered.

The actual effect

summary

At the moment it is only react-Virtualized to achieve the long list of images but the react-Virtualized internal implementations need to be worked on.