Vue implements tab label (label exceeds automatic scrolling)

Vue implements tab label (label exceeds automatic scrolling)

When the created tab label exceeds the visible area of ​​the page, it will automatically scroll a tab label distance, and you can click to scroll the tab label manually. See the GIF image for the effect.

Effect preview GIF

<template>
    <div class="main-box">
        <button @click="add">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
                // If there is duplicate data, exit the subsequent function execution 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(() => {
                  // (How many tabs are there in total - the number of elements visible when not offset) * Length of a single tab label element - Width of the visible part of the obscured tab element 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))
                // Element actual width = element width + margin this.width = marginLeft + marginRight + tabs.children[0].children[0].offsetWidth

                /**
                 * The following comment calculation method is used to understand the implementation logic**/
                // // How many elements can be placed in the visible area = width of the visible area / actual width of the sub-element // let num = tabs.offsetWidth / this.width

                // // The width of the visible part of the obscured tab element = the width of the visible area - (actual width of the child element * num converted to an integer)
                // this.unoccupied = tabs.offsetWidth - (this.width * parseInt(num))

                //Finally simplified to remainder (the result is the same as the calculation above)
                this.unoccupied = tabs.offsetWidth % this.width
                // Convert to integer this.tabsCount = parseInt(tabs.offsetWidth / this.width)
            })
        },
        methods: {
            add() {
                this.count++
                this.actionName = `test${this.count}`
            },

            /**
             * Switch tab page**/
            clickTab(name) {
                if (this.actionName !== name) {
                    this.actionName = name
                }
            },

            /**
             * Close the tab page**/
            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
                        }
                    }
                }
            },

            /**
             * Shift to the right**/
            next() {
                // scrollWidth is not accurate // Use this.width * this.tabs.length to calculate the total length let totalWidth = this.width * this.tabs.length

                this.$nextTick(() => {
                    let dom = this.$refs.tabs
                    // Visible area < scrolling area (scrolling area can only be moved if it is larger than the visible area)
                    // Moving distance + visible area = scroll area width (the last width, the actual width when clicked) < scroll areaif (dom.clientWidth < totalWidth && this.moveX + dom.clientWidth < totalWidth) {
                        // this.moveX is 0 minus the width of the unoccupied space this.moveX += this.moveX ? this.width : this.width - this.unoccupied
                        this.translateX(this.moveX)
                    }
                })
            },

            /**
             * Left offset**/
            previous() {
                if (this.moveX > 0) {
                    this.moveX -= this.width
                    this.translateX(this.moveX)
                }
            },

            /**
             * Start moving 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>

This is the end of this article about Vue's implementation of tab tags (tabs exceed automatic scrolling). For more relevant Vue tab tag content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Solve the cache problem when vue-router switches tab tags and closes them
  • Vue implements the Tab tag routing effect and uses Animate.css to make transition animation effects
  • vuex + keep-alive to implement tab page caching function
  • Example of tab switching effect implemented by Vue2.0 (content can be customized)
  • Vue2.0 How to add styles to Tab tabs and page switching transitions
  • Detailed explanation of vue-router implementing tab page (single page)

<<:  Linux common commands chmod to modify file permissions 777 and 754

>>:  MySQL Server IO 100% Analysis and Optimization Solution

Recommend

Vue.$set failure pitfall discovery and solution

I accidentally found that Vue.$set was invalid in...

A little-known JS problem: [] == ![] is true, but {} == !{} is false

console.log( [] == ![] ) // true console.log( {} ...

Steps to install RocketMQ instance on Linux

1. Install JDK 1.1 Check whether the current virt...

Solution to the ineffective global style of the mini program custom component

Table of contents Too long to read Component styl...

Basic knowledge points of mysql worm replication

Worms replicate, as the name implies, by themselv...

Summary of methods for cleaning Mysql general_log

Method 1: SET GLOBAL general_log = 'OFF';...

18 Amazing Connections Between Interaction Design and Psychology

Designers need to understand psychology reading n...

Summary of Linux command methods to view used commands

There are many commands used in the system, so ho...

js to achieve cool fireworks effect

This article shares the specific code for using j...

Vue implements adding watermark to uploaded pictures

This article shares the specific implementation c...

Two tools for splitting the screen in the Linux command line terminal

Here are two terminal split screen tools: screen ...

A brief analysis of whether using iframe to call a page will cache the page

Recently, I have a project that requires using ifr...

Perfect solution to Google Chrome autofill problem

In Google Chrome, after successful login, Google ...

CentOS 8 is now available

CentOS 8 is now available! CentOS 8 and RedHat En...

Why I recommend Nginx as a backend server proxy (reason analysis)

1. Introduction Our real servers should not be di...