[vue ]滚动视图解决ElementUI NavMenu 导航菜单过长显示的问题
记录一下工作
需求
导航菜单过长的时候会溢出,需要一个像 Tabs 标签页一样的滚动视图容器,可以左右滚动内部视图。
解决方法
由于时间问题,所以直接将 Tabs 源码抽取出来使用。
这里要做一些特殊处理,不允许 NavMenu 导航菜单滚动视图容器内的元素换行。
如:
使用方法如下:
<scrollView> <div> // 一些不换行内容 </div> </scrollView>
下面是抽出来的源码,稍稍改造了下,左右箭头做了自适应垂直居中,把 elementui的样式抽取出来以及修改了类名,减少依赖。
scrollView.vue
<script> import { addResizeListener, removeResizeListener } from "element-ui/src/utils/resize-event"; const firstUpperCase = str => { return str.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase()); }; export default { props: {}, data () { return { scrollable: false, navOffset: 0, isFocus: false, focusable: true, tabPosition: "top" }; }, computed: { navStyle () { const dir = ["top", "bottom"].indexOf(this.tabPosition) !== -1 ? "X" : "Y"; return { transform: `translate${dir}(-${this.navOffset}px)` }; }, sizeName () { return ["top", "bottom"].indexOf(this.tabPosition) !== -1 ? "width" : "height"; } }, methods: { scrollPrev () { const containerSize = this.$refs.navScroll[ `offset${firstUpperCase(this.sizeName)}` ]; const currentOffset = this.navOffset; if (!currentOffset) return; let newOffset = currentOffset > containerSize ? currentOffset - containerSize : 0; this.navOffset = newOffset; }, scrollNext () { const navSize = this.$refs.nav[`offset${firstUpperCase(this.sizeName)}`]; const containerSize = this.$refs.navScroll[ `offset${firstUpperCase(this.sizeName)}` ]; const currentOffset = this.navOffset; if (navSize - currentOffset <= containerSize) return; let newOffset = navSize - currentOffset > containerSize * 2 ? currentOffset + containerSize : navSize - containerSize; this.navOffset = newOffset; }, scrollToActiveTab () { if (!this.scrollable) return; const nav = this.$refs.nav; const activeTab = this.$el.querySelector(".is-active"); if (!activeTab) return; const navScroll = this.$refs.navScroll; const isHorizontal = ["top", "bottom"].indexOf(this.tabPosition) !== -1; const activeTabBounding = activeTab.getBoundingClientRect(); const navScrollBounding = navScroll.getBoundingClientRect(); const maxOffset = isHorizontal ? nav.offsetWidth - navScrollBounding.width : nav.offsetHeight - navScrollBounding.height; const currentOffset = this.navOffset; let newOffset = currentOffset; if (isHorizontal) { if (activeTabBounding.left < navScrollBounding.left) { newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left); } if (activeTabBounding.right > navScrollBounding.right) { newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right; } } else { if (activeTabBounding.top < navScrollBounding.top) { newOffset = currentOffset - (navScrollBounding.top - activeTabBounding.top); } if (activeTabBounding.bottom > navScrollBounding.bottom) { newOffset = currentOffset + (activeTabBounding.bottom - navScrollBounding.bottom); } } newOffset = Math.max(newOffset, 0); this.navOffset = Math.min(newOffset, maxOffset); }, update () { if (!this.$refs.nav) return; const sizeName = this.sizeName; const navSize = this.$refs.nav[`offset${firstUpperCase(sizeName)}`]; this.height = this.$refs.nav[`offset${firstUpperCase('height')}`]; const containerSize = this.$refs.navScroll[ `offset${firstUpperCase(sizeName)}` ]; const currentOffset = this.navOffset; if (containerSize < navSize) { const currentOffset = this.navOffset; this.scrollable = this.scrollable || {}; this.scrollable.prev = currentOffset; this.scrollable.next = currentOffset + containerSize < navSize; if (navSize - currentOffset < containerSize) { this.navOffset = navSize - containerSize; } } else { this.scrollable = false; if (currentOffset > 0) { this.navOffset = 0; } } } }, updated () { this.update(); }, render () { const { navStyle, scrollable, scrollNext, scrollPrev, height } = this; const lineHeight = { 'line-height' : height + 'px' } const scrollBtn = scrollable ? [ <span class={["scrollView__nav-prev", scrollable.prev ? "" : "is-disabled"]} on-click={scrollPrev} > <i style={lineHeight} class="el-icon-arrow-left"></i> </span>, <span class={["scrollView__nav-next", scrollable.next ? "" : "is-disabled"]} on-click={scrollNext} > <i style={lineHeight} class="el-icon-arrow-right"></i> </span> ] : null; return ( <div class={[ "scrollView__nav-wrap", scrollable ? "is-scrollable" : "", `is-${this.tabPosition}` ]} style={{ width: "100%" }} > {scrollBtn} <div class={["scrollView__nav-scroll"]} ref="navScroll" > <div class={["scrollView__nav", `is-${this.tabPosition}`]} ref="nav" style={navStyle} role="tablist" > {this.$slots.default} </div> </div> </div> ); }, mounted () { addResizeListener(this.$el, this.update); }, beforeDestroy () { if (this.$el && this.update) removeResizeListener(this.$el, this.update); } }; </script> <style lang="less"> .scrollView__nav-wrap { overflow: hidden; margin-bottom: -1px; position: relative; } .scrollView__nav-wrap.is-scrollable { padding: 0 20px; -webkit-box-sizing: border-box; box-sizing: border-box; } .scrollView__nav-wrap::after { display: none; } .scrollView__nav-scroll { overflow: hidden; } .scrollView__nav { white-space: nowrap; position: relative; transition: transform 0.3s, -webkit-transform 0.3s; float: left; z-index: 2; } .scrollView__nav-prev { left: 0; } .scrollView__nav-next { right: 0; } .scrollView__nav-next, .scrollView__nav-prev { position: absolute; cursor: pointer; line-height: 44px; font-size: 12px; color: #909399; } </style>