第四章:3 x 3 按钮组件

166 阅读5分钟

fruits-list.png


在落地页经常需要实现一个可以点击的按钮列表,为用户提供直观的可选产品。


组件使用

  1. 创建组件,在根目录下创建 components 路径。
  2. components 下创建组件文件夹,右键文件夹创建组件,文件夹名称和组件名称尽量一致。
  3. 在需要引用组件的 wxml 中,以标签形式使用组件。
  4. 在需要引用组件的 json 文件 usingComponents 数组下,添加组件相对路径。

这里新建一个 <Fruits></Fruits> 水果按钮列表组件。 依赖关系-Fruits.png


分析界面结构:
  • 纵向三层,整体居中。
  • 第一层是标题 “请选择水果”,外加两个小手动画。内部是水平布局。
  • 第二层是 3 x 3 按钮列表,里面有文字,有点击事件,有按钮样式,整体居中布局。
  • 第三层是一个居中布局的文字。

结构很简单,开始动手写 wxml

<view class="title-wrap">
<image class="finger" src="{{fingerImg}}"/>
<text>请选择要购买的水果</text>
<image class="finger" src="{{fingerImg}}"/>
</view>

<view class="fruits-list">
<view class="fruit" wx:for="{{fruitsList}}" wx:key="index" data-index="{{index}}" catchtap="tapFruit">
  <text>{{item}}</text>
  <button class="fruit-btn" bindtap="getUserProfile" data-index="{{index}}"></button>
</view>
</view>

<view class="tips">{{'*购买成功记得五星好评哦'}}</view>

样式:

.title-wrap {
  margin: 40rpx auto;
  padding: 0;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  flex-grow: 1;
  flex-shrink: 1;
  font-size: 34rpx;
  color: #000;
  font-family: PingFangSC-Semibold,PingFang SC;
}

.finger {
  width: 44rpx;
  height: 50rpx;
}

.fruits-list {
  position: relative;
  margin: 0 50rpx;
  display: flex;
  flex-flow: row wrap;
}

.fruit {
  position: relative;
  margin: 0 8rpx 24rpx;
  width: 200rpx;
  background: #fff;
  border: 2rpx solid rgba(255, 107, 44, 1);
  border-radius: 10rpx;
  font-size: 28rpx;
  line-height: 72rpx;
  font-weight: 600;
  color: rgba(255, 98, 3, 1);
  text-align: center;
}

.fruit:nth-child(3n+1) {
  margin-left: 0;
}

.fruit:nth-child(3n) {
  margin-right: 0;
}

.fruit-btn {
  background: transparent;
  width: 100% !important;
  height: 100%;
  z-index: 1;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

.tips {
  margin: 12rpx 30rpx 38rpx;
  font-size: 24rpx;
  font-weight: 300;
  color: #999999;
  line-height: 24rpx;
  letter-spacing: 1rpx;
  text-align: center;
}
Tips:
  • 小手指是上下移动的,可以使用CSS关键帧动画实现
  • 按钮列表中,需要绑定按钮序号,使用 data-index 绑定循环中的序号。
  • 节点自适应左右居中常用的方法就是设置 margin: 0 auto

这里面用到了 flex-growflex-shrink。着重简单介绍一下,因为这两个属性经常会用到.


flex-grow

flex-grow 处理父元素在还有剩余空间时的分配规则,分为两种情况。

即:所有元素的 flex-grow 值之和大于1,和小于1。

  1. 大于1时,例如:
    父元素宽600,子元素A和B,宽分别为200,300。还剩余100。
    此时A,B的 flex-grow 分别为2,3。则剩余100,分给A 2/5,分给B 3/5。
    A,B宽度为:

        200 + 40 = 240
        300 + 60 = 360
    
  2. 小于1时,作为分母的总和会引入1来处理。例如:
    上例中,A,B flex-grow 分别为 0.2,0.3。则分给A 0.2/1,分给B 0.3/1。
    A,B宽度为:

        200 + 20 = 220
        300 + 30 = 330
    

    还剩50没有分配给任何子元素扩张。

  3. 另外,flex-grow 还会受到父元素的 max-width 影响。如果grow后的结果超出 max-widthmax-width 会优先生效。

flex-shrink

flex-grow 处理父元素剩余空间相对应的,是 flex-shrink 处理父元素空间不足时,子元素的收缩规则。
同样分为两种情况,所有元素的 flex-shrink 值之和大于1,和小于1。

  1. 大于1时,例如:
    父元素宽度为600,子元素宽度为400,300。超出100。
    A,B flex-shrink 分别为 1,2。总权重为 400 + 300 * 2 = 1000

        A收缩 -100 * 1 * 400 / 1000 = -40
        B收缩 -100 * 2 * 300 / 1000 = -60
    

    A,B实际宽度为:

        400 - 40 = 360
        300 - 60 = 240
    
  2. 小于1时,例如,
    A,B flex-shrink 分别为 0.1,0.2。总权重为 400 * 0.1 + 300 * 0.2 = 100 子元素收缩总和为 100 * 0.3 / 1 = 30

        A收缩 -30 * 0.1 * 400 / 100 = -12
        B收缩 -30 * 0.2 * 300 / 100 = -18
    

    A,B实际宽度为:

        400 - 12 = 388
        300 - 18 = 282
    

    多出70没有分配给任何子元素收缩。

  3. 同样,也会受到min-width的限制。

组件传递数据

父组件向子组件传递数据

在组件的属性列表中新增参数字段:

properties: {
    option: {
      type: Boolean,
      value: true
    }
}

这个属性需要在使用组件的位置赋值,并作为参数传递下去:

<Fruits option="{{true}}"></Fruits>


子组件向父组件传递数据

在子组件内 trigger 一个事件,然后在子组件被引用的位置 bind 事件。并且在事件响应函数中,使用传递过来的数据。

  • Trigger: this.triggerEvent('eventName', { index }); 可以在后面夹带参数。

  • Bind: <Fruits option="{{true}}" bindeventName="callBack"></Fruits>

  • CallBack:

    callBack: function (e) {
        // 事件传递过来的参数
        const index = e.detail.index;
    }
    

CSS关键帧动画

为了实现手指向下的小动画,使用关键帧处理。
如果在 @keyframes 规则中指定了 CSS 样式,动画将在设定时间逐渐从当前样式更改为新样式。

.finger:first-child {
  margin-right: 10rpx;
  animation: moveDownLeft .9s infinite;
}

.finger:last-child {
  margin-left: 10rpx;
  animation: moveDownRight .9s infinite;
}

  • 左右手各使用一个动画,因为如果使用同一个动画,再Y轴翻转一下也可以。但是会出现左右动画不同时运动的问题。
  • 0.9s 是持续时间,infinite 是无限循环。

keyframes :

@keyframes moveDownLeft {
  0% {
    transform: translateY(0rpx);
  }

  50% {
    transform: translateY(9rpx);
  }

  100% {
    transform: translateY(0rpx);
  }
}

@keyframes moveDownRight {
  0% {
    transform: translateY(0rpx) scale(-1, 1);
  }

  50% {
    transform: translateY(9rpx) scale(-1, 1);
  }

  100% {
    transform: translateY(0rpx) scale(-1, 1);
  }
}

左手动作设置了从开始到一半,再到结束时的Y轴位移。右手Y轴动作一致,只不过水平翻转一下。