Implementation approach

First of all, we have a view that supports sliding, and we can implement it based on the Better Scroll, so when we click on each item, according to the position of the item being clicked, we call the Scroll method to scroll it to the middle of the view

The position calculation mainly consists of the following cases

Refer to the _ADJUST method code comment in the complete code below

To further enrich the support, such as meituan takeout catering effect, and realize the linkage effect of horizontal bar and vertical scrolling, as long as the current is updated every time you click or slide, and the horizontal and vertical use the same current

The complete code is as follows

<template> <div class="wrap" id="wrap"> <div class="wrapper1" ref="wrapper1" > <div class="content1" ref="items" > <div class="item" :class="{'item_active': current === todo}" @click="clickHandler(todo)" v-for="(todo) in labels" :key="todo">{{todo}}</div> </div> </div> <cube-scroll-nav-bar :current="current" :labels="labels" @change="changeHandler" > <template v-slot:default="slotProps">  {{ slotProps.txt }} </template> </cube-scroll-nav-bar> <cube-scroll-nav-bar direction="vertical" :current="current" :labels="labels" :txts="txts" @change="changeHandler"> <i slot-scope="props">{{props.txt}}</i> </cube-scroll-nav-bar> </div> </template> <script> import BetterScroll from 'better-scroll' export default { data() { return { current: 'express' labels: [' express ', 'Bob', 'car', 'lift', 'generation driving', 'public transport', 'drive rental cars',' luxury ', 'cars' and' taxi '], TXTS: Express [' 1 ', '2 minibus',' 3 car ', '4 hitching a ride', 'five generations driving', '6 bus',' 7 drive rental cars', '8 luxury car', '9 used cars',' 10 taxis'], bs: {}}}, the methods: { changeHandler(cur) { this.current = cur }, clickHandler(cur) { console.log(cur) this.current = cur this._adjust() }, _adjust() {this.$nextTick(() => {const targetProp = 'clientWidth' const current = this.current $refs.wrapper1[targetProp] const itemsEle = this.$refs.items // scrollerSize 100 * 10 Const scrollerSize = itemsEle[targetProp] // Max scrollerSize minTranslate 450-100 * 10 // When scrollerSize < viewportSize is greater than viewportSize, no scrolling is required. Const minTranslate = math.min (0, Const middleTranslate = viewportSize /2 const middleTranslate = viewportSize /2 const middleTranslate = viewportSize /2  items = itemsEle.children let size = 0 this.labels.every((label, Index) => {if (label === current) {size += (items[index][targetProp] / 2) return false Items [index][targetProp] return true}) // size is the sum of the left width of the click red box + half the width of the current red box // When the click red box position is in the middle left of the view translate < 0, otherwise greater than 0 let translate = middleTranslate - size console.log('size', size) console.log('minTranslate', MinTranslate) console.log(' before translate ', translate) // math.min (0, translate) means zero when clicking on position left of center of view, Max (minTranslate, math.min (0, translate)),v MinTranslate translate = math. Max (minTranslate, math.min (0, translate)) console.log('translate final ', translate) console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') this.bs.scrollTo( translate , 0 ) }) } }, mounted() { let bs = new BetterScroll(this.$refs.wrapper1, { scrollX:true, StartX :0}) this.bs = bs console.log(bs) setTimeout(()=> {// bs.scrollto (-200,0)}, 1000)},} body { height: 100%; } .wrap { height: 600px; overflow: scroll; } .bg { background: #ccc; height: 300px; } </style> <style lang="stylus" scoped> .wrapper1 { width: 450px; overflow: scroll; .content1-wraper { overflow: hidden; min-height: 0px; } .content1 { white-space: nowrap; display: inline-block; } .item { box-sizing: border-box; display:inline-block; width: 102px; border: 1px solid red; height: 102px; } .item_active { color: blue; } } div::-webkit-scrollbar { width: 0 ! important; height: 0 ! important; } div{ -ms-overflow-style: none; overflow: -moz-scrollbars-none; }. Layer - the container {padding: 1.5625 rem; margin-top: 10px; background-color: red; .text{margin: 0 0 0.9375rem; font-family: PingFangSC-Medium; font-size: 1rem; color: #4B4B4D; The line - height: 1.3125 rem; }.item-wrapper{padding: 1.5625rem 0.9375rem; Background: RGBA (216, 216, 216, 0.25); Border - the radius: 0.4375 rem; . Item {margin - top: 1.6875 rem; display: flex; justify-content: space-between; &:first-child { margin-top: 0; } .left{ display: flex; font-family: PingFangSC-Medium, sans-serif; font-size: 16px; color: #323233; text-align: justify; line-height: 22px; .time{ margin-right: 10px; } } .right{ position: relative; padding-right: 13px; font-family: PingFangSC-Medium, sans-serif; font-size: 16px; line-height: 22px; color: #46648C; box-sizing: border-box; &::after{ position: absolute; content: ''; display: block; right: 0; top: 50%; width: 7px; height: 7px; border-top: 2px solid #46648C; border-right: 2px solid #46648C; transform: translateY(-50%) rotate(45deg); line-height: 22px; }}}}} </style> < SVG t="1604497443000" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2837" :class="{ icon: true, rotate: popoverShow, } "> < path d =" M234.88884163 l279.65492248 377.47806845 279.65492248 278.8075161 278.8075161 z "p - id =" 2838" Data - SPM - anchor - id = "a313x. 7781069.0 i0" class = "selected" > < / path > < / SVG >Copy the code