weex实现带有跟手动画的tab栏

18 篇文章 4 订阅
6 篇文章 0 订阅

在weex开发的群中看到有人提到这个问题,就想着去实现以下,还不是很完美,只支持一屏的tab栏内容,后续会进行优化。

2019-6-20 更新:已支持滚动跟手,可以超出屏幕。

2019-6-23 更新:解决子元素包含滚动标签时无法滑动切换的问题。

2019-6-25 更新:修复底部和指示器会超出屏幕的bug。

效果图如下:

效果图

组件源码:

<template>
    <div class="container">
        <scroller scroll-direction="horizontal" show-scrollbar="false"show style="flex-direction: row;width: 750px;height:90px">
            <div class="tab_top">
                <div ref="tab_item" class="tab_item" @click="itemClick(index)"
                     :style="{width:itemWidth,backgroundColor:backgroundColor}" v-for="(item,index) in tabItems">
                    <text class="tab_item_text" :style="{fontSize:fontSize,
                    color:selectIndex===index?textSelectColor:textColor}">{{item.text}}</text>
                </div>
                <div ref="indicator" class="indicator" :style="{width:itemWidth-40,backgroundColor:indicatorColor}"></div>
            </div>
        </scroller>

        <div class="tab_content" @touchstart="bindingX" :prevent-move-event="true"
             :style="{width:tabItems.length*750}" ref="tab_content">
            <!--@touchstart="bindingX"-->
            <slot></slot>
        </div>

    </div>
</template>

<script>
    import Head from "./head.vue";
    let binding = weex.requireModule('binding');
    import { parse } from 'bindingx-parser';
    let modal=weex.requireModule('modal');
    let dom=weex.requireModule('dom')
    let animation=weex.requireModule('animation');
    export default {
        components: {Head},
        name: "tab-container",
        props:{
            tabItems:{
                default:
                  [{
                    text:"测试1",
                  }],
                type:Array
            },
            itemWidth:{
                default:200,
                type:Number
            },
            fontSize:{
                default:"28px",
                type:String
            },
            textSelectColor:{
                default:'#268cf0'
            },
            textColor:{
                default:'#333'
            },
            /*tab底部指针的偏移量*/
            offset:{
                default:250,
                type:Number
            },
            /*可以触发动画的偏移量*/
            dist:{
                default:250,
                type:Number
            },
            backgroundColor:{
                default:"#ffffff",
                type:String
            },
            indicatorColor:{
                default:"#17acf0",
                type:String
            },
            selectIndex:{
                default:0,
                type:Number
            },
            duration: {
                type: [Number, String],
                default: 150
            },
            timingFunction: {
                type: String,
                default: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
            },
        },
        data(){
            return{
                dx:0,
                dx_i:0,
                animationIng:false,
                startScrollIndex:0
            }
        },
        watch:{
            selectIndex:function () {
                this.exeAnimationX()
            }
        },
        methods:{
            /*手势使用bindingX提高性能*/
            bindingX(element){
                let self=this;
                if(self.animationIng){
                    return;
                }
                let {dist}=this;
                let dx=-750*self.selectIndex;
                let dx_i=self.itemWidth*self.selectIndex;
                let indicator=self.$refs.indicator.ref;
                let tab_content=self.$refs.tab_content.ref;

                let tab_content_x=`x+${dx}>0?0:x+${dx}`;
                let indicator_x=`x+${dx}>0?0:${dx_i}-x/${750/this.itemWidth}`;
                if(-dx/750===self.tabItems.length-1){
                    indicator_x=`x<0?${dx_i}:${dx_i}-x/${750/this.itemWidth}`;
                    tab_content_x=`x<0?${dx}:x+${dx}`;
                }
                if(element.ref==null){
                    return;
                }
                self.token=binding.bind({
                    anchor:element.ref,
                    eventType:'pan',
                    props:[
                        {
                            element:tab_content,
                            property:'transform.translateX',
                            expression:parse(tab_content_x)
                        },
                        {
                            element:indicator,
                            property:'transform.translateX',
                            expression:parse(indicator_x)
                        }
                    ]
                },(event)=>{
                    // modal.alert({message:event});
                    let {deltaX,state}=event;

                    if(state==='end'){
                        if(deltaX>dist&&this.selectIndex>0){
                            this.selectIndex--;
                        }else if(deltaX<-dist&&this.selectIndex<self.tabItems.length-1){
                            this.selectIndex++;
                        }else{
                            this.exeAnimationX()
                        }
                    }
                }).token;
            },
            /*动画尽量使用animation简化代码*/
            exeAnimationX(){
                let self=this;
                self.animationIng=true;
                let tab_content=self.$refs.tab_content;
                let indicator=self.$refs.indicator;
                const { duration, timingFunction,selectIndex} = this;
                const dist =selectIndex * 750;
                const dist_i=self.itemWidth*selectIndex;
                animation.transition(tab_content, {
                    styles: {
                        transform: `translateX(${-dist}px)`
                    },
                    duration: duration,
                    timingFunction,
                    delay: 0
                }, () => {
                    self.$emit("itemClick",selectIndex);
                    self.animationIng=false;
                    if(self.offset<dist_i){
                        dom.scrollToElement(self.$refs.tab_item[selectIndex], {offset:-self.offset,animated:true})
                    }else{
                        dom.scrollToElement(self.$refs.tab_item[selectIndex], {offset:-dist_i,animated:true})
                    }
                    // binding.unbindAll();
                });
                animation.transition(indicator, {
                    styles: {
                        transform: `translateX(${dist_i}px)`
                    },
                    duration: duration,
                    timingFunction,
                    delay: 0
                }, () => {
                });
            },
            itemClick(index){
                 let self=this;
                if(self.animationIng){
                    return;
                }
                self.selectIndex=index;
                // dom.scrollToElement(this.$refs.tab_item[index], {offset:-self.offset,animated:true})
                // self.exeAnimationX(index);
            }
        },
        created(){
             let self=this;
             self.startScrollIndex=750/self.itemWidth;
             setTimeout(()=>{
                 // dom.scrollToElement(this.$refs.tab_item[self.selectIndex], {offset:-self.offset,animated:true})
                 self.exeAnimationX();
             },100);
        }
    }
