How to implement a tabs navigation bar with pictures

Recently received a request to achieve a navigation bar effect with pictures:


 

The specific needs are:

1. If the option picture in the navigation data is not empty, the picture will be displayed without text;

2. The text of the selected item is highlighted. In the case of a picture, the size of the picture becomes larger;

3. The indicator follows the movement;

After receiving the request, I looked through the information, but I didn’t find any ready-made frameworks that can be used. Basically, they are pure text navigation functions. It seems that I can only write one myself.

First, the code structure and style code:


<template>
  <view ref="tabs" class="tabs">
    <view ref="tabsContent" class="tabs-content">
      <view
        v-for="(item, index) in list"
        :ref="'LEl_' + index"
        :class="['tabs-item', 'LEl_' + index, index == current ? 'active' : '']"
        @click="change(index)"
      >
        <div v-if="item.imgUrl">
          <u-image
            :src="item.imgUrl"
            :style="{
              height: index == current ? '56rpx' : '44rpx',
              width: getImgWidth(item.imgWidth, item.imgHeight, (index == current ? 56 : 44)) + 'rpx'
            }"
          >
          <div class="title" slot="loading">{
   
   { item.title }}</div>
          <div class="title" slot="error">{
   
   { item.title }}</div>
          </u-image>
        </div>
        <div v-else>
          {
   
   {item.title}}
        </div>
      </view>  
      <!-- 滑块 -->
      <view ref="scrollBar" class="u-scroll-bar"></view>  
    </view>
  </view>
</template>
<style lang="scss" scoped>
.tabs{
  box-sizing: border-box;
  overflow-x: scroll;
  background-color: #fff;
  .tabs-content{
    position: relative;
    width: max-content;
    min-width: 100%;
    padding: 22rpx 20rpx 20rpx;
    display: flex;
    align-items: flex-end;
    border-bottom: 2rpx solid #EEEEEE;
    .tabs-item{
      display: flex;
      align-items: flex-end;
      position: relative;
      bottom: 0;
      height: 56rpx;
      box-sizing: content-box;
      margin: 0 20rpx;
      font-size: 32rpx;
      font-weight: 400;
      color: #777;
      line-height: 44rpx;
      &.active{
        font-size: 40rpx;
        color: #111;
        font-weight: 500;
        line-height: 56rpx;
      }
      .title{
        font-size: 32rpx;
        font-weight: 400;
        color: #777;
        line-height: 44rpx;
      }
    }
  }
}

//
.u-scroll-bar{
  position: absolute;
  bottom: 8rpx;
  width: 18rpx;
  height: 8rpx;
  background: #C69C6D;
  border-radius: 4rpx;
  left: 0;
  transition: transform 200ms;
}

/deep/ .u-image__error{
  background-color: transparent;
}

</style>

First, the image size problem

The first problem encountered during the development process is the size of the picture. The option data passed from the background is as follows:

When there is a picture, the actual width and height of the picture will be passed together. It is obviously wrong to directly assign the passed size to the picture, so we must first convert the width and height.

It is known that the height of the picture to be displayed is fixed, and the width is not fixed, which is determined by the design effect. Then, given the actual width and height of the picture and the height to be displayed, we can easily calculate how the picture should be displayed. Width to display: 


getImgWidth(w, h, showH){
  let showW = showH *  w / h
  return showW
},

Then, because the height of the picture of the selected item is higher than that of the unselected item, it is necessary to judge the width and height of the picture:

Second, the slider indicator follows the movement of the clicked item

The second problem encountered is that because the text or picture of the selected item is larger than the general text or picture, the width of the tabs-content container layer is not fixed, and the general frame and slider indicator cannot be very good The center alignment of , which is the main reason why I have to write one myself.

So how to solve it? The transition attribute in CSS and the getBoundingClientRect() method of js are used here.

The return value of getBoundingClientRect is a DOMRect object, which is the distance from the dom element to the viewable range of the browser (excluding the rolled-up part of the document)

Through the getBoundingClientRect method, there is the following calculation formula for the left coordinate of the slider:

Slider x coordinate = half of the selected item from the left margin of the screen + (selected item width + slider width) - the left margin of the option container to the screen

let _tabs = this.$refs.tabs.$el,                        // tabs容器,宽度等于屏幕尺寸
            _scrollBox = this.$refs.tabsContent.$el,            // 选项容器,宽度由内容决定
            _tabItem = this.$refs["LEl_"+current][0].$el,       // 选中tab项
            _tabBar = this.$refs.scrollBar.$el;                  // 滑块指示器
        let _tabsRect = _tabs.getBoundingClientRect(),
            _scrollBoxRect = _scrollBox.getBoundingClientRect(),
            _tabItemRect = _tabItem.getBoundingClientRect(),
            _tabBarRect = _tabBar.getBoundingClientRect();
        
        // 滑块x坐标 = 选中项距屏幕左边距 + (选中项宽度+滑块宽度)的一半 - 选项容器到屏幕的左边距
        let barLeft = _tabItemRect.left+(_tabItemRect.width - _tabBarRect.width)/2 - _scrollBoxRect.left
        _tabBar.style.transform = "translate3d(" + barLeft + "px,0,0)";

Third, how to slide the selected item to a position relative to the center line of the screen after it exceeds the center line?

In order to facilitate understanding, I drew the picture below

The distance scrolx between the midpoint of the selected item and the midline is actually the width of the scrolled content we need. Of course, if the midpoint of the selected item does not exceed the midline, the scrolled distance remains zero.

How exactly? Post the code!


// 判断当前tab是否超过中线
        if(barLeft > (_tabsRect.width - _tabBarRect.width)/2){
          
          // 判断是否已经滑动到最右边,超过可滑动最大值
          if(_tabs.clientWidth + _tabs.scrollLeft >= _tabs.scrollWidth){
          // tabs可视区域宽度 + 被滚动卷去宽度 >= tbas内容宽度的话,滚动位置定位到最右边(最大值)
            let scrollx = _tabs.scrollWidth - _tabs.clientWidth
            _tabs.scrollLeft = scrollx
          }else {    
            // 向上取整当前tab与中线的距离,该值为被券去的滚动距离
            let scrollx = Math.ceil(barLeft - _tabsRect.width/2 + _tabBarRect.width/2) 
            
            // 定时器实现滑动效果
            var myMar = setInterval(marquee, 1)
            
            function marquee(){
              /* 
                清除定时器规则:
                1,滚动条位置等于被卷去的距离;
                2,滚动条位置等于可滚动最大值。
              */
              if(_tabs.scrollLeft == scrollx || _tabs.clientWidth + _tabs.scrollLeft == _tabs.scrollWidth) return clearInterval(myMar)
              
              if(_tabs.scrollLeft < scrollx){
                // 当前滚动条坐标小于需要卷去的距离,向右移动
                _tabs.scrollLeft++
              } else {
                // 当前滚动条坐标大于需要卷去的距离,向左移动
                _tabs.scrollLeft--
              }
            }
          }
          
        } else {
          // tab未超过中线,滚动距离为0
          _tabs.scrollLeft = 0
        }

At this point, a swipeable centered navigation effect is complete~

Guess you like

Origin blog.csdn.net/weixin_49707375/article/details/128011228