前端页面样式修改的时候,要用到瀑布流样式,本来是想用flex直接换行显示,试了发现并不是想要的效果;真正的瀑布流是每项的宽度固定,高度不固定,自动填充到高度最小的列下面,还具有有上拉加载更多功能;网上大部分是js实现的瀑布流,react方式实现的比较少,这里记录一下
- 用数组记录每列高度,遍历item数组,将item添加到高度最小的列下面;
renderItemList = () => {//上面图片,下面文字;图片为正方形或长方形,文字一行或两行 const { itemList } = this.state; let columnHeights = [0, 0];//记录每列的高度 return itemList.map((item, index) => { const width = (document.body.clientWidth - 80)/2;//计算图片宽度 const height = (item.type==3) ? width : width * (200/335);//计算图片高度 let columIndex = columnHeights[0] > columnHeights[1] ? 1 : 0;//最小高度列 let top = columnHeights[columIndex];//item的top设置为最小高度列的高度 let left = columIndex==0 ? 0 : (document.body.clientWidth - 80)/2 + 20;//item的left根据最小高度列设置 const textHeight = Math.min(this.getTextSize(item.name).height, 72);//文字高度不同的话,需要计算文字高度 columnHeights[columIndex] += (height + 96 + textHeight + 24);//更新最小高度列的高度,内容高度+间隔 //item使用absolute定位,根据计算出来的top和left设置位置 return <div onClick={onClick} style={{width, top, left, position: 'absolute'}}> <img src={item.image} style={{height}} /> <div> {item.name} </div> </div> }); } getTextSize = (text) => { var span = document.createElement('span') var dom = document.createElement('div') dom.style.width = (document.body.clientWidth - 80)/2 - 40 + 'px'; var result = {} result.width = span.offsetWidth result.height = span.offsetHeight span.style.visibility = "hidden"; span.style.fontSize = '28px'; span.style.lineHeight = '36px';//最好设置行高 方便项目中计算 span.style.fontFamily = 'PingFangSC-Regular, PingFang SC'; span.style.display = 'inline-block'; dom.appendChild(span) document.body.appendChild(dom) if (typeof span.textContent !== 'undefined') { span.textContent = text } else { span.innerText = text } result.width = parseFloat(window.getComputedStyle(span).width) - result.width result.height = parseFloat(window.getComputedStyle(span).height) - result.height document.body.removeChild(dom);//删除节点 // console.log('text size', text, result); return result }
- 设置父元素position为relative,计算父元素高度并设置;
itemContainerWidth = document.body.clientWidth - 60; let columnHeights = [0, 0]; itemList.forEach((item, index) => { const imgWidth = (document.body.clientWidth - 80)/2; const imgHeight = (item.type==3) ? imgWidth : imgWidth * (200/335); let columIndex = columnHeights[0] > columnHeights[1] ? 1 : 0; const textHeight = Math.min(this.getTextSize(item.name).height, 72); columnHeights[columIndex] += (imgHeight + 96 + textHeight + 24); }); height = Math.max(columnHeights[0], columnHeights[1]) - 24;
<div style={{ width: itemContainerWidth, height, position: 'relative' }}> {this.renderItemList()} </div>
- 上拉加载更多,原来使用的都是列表,需要在外面包裹一层组件,这里发现不适合,所以改用自定义监听页面滚动事件判断;
componentDidMount() { window.addEventListener('scroll', this.handleScroll, true); } componentWillUnmount() { window.removeEventListener('scroll', this.handleScroll, true); } handleScroll = () => { const indexDiv = document.querySelector('#example .container > div'); if (!indexDiv) return; if (indexDiv.scrollTop > indexDiv.scrollHeight - indexDiv.clientHeight - 200) { const { pageNo, hasMore, isLoading } = this.state; if (hasMore && !isLoading) { this.getSmartRecommendList(pageNo + 1); } } };
问题
- 内容显示不全,因为absolute元素不会占用空间,导致父元素高度为0,需要在render里面,计算所有行最大高度,设置为容器的高度;
- 直接使用span计算文字高度不对,需要在外面包裹一个div,指定div宽度为列的宽度;
- 在didMount里写window.addEventListener的scroll方法无效,需要把最后一个参数设置为true,window.addEventListener('scroll', this.handleScroll, true)在捕获阶段而不是在冒泡阶段执行事件;
参考链接
https://blog.csdn.net/qq_45473786/article/details/105814902
https://blog.csdn.net/lhz_19/article/details/120413374