用React实现瀑布流

5,750 阅读1分钟

代码

import React from 'react';
import './Home.css';

class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      imgList: [],
      imgArr: [
        require('../assets/images/100x70.png'),
        require('../assets/images/100x80.png'),
        require('../assets/images/100x90.png'),
        require('../assets/images/100x100.png'),
        require('../assets/images/100x120.png'),
        require('../assets/images/100x150.png'),
        require('../assets/images/100x210.png'),
        require('../assets/images/100x230.png'),
        require('../assets/images/100x250.png'),
      ],
      waterfallImgList: [],
      waterfallImgWidth: 100,
      waterfallImgRight: 10,
      waterfallImgBottom: 10,
      waterfallImgCol: null,
      waterfallDeviationHeight: [],
      time: null,
    }
    this.resize.bind(this);
  }
  componentDidMount() {
    for(let i = 0; i < 100; i ++) {
      this.state.imgList.push(this.state.imgArr[Math.round(Math.random() * 8)]);
    }
    this.calculateWidth();
    this.screenChange();
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.resize);
  }
  resize() {
    if (this.state.time) {
      return;
    }
    const time = setTimeout(() => {
      clearTimeout(this.state.time);
      this.setState({
        waterfallImgCol: null,
        waterfallImgList: [],
        time: null,
      })
      this.calculateWidth();
    }, 300);
    this.setState({ time });
  }
  screenChange() {
    window.addEventListener('resize', () => {
      this.resize();
    });
  }
  calculateWidth() {
    const domWidth = document.getElementById('waterfall').offsetWidth;
    const col = parseInt(domWidth / (this.state.waterfallImgWidth + this.state.waterfallImgRight));
    this.setState({ waterfallImgCol: col });
    const heightList = new Array(col);
    for(let i = 0; i < col; i ++) {
      heightList[i] = 0;
    }
    this.setState({ waterfallDeviationHeight: heightList });
    this.imgPreloading();
  }
  imgPreloading() {
    for(let i = 0; i < this.state.imgList.length; i ++) {
      const img = new Image();
      img.src = this.state.imgList[i];
      img.onload = img.onerror = () => {
        const imgData = {};
        imgData.height = (this.state.waterfallImgWidth / img.width) * img.height;
        imgData.src = this.state.imgList[i];
        this.state.waterfallImgList.push(imgData);
        this.rankImg(imgData);
      }
    }
  }
  rankImg(imgData) {
    const { waterfallImgWidth, waterfallImgRight, waterfallImgBottom, waterfallDeviationHeight } = this.state;
    const minIndex = this.filterMin();
    imgData.top = waterfallDeviationHeight[minIndex];
    imgData.left = minIndex * (waterfallImgWidth + waterfallImgRight);
    let heightList = waterfallDeviationHeight;
    heightList[minIndex] += imgData.height + waterfallImgBottom;
    this.setState({ waterfallDeviationHeight: heightList, waterfallImgList: this.state.waterfallImgList });
  }
  filterMin() {
    const { waterfallDeviationHeight } = this.state;
    const min = Math.min.apply(null, [...waterfallDeviationHeight]);
    return this.state.waterfallDeviationHeight.indexOf(min);
  }
  render() {
    return <div className="waterfall" id="waterfall">
      { this.state.waterfallImgList.map((img, index) => {
        const {top, left, height} = img;
        const {waterfallImgWidth} = this.state;
        return <div className="waterfall-item" key={index} style={{ width: `${waterfallImgWidth}px`, height: height, top: `${top}px`, left: `${left}px` }}>
          <img src={img.src} alt=""/>
        </div>
      })}
    </div>
  }
}

export default Home;

效果

waterfall

思路

1.根据窗口宽度,计算列数n;waterfallDeviationHeight.length = n; waterfallDeviationHeight 记录每一列的当前高度
2.根据加载的图片原始宽高比以及预先设定的宽度计算图片高度;
3.遍历所有图片,waterfallDeviationHeight;更新当前列高(原始列高 + 当前图片高度)。

文档只做个人学习用,部分思路有参考其他博主的文章,勿喷。