全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2023-10-15_一文教你实现小红书响应式瀑布流

您的位置:首页 >> 新闻 >> 行业资讯

一文教你实现小红书响应式瀑布流 点击小卡片,参与粉丝专属福利!!前言瀑布流布局,不管是在pc端还是手机端都很常见,但是我们通常都是列固定。今天来实现一下小红书的响应式瀑布流。后面有完整代码。 正文还是先来看看效果 原理: ?对每一个item都使用绝对定位,left和top都是0,最后根据容器大小、item的height通过计算来确定item的transform值 ?接下来从易到难来解析一下实现 初始化数据列表怎么可以没有数据,先来初始化一下数据 确定列数及列大小由于是响应式,我们要去监听列表容器的大小变化并记录容器宽度,这样才能做出相应的处理 code.png根据监听得到的容器大小信息,我们可以确定每行个数和每一个item的宽度 确定列表中item位置确定item的位置,那么我们只需要确定transform值就可以了,这也是整个实现的核心。我们还需要解决几个问题 对还不知道item的高度,怎么确定我们希望把新的item放置在最低高度的旧item下方,这样全部渲染完每一列的高度才不会相差很多。code.pngitem放置的原理图,放置在当前最低高度的下面 更新item高度当我们第一次运行的时候,每一个item的高度一定都是随机生成的,现在我们要确定item的实际高度。在这里我们还可优化一下,使用懒加载和底部加载,提升性能。这两个在这里就不讲了,不懂的可以去搜一下。 下面代码一共两个作用 记录容器滚动值,传递给每一个item,用于判断是否加载图片。判断是否请求添加数据根据滚动值判断是否加载图片,加载图片后触发父亲更新高度函数 父亲接受到新的高度并更新高度,然后去重新计算transform值和item高度 完整代码divstyle={{ display:'flex' }} div左侧导航栏/div WaterfallFlow/ /div //瀑布流 importReact,{useState,useEffect,useRef,useMemo,useCallback,Fragment}from'react' importimg1from'../assets/imgs/1.jpg' importimg2from'../assets/imgs/2.jpg' importimg12from'../assets/imgs/12.png' import'./WaterfallFlow.scss' interfaceWaterfallFlowItemProps{ /**显示边界*/ showBorder:number src:string, title:string, style:React.CSSProperties, unitWidth:number, index:number, sizeChange?:(height:number,index:number)=void } constWaterfallFlowItem=(props:WaterfallFlowItemProps)={ let{src,title,style={},sizeChange=()={},unitWidth,index,showBorder}=props letframeDom=useRef(null) let[isLoading,setIsLoading]=useState(false) let[imgInfo,setImgInfo]=useState{ height:number, width:number }({ height:1, width:1 }) letimgDom=useRefany(null) /**离父亲上边框的距离*/ lettop=useMemo(()={ lety=style.transform?Number(style.transform?.substring(style.transform.indexOf(',',0)+1,style.transform.length-3)):undefined returny },[style]) /**是否加载图片*/ letisImgShow=useMemo(()={ if(top===undefined){ returnfalse } if(top=showBorder){ returntrue }else{ returnfalse } },[top,showBorder]) /**符合条件懒加载图片*/ useEffect(()={ if(imgDom.current===null||src===''||isImgShow===false){ return } letimg=newImage(); img.src= img.onload=()={ setImgInfo({ height:img.height, width:img.width, }) setIsLoading(true) } imgDom.current.src=src },[src,isImgShow]) useEffect(()={ //通过宽度比例获取图片高度 letheight=imgInfo.height*(unitWidth/imgInfo.width); if(isLoading){ //加40是因为下方文字部分高度为40,可以自己设置 sizeChange(height+40,index) } },[imgInfo,index,unitWidth,isLoading,sizeChange]) return( divclassName='WaterfallItem'style={{ ...style, }}ref={frameDom} divclassName='WaterfallItem__img' { imgref={imgDom}style={{ visibility:isLoading?'visible':'hidden' }}/ } /div divclassName='WaterfallItem__name' {titletitle} { !isLoading&& divclassName='WaterfallItem__name--placeholder'/div } /div /div ) } exportdefaultfunctionWaterfallFlow(){ /**滚动的父元素*/ constscrollParent=useRef(null) /**向上滚动的距离*/ const[scrollTop,setScrollTop]=useState(0) /**数据列表*/ const[list,setList]=useStateWaterfallFlowItemProps[] constwaterfallFlowDom=useRef(null) /**样式列表*/ const[styleList,setStyleList]=useStateReact.CSSProperties[]([]) /**自定义骨架屏高度*/ letheightList=[170,230,300]; /**到达底部*/ letisLoadingData=useRef(false); /**生成随机数*/ constcreateRandomNum=useCallback((min:number,max:number):number={ returnMath.floor(Math.random()*(max-min+1))+ },[]) letwaterfallFlowListInfo=useRef{ left:number, top:number, height:number, }[]([]) /**当前容器信息*/ let[frameInfo,setFrameInfoInfo]=useState{ width:number, }({width:0}) /**每行个数*/ letrowsNum=useMemo(()={ letwidth=frameInfo.width||0; if(width=1200){ return6 }elseif(width=768width=1199){ return4 }else{ return2 } },[frameInfo]) /**每一个的宽度*/ letunitWidth=useMemo(()={ return(frameInfo.width-(rowsNum-1)*10)/rowsNum; },[rowsNum,frameInfo]) /**获取位置*/ constgetStyleList=useCallback(()={ lettemporaryStyleList:React.CSSProperties[]=styleList; /**目前最下一行的index*/ letbottomItemIndex= for(leti=0;ilist.length;i++){ //原本应对应的行数 letcurrentRow=Math.floor(i/rowsNum); // letremainder=i%rowsNum+1; //最低item下标 letminHeightInd=0; //最低高度 letminHeight=9999999999; //寻找最低高度的下标 if(currentRow===0){ bottomItemIndex[i]= }else{ for(letj=0;jbottomItemIndex.length;j++){ if(waterfallFlowListInfo.current[bottomItemIndex[j]].top+waterfallFlowListInfo.current[bottomItemIndex[j]].heightminHeight){ minHeightInd= minHeight=waterfallFlowListInfo.current[bottomItemIndex[j]].top+waterfallFlowListInfo.current[bottomItemIndex[j]].height } } bottomItemIndex[minHeightInd]= } if(waterfallFlowListInfo.current[i]===undefined){ waterfallFlowListInfo.current[i]={}as } //第一行特殊处理,一定是从左到右铺的 if(currentRow===0){ if(remainder===1){ waterfallFlowListInfo.current[i].left=0; }else{ waterfallFlowListInfo.current[i].left= waterfallFlowListInfo.current[i-1].left+unitWidth+10; } waterfallFlowListInfo.current[i].top=0; } //剩下的行数,铺在当前最低高度下面 else{ waterfallFlowListInfo.current[i].left=waterfallFlowListInfo.current[minHeightInd].left waterfallFlowListInfo.current[i].top=minHeight+25; } //是否已经有高度,有高度使用已有高度,否则随机生成 waterfallFlowListInfo.current[i].height=waterfallFlowListInfo.current[i].height||heightList[createRandomNum(0,2)]; temporaryStyleList[i]={ transform:`translate(${waterfallFlowListInfo.current[i].left}px,${waterfallFlowListInfo.current[i].top}px)`, width:`${unitWidth}px`, height:waterfallFlowListInfo.current[i].height } } return[...temporaryStyleList] },[unitWidth,rowsNum,list]) /**图片加载完更新高度*/ constonSizeChange=useCallback((height:number,index:number)={ if(waterfallFlowListInfo.current[index]===undefined){ waterfallFlowListInfo.current[index]={}as } waterfallFlowListInfo.current[index].height=height; setStyleList(getStyleList()) },[getStyleList]) /**大小、数量发生变化时触发*/ useEffect(()={ setStyleList(getStyleList()) },[unitWidth,rowsNum,list]) /**初始化请求数据*/ useEffect(()={ isLoadingData.current=true; letdata:any=[] //为了出现骨架屏 for(leti=0;i50;i++){ letitem; item={ src:"", title:"" } data.push(item); } setList(data) data= for(leti=0;i50;i++){ letitem; if(i%3==0){ item={ src:img1, title:`第${i}个Item` } }elseif(i%3==1){ item={ src:img2, title:`第${i}个Item` } }else{ item={ src:img12, title:`第${i}个Item` } } data.push(item); } //模拟请求 setTimeout(()={ setList(data) isLoadingData.current=false; },1200) },[]) constonResize=useCallback(()={ if(waterfallFlowDom.current===null){ return } setFrameInfoInfo({ width:(waterfallFlowDom.currentasHTMLDivElement).getBoundingClientRect().width }) },[]) /**监听列表容器大小变化*/ useEffect(()={ if(waterfallFlowDom.current===null){ return } constresizeObserver=newResizeObserver(entries={ onResize() resizeObserver.observe(waterfallFlowDom.current); return()={ resizeObserver.disconnect() } },[]) constonScroll=useCallback(()={ //记录滚动值 setScrollTop((scrollParent.currentasany).scrollTop) lettop=(scrollParent.currentasany).scrollTop letclientHeight=(scrollParent.currentasany).clientHeight letscrollHeight=(scrollParent.currentasany).scrollHeight //做底部加载 if(scrollHeight-clientHeight/3=top+clientHeightisLoadingData.current===false){ isLoadingData.current=true; letdata:any=[] for(leti=0;i50;i++){ letitem; if(i%3==0){ item={ src:img1, title:`第${i}个Item` } }elseif(i%3==1){ item={ src:img2, title:`第${i}个Item` } }else{ item={ src:img12, title:`第${i}个Item` } } data.push(item); } //请求数据并加载 setTimeout(()={ isLoadingData.current=false setList((lastData)={ return[...lastData,...data] }) },1200) } },[]) /**监听滚动*/ useEffect(()={ (scrollParent.currentasany).addEventListener('scroll',onScroll); return()={ (scrollParent.currentasany).removeEventListener('scroll',onScroll); } },[]) return( divclassName='waterfallFlow'ref={scrollParent} divclassName='waterfallFlow__title'响应式瀑布流/div sectionref={waterfallFlowDom}className='waterfallFlow__content' { list.map((item,ind)={ return( divkey={ind} WaterfallFlowItem showBorder={scrollTop+(scrollParent.currentasany).clientHeight} src={item.src} title={item.title} style={styleList[ind]} sizeChange={onSizeChange} unitWidth={unitWidth} index={ind} / /div ) }) } /section /div ) } .waterfallFlow{ flex:1; height:100vh; overflow-y:auto; &__title{ text-align:center; } &__content{ position:relative; box-sizing:border-box; background-color: } } .WaterfallItem{ display:flex; flex-direction:column; position:absolute; top:0px; left:0px; border-radius:20px; background-color:#FFF; box-sizing:border-box; box-shadow:0px0px12px2pxrgba(0,0,0,0.1); &__img{ flex:1; overflow:hidden; border-radius:20px; background:#f7f7f7; } img{ display:inline-block; height:100%; width:100%; object-fit:cover; border-radius:20px; } &__name{ height:30px; margin-top:10px; padding:"0px5px"; box-sizing:border-box; &--placeholder{ height:20px; background:#fbfbfb } } } 结语感兴趣的可以去试试 如果文章对你有帮助的话欢迎「关注+点赞+收藏」 阅读原文

上一篇:2017-11-09_穿越梗真的玩!不!坏! 下一篇:2024-12-08_影视摄影大师班(2025春季)招生简章

TAG标签:

17
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价