瀑布流的多种实现方式和优缺点

3 篇文章 0 订阅

瀑布流相关
个人建议(供参考)用flex布局+JS计算两列高度来实现。基本思路是flex-box容器下有左右两列容器(对应组件state中的list1和list2数组),从list1开始创建子项,创建后计算左右两列容器的高度来比较,高度小的列对应的list数组就会push一个子项,这样循环到列表项全部创建完为止。(因为商品流的图片区域高度是固定的,影响列表子项容器高度的只有标签,所以不用考虑图片加载完成后才能得到的高度,即不用考虑Image组件的onLoad事件,因此实现起来还是简单的)

注意点:Taro3的setState是异步的,给数组赋值后,获取列的DOM高度要在setState完成后。

目前纯CSS实现的瀑布流布局从multi-columns、flex-box到grid都有一定的局限性。产品要求商品流的是按行的方式排列的,高度不均的变化影响的是子项的水平位置。multi-columns是按列来排列子项,纯CSS的flex-box的瀑布流实现基本也是用列排列子项的思路。现在的grid布局实现瀑布流要基本确定每一个子项的行空间大小。grid有瀑布流布局的草案,可以完美实现瀑布流,但是还没有成为规范。

flex布局+JS计算两列高度来实现瀑布流

import Taro from '@tarojs/taro'
import React, { Component } from 'react'
import { View, Text, Image, ScrollView } from '@tarojs/components'
import { gotoPresaleGoodDetails } from '@/utils/common/presale/index'
import { jumpSpotGoodsDetail } from '@/utils/common/linkUrl/index'
import { imgCdn, storeSign } from '@/utils/conf'
import { addCartApi } from '@/api/pages/cart/index'
import { tip } from '@/src/utils/util'
import './index.scss'

/**
 * @description             公共组件==> 商品列表(预售/NEW/HOT/特供)
 */
export default class GatherListComp extends Component {

  static defaultProps = {
    list: []
  }

  state = {
  }

  componentWillMount() { }
  componentDidMount() { }

  gotoGoodDetails(item) {
    // 预售or现货
    if (item.labelType == 1 || item.skuType == 2 || item.type == 2) {// 现货
      jumpSpotGoodsDetail(item.skuId)
    }
    else if (item.labelType == 2 || item.skuType == 1 || item.type == 1) {// 预售
      gotoPresaleGoodDetails(item.id || item.skuId)
    }
  }

  getIconFlag(item) {
    let flag = ''
    if (item.labelType == 2) {
      flag = 'icon-pre'
    }
    if (item.skuType == 1) {
      flag = 'icon-new'
    }
    if (item.skuType == 2) {
      flag = 'icon-hot'
    }
    if (item.skuType == 3) {
      flag = 'icon-tg'
    }
    return flag
  }

  // 加入购物车
  addShopCarNum(item) {
    Taro.showLoading({ title: '添加中', mask: true })
    addCartApi({ skuId: item.skuId, quantity: 1 }).then(res => {
      Taro.hideLoading()
      tip('成功加入购物车')
    }).catch(res => {
      Taro.hideLoading()
      let errorMsg = res && (res.message || res.error || '')
      tip(errorMsg || '加入购物车失败')
    })
  }

