swiper-list2.uvue 4.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
<template>
  <view class="swiper-list">
    <scroll-view ref="tabScroll" class="swiper-tabs" :scroll-x="true" :show-scrollbar="false">
      <view class="flex-row" style="align-self: flex-start;">
        <text ref="swipertab" class="swiper-tabs-item" :class="swiperIndex==index ? 'swiper-tabs-item-active' : ''"
          v-for="(item, index) in swiperList" :key="index" @click="onTabClick(index)">
          {{item.title}}
        </text>
      </view>
    </scroll-view>
    <swiper ref="swiper" class="swiper-view" :current="swiperIndex" @transition="onSwiperTransition"
      @animationfinish="onSwiperAnimationfinish">
      <swiper-item class="swiper-item" v-for="(_, index) in swiperList" :key="index">
        <text class="swiper-item-text">{{index}}</text>
      </swiper-item>
    </swiper>
  </view>
</template>

<script>
  type SwiperViewItem = {
    title : string,
  }

  export default {
    data() {
      return {
        swiperList: [] as SwiperViewItem[],
        swiperIndex: -1,
        $tabScrollView: null as null | INode,
        $animationFinishIndex: 0,
        $swiperWidth: 0
      }
    },
    onLoad() {
      for (let i = 0; i < 4; i++) {
        this.swiperList.push({
          title: "Tab " + i
        } as SwiperViewItem)
      }
    },
    onReady() {
      this.$tabScrollView = this.$refs.get('tabScroll') as INode
      this.$swiperWidth = (this.$refs["swiper"] as INode).getBoundingClientRect().width
      this.setSwiperIndex(0, true)
    },
    methods: {
      onTabClick(index : number) {
        this.setSwiperIndex(index, false)
      },
      onSwiperTransition(e : SwiperTransitionEvent) {
        const offset_x = e.detail.dx

        // 计算当前索引并重置差异
        const current_offset_x = offset_x % this.$swiperWidth
        const current_offset_i = offset_x / this.$swiperWidth
        const current_index = this.$animationFinishIndex + current_offset_i.toInt()

        // 计算目标索引及边界检查
        let move_to_index = current_index
        if (current_offset_x > 0 && move_to_index < this.swiperList.length - 1) {
          move_to_index += 1
        } else if (current_offset_x < 0 && move_to_index > 0) {
          move_to_index -= 1
        }

        // 计算偏移百分比
        const percentage = Math.abs(current_offset_x) / this.$swiperWidth

        // 通知更新指示线
        this.updateTabIndicator(current_index, move_to_index, percentage)
      },
      onSwiperAnimationfinish(e : SwiperAnimationFinishEvent) {
        this.setSwiperIndex(e.detail.current, true)
        this.$animationFinishIndex = e.detail.current
      },
      setSwiperIndex(index : number, updateIndicator : boolean) {
        if (this.swiperIndex == index) {
          return
        }

        this.swiperIndex = index

        if (updateIndicator) {
          this.updateTabIndicator(index, index, 1)
        }
      },
      updateTabIndicator(current_index : number, move_to_index : number, percentage : number) {
        if (percentage == 0) {
          return
        }

        // 缩放范围
        const min_ratio = 1
        const max_ratio = 1.5

        const tabs = this.$refs["swipertab"] as INode[]
        const current_node = tabs[current_index]!
        const move_to_node = tabs[move_to_index]!

        // 当前
        const current_scale = this.lerpNumber(min_ratio, max_ratio, 1 - percentage)
        current_node.style?.setProperty('transform', `scale(${current_scale})`)

        // 目标
        const move_to_scale = this.lerpNumber(min_ratio, max_ratio, percentage)
        move_to_node.style?.setProperty('transform', `scale(${move_to_scale})`)

        // 滚动到水平中心位置
        const target_x = current_node.offsetLeft + (move_to_node.offsetLeft - current_node.offsetLeft) * percentage
        const center_x = target_x + move_to_node.offsetWidth / 2 - this.$swiperWidth / 2
        this.$tabScrollView?.setAttribute('scrollLeft', center_x)
      },
      /**
       * 根据权重在两个值之间执行线性插值.
       * @constructor
       * @param {number} value1 - 第一个值,该值应为下限.
       * @param {number} value2 - 第二个值,该值应为上限.
       * @param {number} amount - 应介于 0 和 1 之间,指示内插的权重.
       * @returns {number} 内插值
       */
      lerpNumber(value1 : number, value2 : number, amount : number) : number {
        return (value1 + (value2 - value1) * amount)
      }
    }
  }
</script>

<style>
  .flex-row {
    flex-direction: row;
  }

  .swiper-list {
    flex: 1;
  }

  .swiper-tabs-item {
    color: #555;
    font-size: 16px;
    padding: 12px 25px 5px 25px;
    transform-origin: left bottom;
  }

  .swiper-tabs-item-active {
    color: #000000;
  }

  .swiper-view {
    flex: 1;
  }

  .swiper-item {
    flex: 1;
    align-items: center;
    justify-content: center;
  }

  .swiper-item-text {
    font-size: 72px;
    font-weight: bold;
  }
</style>