vue实现tab标签(标签超出自动滚动)

 更新时间:2021年05月25日 11:26:53   作者:卑鄙的你  
当创建的tab标签超出页面可视区域时自动滚动一个tab标签距离,并可手动点击滚动tab标签,需要的朋友们下面随着小编来一起学习学习吧

当创建的tab标签超出页面可视区域时自动滚动一个tab标签距离,并可手动点击滚动tab标签,实现效果请看GIF图

效果预览GIF图

<template>
    <div class="main-box">
        <button @click="add">添加</button>
        <div class="main-box-tab">
            <i @click="previous"><<</i>
            <i @click="next">>></i>
            <div class="main-box-tab-content" ref="tabs">
                <div class="main-box-tab-roll">
                    <div v-for="(item,index) in tabs" :key="index"
                         :class="{'tab-item-action':actionName === item.name ,'tab-item':actionName !== item.name}"
                         @click.stop="clickTab(item.name,index)">
                        <span>{{item.meta.title}}</span>
                        <i class="el-icon-close" @click.stop="close(item.name)"></i>
                    </div>
                </div>
            </div>
        </div>
        <div class="main-box-content">
            <div>{{actionName}}</div>
        </div>
    </div>
</template>
<script>
    export default {
        name: "index",
        data() {
            return {
                tabs: [],
                moveX: 0,
                count: 1,
                unoccupied: 0,
                tabsCount: 0,
                actionName: 'test1'
            }
        },
        watch: {
            actionName(val) {
                let len = this.tabs.length
                // 如有重复数据退出后续函数执行
                for (let i = 0; i < len; i++) {
                    if (this.tabs[i].name === val) {
                        this.$nextTick(() => {
                            this.translateX((i + 1 - this.tabsCount) * this.width - this.unoccupied)
                        })
                        return
                    }
                }

                this.tabs.push({
                    name: `test${this.count}`,
                    meta: {
                        title: `test${this.count}`
                    }
                })
                this.$nextTick(() => {
                  // (总共有多少个tabs - 未偏移时可见的元素个数) * 单个tab标签元素长度 - 被遮挡tab元素的可见部分的宽度
                    this.translateX((this.tabs.length - this.tabsCount) * this.width - this.unoccupied)
                })
            }
        },
        mounted() {
            this.tabs.push({
                name: `test${this.count}`,
                meta: {
                    title: `test${this.count}`
                }
            })
            this.$nextTick(() => {
                let tabs = this.$refs.tabs
                let getStyle = getComputedStyle(tabs.children[0].children[0], null)
                let marginLeft = parseFloat(getStyle.marginLeft.substr(0, getStyle.marginLeft.length - 2))
                let marginRight = parseFloat(getStyle.marginRight.substr(0, getStyle.marginRight.length - 2))
                // 元素实际宽度 = 元素的宽度 + 外边距
                this.width = marginLeft + marginRight + tabs.children[0].children[0].offsetWidth

                /**
                 * 以下注释计算方式用于理解实现逻辑
                 **/
                // // 可视区域能放入多少个元素 = 可视区域的宽度 / 子元素实际宽度
                // let num = tabs.offsetWidth / this.width

                // // 被遮挡tab元素的可见部分的宽度 = 可见区域的宽度 - (子元素实际宽度 * num转为整数)
                // this.unoccupied = tabs.offsetWidth - (this.width * parseInt(num))

                // 最终精简为取余(得数跟上面的计算是一样的)
                this.unoccupied = tabs.offsetWidth % this.width
                // 转为整数
                this.tabsCount = parseInt(tabs.offsetWidth / this.width)
            })
        },
        methods: {
            add() {
                this.count++
                this.actionName = `test${this.count}`
            },

            /**
             * 切换tab标签页
             **/
            clickTab(name) {
                if (this.actionName !== name) {
                    this.actionName = name
                }
            },

            /**
             * 关闭tab标签页
             **/
            close(name) {
                let len = this.tabs.length
                let jumpName = null
                if (len > 1) {
                    for (let i = 0; i < len; i++) {
                        if (this.tabs[i].name === name) {
                            this.tabs.splice(i, 1)

                            jumpName = this.tabs[i ? i - 1 : 0].name
                            if (this.actionName !== jumpName && name === this.actionName) {
                                this.actionName = jumpName
                            }

                            this.$nextTick(() => {
                                this.previous()
                            })
                            return
                        }
                    }
                }
            },

            /**
             * 往右偏移
             **/
            next() {
                // scrollWidth获取不准确
                // 使用this.width * this.tabs.length计算出总长度
                let totalWidth = this.width * this.tabs.length

                this.$nextTick(() => {
                    let dom = this.$refs.tabs
                    // 可视区域 < 滚动区域(滚动区域大于可视区域才可以移动)
                    // 移动距离 + 可视区域 = 滚动区域的宽度(上一次的宽度,当点击时才是实际宽度)< 滚动区域
                    if (dom.clientWidth < totalWidth && this.moveX + dom.clientWidth < totalWidth) {
                        // this.moveX为0减去空余空间的宽度
                        this.moveX += this.moveX ? this.width : this.width - this.unoccupied
                        this.translateX(this.moveX)
                    }
                })
            },

            /**
             * 往左偏移
             **/
            previous() {
                if (this.moveX > 0) {
                    this.moveX -= this.width
                    this.translateX(this.moveX)
                }
            },

            /**
             * 开始移动dom
             **/
            translateX(x) {
                this.moveX = x < 0 ? 0 : x
                this.$refs.tabs.children[0].style.transform = `translateX(-${this.moveX}px)`
            }
        }
    }