  goodsItemRender (item, index) {
    return (
      <View className='gather-list__item' key={index}>
        <View onClick={this.gotoGoodDetails.bind(this, item)} className={['gather-list__item-icon', this.getIconFlag(item)]}></View>

        <Image onClick={this.gotoGoodDetails.bind(this, item)} className='gather-list__item-pic' src={item.cover} lazyLoad={true}></Image>
        <View className='gather-list__item-wrap'>
          <View>
            <View className='gather-list__item-title ell' onClick={this.gotoGoodDetails.bind(this, item)}>{item.skuName}</View>
            <View className='gather-list__promotion' onClick={this.gotoGoodDetails.bind(this, item)}>
              {Array.isArray(item.tagList) && item.tagList.length ?
                item.tagList.map((tag) => {
                  return (
                    <Text className='gather-list__promotion-item'>{tag}</Text>
                  )
                }) : ''
              }
            </View>
            <View className='gather-list__item-price'>
              {/*促销价或原价*/}
              <Text onClick={this.gotoGoodDetails.bind(this, item)}>
                <Text className='gather-list__price-txt1'>¥</Text>
                <Text className='gather-list__price-txt2'>{item.preSalePrice || item.sellPrice}</Text>
              </Text>
              {/*划线价*/}
              <Text className='gather-list__through-price' onClick={this.gotoGoodDetails.bind(this, item)}>
                <Text>¥</Text>
                <Text>{item.preSalePrice || item.sellPrice}</Text>
              </Text>
            </View>
          </View>
          {/*加购按钮*/}
          {
            item.labelType != 2 ?
              <View className='gather-list__add-icon' onClick={this.addShopCarNum.bind(this, item)}>
                <Text className='gather-list__add-horizontal'></Text>
                <Text className='gather-list__add-vertical'></Text>
              </View>
              : ''
          }
        </View>
      </View>
    )
  }

  render() {
    const { list } = this.props
    let leftData = [] // 左边的数据
    let rightData = [] // 右边的数据
    let leftTagLine = 0
    let rightTagLine = 0
    if (Array.isArray(list) && list.length) {
      list.filter((row, index) =>{
        // todo 暂时写死标签
        let tagList = []
        if (index % 5 == 0) {
          tagList = ['直减20', '满100减20']
        } else if (index % 4 == 0) {
          tagList = ['直减20', '满100有赠品', '满88减20', '两件5折', '三件4折']
        } else if (index % 3 == 0) {
          tagList = ['直减20', '满199有赠品', '满99减15', '满199减40']
        } else if (index % 2 == 0) {
          tagList = ['满100有赠品', '买一送一']
        }
        row.tagList = tagList

        let tagLine = 0 // tag标签占的行数
        if (Array.isArray(row.tagList) && row.tagList.length) {
          let newTagList = []
          // 计算每个标签的宽度
          row.tagList.filter((tag) => {
            let tagWidth = 0
            if (tag) {
              var regChinese = /^[\u4e00-\u9fa5]+$/;
              for (let i = 0; i < tag.length; i++) {
                let oneChar = tag.charAt(i)
                if (regChinese.test(oneChar)){ // 字体大小10px, 中文字体宽度10px
                  tagWidth += 10
                } else {  // 其他字符宽度5.5px
                  tagWidth += 5.5
                }
              }
            }
            tagWidth += 4 + 4 + 4 // 标签的左右内边距 + 右外边距
            newTagList.push({ tag, tagWidth })
          })

          tagLine = calcTagLine(JSON.parse(JSON.stringify(newTagList)), tagLine)
        }

        // 如果左侧的高度小于右侧的高度,往左侧添加数据
        if ((leftTagLine * 23 + 247 * leftData.length) <= (rightTagLine * 23 + 247 * rightData.length)) {
          leftTagLine += tagLine
          leftData.push(row)
        } else {
          rightTagLine += tagLine
          rightData.push(row)
        }
        // console.log(`leftTagLine:${leftTagLine}\t\t\t\t\trightTagLine:${rightTagLine}`)
        // console.log(`leftData:${leftData.length}\t\t\t\t\trightData:${rightData.length}`)
      })
    }
    function calcTagLine (newArr, tagLine) {
      let oneLineWidth = 160 // 每行的宽度
      let isBr = false // 是否换行,true为已换行
      // 换行判断,满足条件换行,行数+1
      function dealIsBr(tagArr = [], startIndex = 1) {
        let widthAdd = 0
        for (let i = 0; tagArr.length >= startIndex && i < startIndex; i++) {
          widthAdd += tagArr[i].tagWidth
        }
        if ((tagArr.length >= startIndex && (widthAdd) >= oneLineWidth)) {
          tagLine += 1
          tagArr.splice(0, startIndex-1)
          isBr = true
        } else if ((tagArr.length === startIndex && (widthAdd) <= oneLineWidth)) {
          tagLine += 1
          tagArr.splice(0, startIndex)
          isBr = true
        }
        if (!isBr && tagArr.length && startIndex >= 0 && startIndex <= tagArr.length && widthAdd) {
          dealIsBr(tagArr, startIndex + 1)
        }
      }
      dealIsBr(newArr)
      if (newArr.length > 0) {
        tagLine = calcTagLine(newArr, tagLine)
      }
      return tagLine
    }

    return (
      <View className='gather-list'>
        <View>
          {leftData.map((item, index) => {
            return (
              this.goodsItemRender(item, index)
            )
          })}
        </View>
        <View>
          {rightData.map((item, index) => {
            return (
              this.goodsItemRender(item, index)
            )
          })}
        </View>
      </View>
    )
  }
}