</script>

<style scoped>
    .container{
        /*position:relative;*/
        /*top:0;*/
        /*left:0;*/
        /*bottom: 0;*/
        /*background-color: yellow;*/
    }
    .tab_content{
        flex-direction: row;
        position:relative;
        top:0;
        left:0;
        bottom: 0;
    }
    .tab_top{
        flex-direction: row;
    }
    .tab_item{
        height: 90px;
        align-items: center;
        justify-content: center;
    }
    .tab_item_text{
    }
    .tab_item_text_focus{
        font-size: 26px;
        color: #268cf0;
    }
    .indicator{
        height: 5px;
        background-color: white;
        position: absolute;
        bottom: 0;
        left: 20px;
    }
</style>

使用方法:

<template>
  <div>
     <head></head>
      <tab-container @itemClick="itemClick" :tabItems="tabItems">
           <list v-for="(item,index) in tabItems" class="tabCell">
                        //滚动元素需要把事件给到每个根子元素上,并设置prevent-move- 
                        //event="true" 
                        <cell class="content" :prevent-move-event="true" @horizontalpan="wxcPanItemPan">
              <text class="tab_item_text" style="font-size: 34px;color:#ffffff">{{item}}</text>
                     </cell >
           </list>
           //普通非滚动元素用法,不用传递事件解决冲突
           // <div class="content"  v-for="(item,index) in tabItems" >
           // <text class="tab_item_text" style="font-size: 34px;color:#ffffff">{{item}} 
           //  </text>
           // </div>

      </tab-container>

  </div>
 
</template>
<style>
    .content{
        width: 750px;
        height: 1200px;
        align-items: center;
        justify-content: center;
        background-color: green;
    }
</style>

<script>
  const modal=weex.requireModule('modal');
  import Head from "../components/head.vue";
  import TabContainer from "../components/tabContainer.vue";
  export default {
      components: {
          TabContainer,
          Head},
      data () {
      return {
        text: 'Hello World.',
          tabItems:[{
              text:"测试1",
              font_size:26,
              textColor:"#ffffff"
          },{
              text:"测试2",
              font_size:26,
              textColor:"#ffffff"
          },{
              text:"测试3",
              font_size:26,
              textColor:"#ffffff"
          },
              {
                  text:"测试4",
                  font_size:26,
                  textColor:"#ffffff"
              },
              {
                  text:"测试5",
                  font_size:26,
                  textColor:"#ffffff"
              },
              {
                  text:"测试6",
                  font_size:26,
                  textColor:"#ffffff"
              },
              {
                  text:"测试7",
                  font_size:26,
                  textColor:"#ffffff"
              }],
          contents:[111,222,333],
          param:''
      }
    }
    ,
      methods:{
          itemClick(index){
              modal.toast({message:index})
          },
          //这个事件是为了解决事件冲突
          horizontalpan(e) {
                // modal.alert({message:e});
                if (e.state === 'start') {
                    if (commonUtils.env.supportsEBForAndroid()) {
                        this.$refs['tabContainer'].bindingX(e.currentTarget);
                    }
                }

            },
      }
     ,
      created:function(){

      }
  }
</script>

我的weex相关博客:weex开发系列

我的开源weex模板项目:IWeex

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一拳小和尚LXY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值