</script>

<style lang="scss" scoped>
    .main-box {
        height: 500px;
        width: 500px;
        padding: 10px 20px 20px 20px;

        .main-box-tab {
            position: relative;
            padding: 10px 20px;
            overflow: hidden;

            & > i {
                position: absolute;
                cursor: pointer;
                bottom: 15px;

                &:nth-child(1) {
                    left: 0;
                }

                &:nth-child(2) {
                    right: 0;
                }
            }

            .main-box-tab-content {
                overflow: hidden;

                .main-box-tab-roll {
                    transition: transform .5s;
                    display: flex;
                    align-items: center;

                    div {
                        flex-shrink: 0;
                        cursor: pointer;
                        width: 130px;
                        height: 25px;
                        margin: 0 5px;
                        display: flex;
                        align-items: center;
                        justify-content: space-between;

                        span, i {
                            font-size: 12px;
                        }

                        span {
                            margin-left: 10px;
                            overflow: hidden;
                            white-space: nowrap;
                            text-overflow: ellipsis;
                        }

                        i {
                            margin-right: 10px;
                        }
                    }
                }
            }

            .tab-item {
                color: #cccccc;
                background-color: rgba(255, 255, 255, .5);
                border-radius: 0 1px 0 1px;
                border: 1px solid #052141;
            }

            .tab-item-action {
                color: #ffffff;
                background: rgba(0, 180, 255, 0.8);
                border-radius: 0 1px 0 1px;
                border: 1px solid #1E2088;
            }

        }

        .main-box-content {
            height: calc(100% - 70px);
            padding: 10px;
            border: 1px saddlebrown solid;
            background-size: 100% 100%;
        }
    }
</style>

到此这篇关于vue实现tab标签(标签超出自动滚动)的文章就介绍到这了,更多相关vue tab标签 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue中接口域名配置为全局变量的实现方法

    vue中接口域名配置为全局变量的实现方法

    今天小编就为大家分享一篇vue中接口域名配置为全局变量的实现方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-09-09
  • 深入了解Vue3组件传值方式

    深入了解Vue3组件传值方式

    学习过 vue2 的宝子们肯定知道,组件传值是 vue 项目开发过程中必不可少的功能场景,在 vue2 里面有很多传值的方式。今天就来和大家讲讲Vue3的组件传值方式,需要的可以参考一下
    2022-07-07
  • 快速处理vue渲染前的显示问题

    快速处理vue渲染前的显示问题

    下面小编就为大家分享一篇快速处理vue渲染前的显示问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-03-03
  • Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)

    Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)

    下面小编就为大家带来一篇Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • 前端开发利器Vite完整版详解

    前端开发利器Vite完整版详解

    这篇文章主要给大家介绍了关于前端开发利器Vite完整版详解的相关资料,Vite是一种基于ES模块的开发服务器和构建工具,专为现代化的前端开发而设计,需要的朋友可以参考下
    2023-11-11
  • vue3的api解读之ref和reactive示例详解

    vue3的api解读之ref和reactive示例详解

    这篇文章主要介绍了vue3的api解读之ref和reactive详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • Vue中CSS scoped的原理详细讲解

    Vue中CSS scoped的原理详细讲解

    在组件中增加的css加了scoped属性之后,就在会在当前这个组件的节点上增加一个data-v-xxx属性,下面这篇文章主要给大家介绍了关于Vue中CSS scoped原理的相关资料,需要的朋友可以参考下
    2023-01-01
  • Vue如何解决跨域问题详解

    Vue如何解决跨域问题详解

    VUE访问接口的时候,很可能出现跨域请求,从而被提供接口的服务器拒绝,下面这篇文章主要给大家介绍了关于Vue如何解决跨域问题的相关资料,需要的朋友可以参考下
    2022-02-02
  • Vue3中reactive丢失响应式问题详解

    Vue3中reactive丢失响应式问题详解

    在vue3中,如果你用reactive声明了一个对象,用另一个对象直接给它赋值,那么它就会失去响应式,下面这篇文章主要给大家介绍了关于Vue3中reactive丢失响应式问题的相关资料,需要的朋友可以参考下
    2023-03-03
  • 浅谈关于vue中scss公用的解决方案

    浅谈关于vue中scss公用的解决方案

    这篇文章主要介绍了浅谈关于vue中scss公用的解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12

最新评论