$img-cdn: "https://xxx.xxx.com";
.gather-list {
  display: flex;
  flex-direction: row;
  .gather-list__item {
    width: 352px;
    margin-right: 14px;
    margin-bottom: 16px;
    box-sizing: border-box;
    position: relative;
    &:nth-of-type(2n) {
      margin-right: 0;
    }
    .gather-list__item-icon {
      position: absolute;
      right: 0;
      top: 0;
      width: 98px;
      height: 50px;
      &.icon-pre {
        background: url($img-cdn + "/toptoy/index/index-icon-pre-v8.png") 0 0 no-repeat;
        background-size: 100% 100%;
      }
      &.icon-hot {
        background: url($img-cdn + "/toptoy/index/index-icon-hot-v8.png") 0 0 no-repeat;
        background-size: 100% 100%;
      }
      &.icon-tg {
        background: url($img-cdn + "/toptoy/index/index-icon-tg-v8.png") 0 0 no-repeat;
        background-size: 100% 100%;
      }
      &.icon-new {
        background: url($img-cdn + "/toptoy/index/index-icon-new-v8.png") 0 0 no-repeat;
        background-size: 100% 100%;
      }
    }
    .gather-list__item-pic {
      width: 352px;
      height: 352px;
    }
    .gather-list__item-wrap {
      background: #ffffff;
      box-sizing: border-box;
      padding: 10px 16px;
      position: relative;
      .gather-list__item-title {
        font-size: 26px;
        font-weight: 400;
        color: #333333;
        line-height: 42px;
      }
      .gather-list__promotion {
        .gather-list__promotion-item {
          display: inline-block;
          padding: 6px 8px;
          line-height: normal;
          background: #FBED00;
          border-radius: 6px;
          margin: 8px 8px 0 0;
          font-size: 20px;
          font-weight: 400;
          color: #DA7100;
          text-align: justify;
          text-justify: newspaper;
          word-break: break-all;
          word-wrap: break-word;
        }
      }
      .gather-list__item-price {
        margin-top: 12px;
        margin-bottom: 16px;
        font-size: 24px;
        line-height: 48px;
        color: #333333;
        font-weight: bold;
        .gather-list__price-txt2 {
          font-size: 32px;
        }
      }
      .gather-list__through-price {
        text-decoration: line-through;
        font-size: 18px;
        font-weight: 400;
        color: #999999;
        margin: 0 8px;
      }
    }
    .gather-list__add-icon {
      position: absolute;
      right: 16px;
      bottom: 27px;
      width: 46px;
      height: 46px;
      background: #000000;
      border-radius: 50%;
      .gather-list__add-horizontal {
        display: inline-block;
        position: absolute;
        top: 22px;
        left: 13px;
        width: 22px;
        height: 3px;
        background: #FBED00;
        border-radius: 2px;
      }
      .gather-list__add-vertical {
        display: inline-block;
        position: absolute;
        top: 13px;
        left: 22px;
        width: 3px;
        height: 22px;
        background: #FBED00;
        border-radius: 2px;
      }
    }
  }
}

实现效果如图在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值