小程序 swiper 如何多页面高度自适应

6,478 阅读4分钟

轮播,这个概念只要做过 UI 的都不会陌生,盲猜市场上 90% 的应用都有这个需求,在 iOS 和 Android 上都有很完善的控件,比如 Android 的 ViewPager 和 iOS 的 UIScrollview。

小程序这么牛逼,肯定也要有控件支持这个特性啊, swiper 就这么诞生了。

但是 swiper 有一个很严重的问题,就是高度默认 150px,且不可以自适应内容调整高度。

这就有问题了,我现在有一个多 Tab 的页面,最少高度要满屏,还要超出内容可以往下滚动,此时就蒙蔽了,怎么给 swiper 设置高度呢?

首先看一下我搜索到的一些方法:

  1. 在初始化的时候获取到屏幕的高度,然后将高度设置到 swiper 上,至于滚动的问题,在里面再嵌入一个 scroll-view

    这个问题有很多坑,首先 屏幕的高度要比内容区的高度大,这么设置以后就算内容较少,页面也能滑动一点;其次,小程序的 scroll-view 在实现上拉加载更多的时候,坑更多。

  2. 每个 item 的高度都一致,根据 item 的数量和统一的高度计算出内容的高度,然后设置进去

    这个方案感觉完全是 zz 方案,局限性太大了

我的方案

一句话解释:给 swiper-item 内部添加三个锚点,最上面一个,最下面一个,还有一个锚点始终位于屏幕最底下。根据这三个锚点计算出内容高度和内容显示区高度。 PS:锚点,宽高为 0 的不可见的 view,用于获取定位

如果还有不理解可以看下面这个示意图:

这三个锚点的具体作用是用来计算 swiper 内容高度和 swiper 距离屏幕底部的具体,计算方式如下:

  1. 使用 swiper-item 内部的两个锚点计算出内容区高度
  2. 通过屏幕底部和 swiper-item 顶部的锚点计算出离屏幕底部的距离

接下来看看代码具体实现

代码实现

page.wxml

<view>
	<swiper style="height: {{anchor.deviceHeight + 'px'}}">
		<swiper-item>
			<view class="anchor-top"></view>
			<!-- 你的内容 -->
			<view class="anchor-bottom"></view>
		</swiper-item>
	</swiper>
	<view class="anchor-screen-bottom"></view>
</view>

page.wxss

.anchor-top {
    width: 0;
    height: 0;
}

.anchor-bottom {
    width: 0;
    height: 0;
}

.anchor-screen-bottom {
    position: absolute;
    bottom: 0;
    width: 0;
    height: 0;
}

page.js

Page({
	data: {
		anchor: {
			deviceHeight: 0,
      anchorTop: 0,
      anchorBottom: 0,
      anchorScreenBottom: 0
		}
	},
	onReady: function() {
		this.computeSwiperHeight(0)
	},
	computeSwiperHeight(pageIndex) {
	  let getSwiperHeight = () => {
      let min = this.data.anchor.anchorScreenBottom - this.data.anchor.anchorTop;
      let value = this.data.anchor.anchorBottom - this.data.anchor.anchorTop
      return Math.max(min, value)
	  }
	  wx.createSelectorQuery()
      .select('.anchor-screen-bottom')
      .boundingClientRect()
      .selectViewport()
      .scrollOffset()
      .exec(res => {
        this.data.anchor.anchorScreenBottom = res[0].bottom
      })
	  wx.createSelectorQuery()
      .selectAll('.anchor-top')
      .boundingClientRect()
      .selectViewport()
      .scrollOffset()
      .exec(res => {
        this.data.anchor.anchorTop = res[0].top
        this.setData({
          'anchor.deviceHeight': getSwiperHeight()
        })
      })
	  wx.createSelectorQuery()
      .selectAll('.anchor-bottom')
      .boundingClientRect()
      .selectViewport()
      .scrollOffset()
      .exec(res => {
        this.data.anchor.anchorBottom = res[0].bottom
        this.setData({
          'anchor.deviceHeight': getSwiperHeight()
        })
      })
	},
})

适配多页面

当然,肯定要适配每个页面的高度不一样的情况。方案也很简单,屏幕底部的锚只需要一个了,给每个 swiper-item 的都添加两个锚点,和之前一样一个在上面一个在下面,在切换页面的时候,根据当前页面的锚点重新计算一下高度,然后设置进去。

只需要在原有基础上改一下代码:

page.wxml

<view>
	<swiper style="height: {{anchor.deviceHeight + 'px'}}" bindchange="swiperChange">
		<swiper-item>
			<view class="anchor-top"></view>
			<!-- 你的内容 -->
			<view class="anchor-bottom"></view>
		</swiper-item>
		<swiper-item>
			<view class="anchor-top"></view>
			<!-- 你的内容 -->
			<view class="anchor-bottom"></view>
		</swiper-item>
	</swiper>
	<view class="anchor-screen-bottom"></view>
</view>

page.wxss

CSS 不需要改动

page.js

Page({
	data: {
		anchor: {
			deviceHeight: 0,
      anchorTop: 0,
      anchorBottom: 0,
      anchorScreenBottom: 0
		}
	},
	onReady: function() {
		this.computeSwiperHeight(0)
	},
	swiperChange(e) {
    this.computeSwiperHeight(e.detail.current)
  },
	computeSwiperHeight(pageIndex) {
	  let getSwiperHeight = () => {
      let min = this.data.anchor.anchorScreenBottom - this.data.anchor.anchorTop;
      let value = this.data.anchor.anchorBottom - this.data.anchor.anchorTop
      return Math.max(min, value)
	  }
	  wx.createSelectorQuery()
      .select('.anchor-screen-bottom')
      .boundingClientRect()
      .selectViewport()
      .scrollOffset()
      .exec(res => {
        this.data.anchor.anchorScreenBottom = res[0].bottom
      })
	  wx.createSelectorQuery()
      .selectAll('.anchor-top')
      .boundingClientRect()
      .selectViewport()
      .scrollOffset()
      .exec(res => {
        this.data.anchor.anchorTop = res[0][pageIndex].top
        this.setData({
          'anchor.deviceHeight': getSwiperHeight()
        })
      })
	  wx.createSelectorQuery()
      .selectAll('.anchor-bottom')
      .boundingClientRect()
      .selectViewport()
      .scrollOffset()
      .exec(res => {
        this.data.anchor.anchorBottom = res[0][pageIndex].bottom
        this.setData({
          'anchor.deviceHeight': getSwiperHeight()
        })
      })
	},
})

实现效果

  1. swiper 的高度高度根据内容自适应
  2. swiper 的高度最小占满屏幕,最大和内容一样高(为了用户滑动体验(如果划页后高度突然变小,用户在原来的位置就划不回去了)
  3. 适配不同高度的页面

这个方案是了实现为自己的需求而写的,应该不适应全部的场景,不过希望可以为你提供一点思路。

感想

小程序里的坑真的很多,而且有些 API 设计的很奇怪,真的不知道当初开发人员怀着怎样的心路历程设计出的 API。

个人感觉小程序就是给前端新造了一个轮子,更新还很不及时,有很多陈年老 Bug,比如本文讲的 swiper

前端娱乐圈发展这么快,感觉小程序可能会跟不上潮流。

最后

给我正在开发的小程序预热一下~

欢迎关注~