封装小程序组件,实现展开/收缩动画

688 阅读2分钟

在许多应用中,我们可能需要实现展开和收缩的动画效果。在本文中,我们将探讨如何在小程序中实现这种效果。本文只实现基础的展开/收缩动画组件,想要实现折叠面板,可以针对该组件做二次封装;支持嵌套折叠。

实现该功能分为下面两点:

  1. 实现节点高度逐渐变化的动画
  2. 节点高度可能不固定,需要动态获取节点高度

一、实现节点高度逐渐变化的动画

微信提供了动画api wx.createAnimation(Object object)

创建一个动画实例。调用实例的方法来描述动画。最后通过动画实例的 export 方法导出动画数据传递给组件的 animation 属性。

<view animation="{{animationData}}"></view>
const animation = wx.createAnimation({
  duration: 0,
  timingFunction: 'ease-in-out'
});
animation
  .height(400)
  .top(1)
  .step({
    duration: 1500
  })
  .height('auto')
  .step();
this.setData({
  animationData: animation.export()
})

二、动态获取节点高度

微信提供了获取页面节点信息api wx.createSelectorQuery()

wx.createSelectorQuery()
  .select('#the-id')
  .boundingClientRect(async res => {
    console.log('res====', res);
  })
  .exec();

三、完整代码

<view class='transition-collapse' animation='{{ animationData }}'>
  <view class='transition-collapse__content'>
    <slot></slot>
  </view>
</view>
Component({
  options: {
    pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段
  },
  properties: {
    // 是否折叠
    collapsed: {
      type: Boolean,
      value: false,
      observer() {
        if (!this.data._mounted) return
        this.updateAnimate()
      }
    },
    // 动画时间
    duration: {
      type: Number,
      value: 300
    },
    // 动画时间曲线
    timingFunction: {
      type: 'String',
      value: 'ease-in-out'
    }
  },
  data: {
    animationData: null,
    // 是否已渲染,首次渲染时,如果是展开状态则不需要动画
    _mounted: false
  },
  attached() {
    this.updateAnimate()
    this.setData({
      _mounted: true
    })
  },
  methods: {
    // 执行动画
    async updateAnimate() {
      const { _mounted, collapsed, duration, timingFunction } = this.data
      const height = await this.getNodeRect();
      const animation = wx.createAnimation({
        duration: 0,
        timingFunction
      });
      if (collapsed) {
        // 增加容错,防止查询节点尺寸失败
        if (height === 0) {
          animation.height('auto').top(1).step();
        } else {
          animation
            .height(height)
            .top(1)
            .step({
              duration: _mounted ? duration : 1
            })
            .height('auto')
            .step();
        }
        this.setData({
          animationData: animation.export()
        });
        return
      }
      if (_mounted) {
        animation.height(height).top(0).step({ duration: 1 }).height(0).step({
          duration
        });
      }
      this.setData({
        animationData: animation.export()
      });
    },
    // 查询节点尺寸
    getNodeRect(selector = '.transition-collapse__content') {
      return new Promise((resolve) => {
        wx.createSelectorQuery()
          .in(this)
          .select(selector)
          .boundingClientRect(res => {
            if (res?.height) {
              return resolve(res.height);
            }
            return resolve(0);
          })
          .exec();
      });
    }
  }
})


.transition-collapse {
  overflow: hidden;
  height: 0;
}
.transition-collapse__content {
  overflow: hidden;
}

四、使用

<w-transition-collapse collapsed="{{collapsed}}">
  <view wx:for="{{[1, 2, 3, 4, 5, 6, 7]}}" wx:key="index">折叠内容{{item}}</view>
</w-transition-collapse>
<button bindtap="handleClick">{{collapsed ? '折叠' : '展开'}}</button>
Page({
  data: {
    collapsed: false
  },
  handleClick() {
    this.setData({
      collapsed: !this.data.collapsed
    })
  }
})
{
  "usingComponents": {
    "w-transition-collapse": "../../components/transition-collapse/index"
  }
}

示例使用的微信平台,其他平台使用对应的api即可

五、组件支持度

小程序平台支持度
微信支持
百度支持
京东支持
小红书支持