小程序使用Grid和css变量实现瀑布流布局
发布于 4 年前 作者 ztan 973 次浏览 来自 分享

前言

要实现如下瀑布流效果,动态图片,动态高度

我知道使用JS能够实现完美瀑布流,但小程序不比web,坑点会比较多,因此我决定先使用CSS看能不能解决,最后实在不行在使用JS来实现

根据网上的教程尝试使用css的方式(column和flex)实现效果,但排列顺序都是竖排而不是横排,不符合产品需求,实现效果如下

GRID瀑布流

如此看来只剩grid这一条路了,还好成功了

基础版

下列摘自网上使用GRID实现瀑布流的实例

模板

<view class="waterfall">
  <view class="item">1view>

<view class=“item”>2view> <view class=“item”>3view> <view class=“item”>4view> <view class=“item”>5view> <view class=“item”>6view> … view>

wxss

.waterfall {
  display: grid;
  grid-template-columns: repeat(2, 1fr);  // 指定两列,自动宽度
  grid-gap: 0.25em; // 横向,纵向间隔
  grid-auto-flow: row dense;  // 是否自动补齐空白
  grid-auto-rows: 20px;  // base高度,grid-row基于此运算
}
.waterfall .item {
  width: 100%;
  background: #222;
  color: #ddd;
}
.waterfall .item:nth-of-type(3n+1) {
  grid-row: auto / span 5;
}
.waterfall .item:nth-of-type(3n+2) {
  grid-row: auto / span 6;
}
.waterfall .item:nth-of-type(3n+3) {
  grid-row: auto / span 8;
}

效果

基础版的问题

看看上面的css是如何使用grid实现

.waterfall .item:nth-of-type(3n+1) {
  grid-row: auto / span 5;
}

上述代码指定1,4,7,10...等item的高度,auto为grid自动设置该item的起始位置,span 5则指定该item的高度为grid-auto-rows * 5, grid-auto-rows在CSS的设定中为20px,在源码中我做了说明,它是一个基础高度

.waterfall .item:nth-of-type(3n+2) {
  grid-row: auto / span 6;
}

上述代码指定2,8,11,14...等item的高度,auto为grid自动设置该item的起始位置,span 6则指定该item的高度为grid-auto-rows * 6

.waterfall .item:nth-of-type(3n+3) {
  grid-row: auto / span 8;
}

上述代码指定3,6,12,15...等item的高度,auto为grid自动设置该item的起始位置,span 8则指定该item的高度为grid-auto-rows * 8

基础版虽然看上去基本符合我们的产品需求,但由css可以知道,它的问题是__高度固定__,但业务上我们并不确切知道每个item的高度及所包含的图片的高度。所以接下来我们要解决__动态高度__设定的问题,让每一个item都自动计算自己的高度,而不是通过CSS来手动指定

css变量登场

微信小程序swiper的自适应高度
小程序中使用css var变量,使js可以动态设置css样式属性

上面两篇文章是之前写得关于css变量的一些巧妙的用法,css变量确实能够解决很多之前很棘手的问题,此时我脑海里面迸发出了一个绝佳的IDEA

仔细观察这段css

.waterfall .item:nth-of-type(3n+2) {
  grid-row: auto / span 6;
}

唯一不确定的就是6,对,它应该是一个变量而不是一个恒量,它应该是一个与高度关联的比值,而我们可以通过css变量动态设置这个比值,它大概应该长这样

page{
  --item-span: x  // 需要使用setData设置x值
}

.waterfall .item {
  grid-row: var(--item-span);
}

考虑到需要设置每个item的高度,应该为每一个item设定独立的样式

.waterfall{
  --item-span-1: x;  // 使用setData设置x值
  --item-span-2: y   // 使用setData设置y值
}

.waterfall .item-1 {
  grid-row: var(--item-span-1);
}
.waterfall .item-2 {
  grid-row: var(--item-span-2);
}

原理

到此我们就可以讲通如何实现的原理了

注意:下面的例子使用内联样式代替上面的样式设定,因为内联样式可以由JS动态输出

模板

<page>
  <view class="waterfall" style="{{waterStyle}}">
    <view class="item item-1">...view>

view> <view class=“waterfall” style="{{waterStyle}}" wx:for="{{items}}" wx:key=> <view class=“item” style=“grid-row: var(–item-span-{{index}})”>view> view> page>

Page

Page({
  data: {
    waterStyle: '',
    items: [...]
  },
  onReady(){
    let query = wx.createSelectorQuery().in(this)
    query.selectAll('.waterfall .item').boundingClientRect(ret=>{
      let styleStr = '';
      ret.forEach((ele, ii) => {
        let height = ele.height
        let span = parseInt(height/ 20)  // 20 = grid-auto-row
        styleStr += `--item-span-${ii}: auto / span ${span};`
      })
      this.setData({
        waterStyle: styleStr
      })
    })
  }
})

// 注释一
// 所有item的css变量合集 waterStyle  
/*
xn在onReady方法中计算得出  

 --item-span-1: auto/span x1;
 --item-span-2: auto/span x2;
 --item-span-3: auto/span x3;
 ...
*/

  • 使用内联样式而不是.item-n
    item元素使用内联样式,因为我们不确定item的数量。

  • 高度计算
    通过query我们可以获取所有item子元素的rect属性(长宽高…),计算height属性与grid-auto-row的比值,即我们需要的设定值

  • waterStyle
    参考上述代码的注释一(动态计算每一个item的span比值),通过setData方法设置生效(设置在父级view.waterfall上),如此grid会自动设置每一个item子元素的位置

优化

以上基本将如何使用grid实现瀑布流的原理阐述了一遍,实际代码中需要注意grid-auto-row值的设定,在我们的项目中省略了此项css属性的设置,即span比值实际是由height/grid-gap得出,反而效果更好,具体原因我也一脸懵逼,如果有知道的留言告诉我

回到顶部