UPDATE 2019/07/16:

下文中,dataduration不要setData,因为页面并没有引用这个变量,setData中会浪费性能,直接赋值就行了:this.data.duration = 0;。如果是在Page中,可在Page的配置中自定义一个变量,但在组件中是不行的。文中用的是组件。


一、前言和效果图

可能方法比较原始,如果各位大神有高大上的方法,请不吝赐教。

效果图(可能录屏软件帧数低,实际是没有卡顿和跳动的。动画起始和结束位置都是2):
record.gif

二、难点分析

获取相关元素的尺寸

小程序中和DOM打交道是费力的,如何布局并获取DOM尺寸,是一个难点。

实现无缝滚动

同样,由于DOM样式的改变都是由数据驱动的,不能像以前那样直接改变DOM,所以这一点也需要思考一下。

三、实现

1、布局

首先,需要一个overflow: hidden的容器,然后需要一个盛放列表的容器,这个容器的高度会随着列表的数量变化。

<view class='scroll-box'>
  <view class='item-box'>
    <view class='item'>4每周精选上新:本周精选欢迎采购!</view>
    ......
  </view>
</view>

这样的结构已经可以实现滚动了,但是,每一个item的高度要设置和scroll-box一样,所以文字就会从scroll-box最底部出现,滚动到最上面,然后消失,这样不太美观,所以再加一层容器,设置合适的margin:

<view class='scroll-box'>
  <view class='item-box'>
    <view class='scroll'>
      <view class='item'>1每周精选上新:本周精选欢迎采购!</view>
      ......
    </view>
  </view>
</view>

这样,最外层可以自由设置宽高,文字的滚动区域,只要设置item-box就好了。下面是样式:

.scroll-box {
  overflow: hidden;
  height: 92rpx;
  /*设置小图标*/
  background: url(icon.png) no-repeat 28rpx center/31rpx 25rpx;
}

.item-box {
  overflow: hidden;
  position: relative;
  height: 40rpx;
  /*根据需要设置合适的边距,左边距88rpx是因为左侧有个小图标*/
  margin: 26rpx 28rpx 0 88rpx;
}

.scroll {
  /*实现定位*/
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  /*动画由css3完成*/
  transition: all 0.24s;
}

.item {
  height: 40rpx;
  line-height: 40rpx;
  font-size: 24rpx;
}

下面来用js实现滚动。原理是:

首先,获取到列表总高度和项目的高度。虽然在wxss中给.item-box.item设置了高度,但那是以rpx为单位的,实际在wxml中,还是以px为单位的。
然后,用计时器定时改变.item-boxtop,配合设置的transition,就实现了向上滚动。
最后,在滚动到最后一项后,返回第一项。

这样的问题是,没法实现无缝滚动。回想在用jQuery的时代,实现无缝滚动的方法是:复制列表最后一项,追加到列表的最前面。当列表滚动到最后一项时,立刻把列表定位到第一项。因为第一项和最后一项是相同的,所以用户看不到这个切换的过程。然后再继续重复这个过程就行了。

在小程序中我们同样这样做。这里,列表项就用静态数据了,注意.item的顺序:

<view class='scroll-box'>
  <view class='item-box'>
    <view class='scroll'>
      <view class='item'>4每周精选上新:本周精选欢迎采购!</view>
      <view class='item'>1每周精选上新:本周精选欢迎采购!</view>
      <view class='item'>2每周精选上新:本周精选欢迎采购!</view>
      <view class='item'>3每周精选上新:本周精选欢迎采购!</view>
      <view class='item'>4每周精选上新:本周精选欢迎采购!</view>
    </view>
  </view>
</view>

考虑到1、从最后一项定位到第一项,不需要动画效果;2、这个切换要立刻完成,不能占用一个定时器周期,所以需要定义一个变量,来给.scroll增加/去除动画,还要定义一个变量,用来改变定时器的等待时间。

改变的css如下:

.scroll {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
}

/*去掉.scroll的transition,放到单独的类中*/
.scroll.trans {
  transition: all 0.24s;
}

js代码如下:

Component({
  properties: {

  },

  data: {
    // 给.scroll增删动画效果
    trans: 'trans',
    // 定位.scroll,初始在第2条.item的位置(因为第一条和最后一条相同)
    //这里省略了计算过程,给了经过计算的scrollBoxItemHeight值
    top: 20,
    // 记录.scroll的高度
    scrollBoxItemHeight: null,
    // 记录.item的高度
    scrollBoxHeight: null,
    // .item的条数
    itemCount: 5,
    timer: null,
    // 计时器的等待时间
    duration: 4000
  },

  methods: {
    // 无缝滚动,要复制列表最后一项,追加为列表第一项,使第一项和最后一项相同
    // 当滚动到最后一项时,去掉css3动画,定位到第一项,设置计时器的等待时间为0
    _scroll() {
      let _this = this; // 本网站箭头函数的语法高亮有问题...
      this.data.timer = setTimeout(function(){
        // 滚动到最后一条后,去掉动画,改变duration为0,立刻定位到第一条
        if (_this.data.top >= _this.data.scrollBoxHeight) {
          _this.setData({
            trans: '',
            top: 0,
            duration: 0
          }, function(){
            // 继续
            _this._scroll();
          });
        } else {
          _this.data.top += 20;
          _this.setData({
            trans: 'trans',
            top: this.data.top,
            duration: 4000
          }, function(){
            _this._scroll();
          });
        }
      }, _this.data.duration);
    }
  },

  ready() {
    let _this = this;
    // 获取高度,用法请查询文档
    this.createSelectorQuery().select('.scroll .item').boundingClientRect(function(r) {
      _this.data.scrollBoxItemHeight = r.height;
      _this.data.scrollBoxHeight = (_this.data.itemCount - 1) * r.height;
      _this._scroll();
    }).exec();
  }
})

最后,给wxml绑定相关数据:

......
<view class='scroll {{trans}}' style='top:{{-top}}px'>
......

以上就实现了文字无缝垂直滚动。

标签: 无缝滚动