Commit ec1a5e268486e40269f82d36b47e8c0a05d87abc
Committed by
GitHub
Merge pull request #2156 from marxy/tabs-scroll
Tabs add scroll
Showing
3 changed files
with
161 additions
and
6 deletions
Show diff stats
examples/routers/tabs.vue
... | ... | @@ -70,7 +70,7 @@ |
70 | 70 | <!--</script>--> |
71 | 71 | |
72 | 72 | |
73 | -<template> | |
73 | +<!-- <template> | |
74 | 74 | <Tabs type="card" closable @on-tab-remove="handleTabRemove"> |
75 | 75 | <TabPane label="标签一" v-if="tab0">标签一的内容</TabPane> |
76 | 76 | <TabPane label="标签二" v-if="tab1">标签二的内容</TabPane> |
... | ... | @@ -92,4 +92,41 @@ |
92 | 92 | } |
93 | 93 | } |
94 | 94 | } |
95 | +</script> --> | |
96 | + | |
97 | +<template> | |
98 | + <div> | |
99 | + <Button type="ghost" @click="toFirst" size="small">to first</Button> | |
100 | + <Button type="ghost" @click="toLast" size="small">to last</Button> | |
101 | + <Tabs type="card" :animated="animated" v-model="activeTab"> | |
102 | + <TabPane v-for="tab in tabs" :key="tab" :label="'标签' + tab" :name="tab+''" closable>标签{{ tab }}</TabPane> | |
103 | + <div slot="extra"> | |
104 | + <Button type="ghost" @click="handleTabsAdd" size="small">增加</Button> | |
105 | + </div> | |
106 | + </Tabs> | |
107 | + </div> | |
108 | +</template> | |
109 | +<script> | |
110 | + export default { | |
111 | + data () { | |
112 | + return { | |
113 | + tabs: 2, | |
114 | + activeTab:"2", | |
115 | + animated:true | |
116 | + } | |
117 | + }, | |
118 | + methods: { | |
119 | + handleTabsAdd () { | |
120 | + this.tabs ++; | |
121 | + this.activeTab = this.tabs + ''; | |
122 | + }, | |
123 | + toFirst () { | |
124 | + this.activeTab = '1'; | |
125 | + }, | |
126 | + toLast () { | |
127 | + this.activeTab = this.tabs+''; | |
128 | + } | |
129 | + } | |
130 | + } | |
95 | 131 | </script> |
132 | + | ... | ... |
src/components/tabs/tabs.vue
1 | 1 | <template> |
2 | 2 | <div :class="classes"> |
3 | 3 | <div :class="[prefixCls + '-bar']"> |
4 | + <div :class="[prefixCls + '-nav-right']" v-if="showSlot"><slot name="extra"></slot></div> | |
4 | 5 | <div :class="[prefixCls + '-nav-container']"> |
5 | - <div :class="[prefixCls + '-nav-wrap']"> | |
6 | - <div :class="[prefixCls + '-nav-scroll']"> | |
7 | - <div :class="[prefixCls + '-nav']" ref="nav"> | |
6 | + <div ref="navWrap" :class="[prefixCls + '-nav-wrap', scrollable ? prefixCls + '-nav-scrollable' : '']" > | |
7 | + <span :class="[prefixCls + '-nav-prev', scrollable ? '' : prefixCls + '-nav-scroll-disabled']" @click="scrollPrev"><Icon type="chevron-left"></Icon></span> | |
8 | + <span :class="[prefixCls + '-nav-next', scrollable ? '' : prefixCls + '-nav-scroll-disabled']" @click="scrollNext"><Icon type="chevron-right"></Icon></span> | |
9 | + <div ref="navScroll" :class="[prefixCls + '-nav-scroll']"> | |
10 | + <div ref="nav" :class="[prefixCls + '-nav']" class="nav-text" :style="navStyle"> | |
8 | 11 | <div :class="barClasses" :style="barStyle"></div> |
9 | 12 | <div :class="tabCls(item)" v-for="(item, index) in navList" @click="handleChange(index)"> |
10 | 13 | <Icon v-if="item.icon !== ''" :type="item.icon"></Icon> |
... | ... | @@ -13,7 +16,6 @@ |
13 | 16 | <Icon v-if="showClose(item)" type="ios-close-empty" @click.native.stop="handleRemove(index)"></Icon> |
14 | 17 | </div> |
15 | 18 | </div> |
16 | - <div :class="[prefixCls + '-nav-right']" v-if="showSlot"><slot name="extra"></slot></div> | |
17 | 19 | </div> |
18 | 20 | </div> |
19 | 21 | </div> |
... | ... | @@ -26,6 +28,7 @@ |
26 | 28 | import Render from '../base/render'; |
27 | 29 | import { oneOf } from '../../utils/assist'; |
28 | 30 | import Emitter from '../../mixins/emitter'; |
31 | + import elementResizeDetectorMaker from 'element-resize-detector'; | |
29 | 32 | |
30 | 33 | const prefixCls = 'ivu-tabs'; |
31 | 34 | |
... | ... | @@ -65,7 +68,11 @@ |
65 | 68 | barWidth: 0, |
66 | 69 | barOffset: 0, |
67 | 70 | activeKey: this.value, |
68 | - showSlot: false | |
71 | + showSlot: false, | |
72 | + navStyle:{ | |
73 | + transform: '' | |
74 | + }, | |
75 | + scrollable:false | |
69 | 76 | }; |
70 | 77 | }, |
71 | 78 | computed: { |
... | ... | @@ -163,6 +170,7 @@ |
163 | 170 | } else { |
164 | 171 | this.barOffset = 0; |
165 | 172 | } |
173 | + this.updateNavScroll(); | |
166 | 174 | }); |
167 | 175 | }, |
168 | 176 | updateStatus () { |
... | ... | @@ -222,6 +230,85 @@ |
222 | 230 | } else { |
223 | 231 | return false; |
224 | 232 | } |
233 | + }, | |
234 | + scrollPrev() { | |
235 | + const containerWidth = this.$refs.navScroll.offsetWidth; | |
236 | + const currentOffset = this.getCurrentScrollOffset(); | |
237 | + | |
238 | + if (!currentOffset) return; | |
239 | + | |
240 | + let newOffset = currentOffset > containerWidth | |
241 | + ? currentOffset - containerWidth | |
242 | + : 0; | |
243 | + | |
244 | + this.setOffset(newOffset); | |
245 | + }, | |
246 | + scrollNext() { | |
247 | + const navWidth = this.$refs.nav.offsetWidth; | |
248 | + const containerWidth = this.$refs.navScroll.offsetWidth; | |
249 | + const currentOffset = this.getCurrentScrollOffset(); | |
250 | + if (navWidth - currentOffset <= containerWidth) return; | |
251 | + | |
252 | + let newOffset = navWidth - currentOffset > containerWidth * 2 | |
253 | + ? currentOffset + containerWidth | |
254 | + : (navWidth - containerWidth); | |
255 | + | |
256 | + this.setOffset(newOffset); | |
257 | + }, | |
258 | + getCurrentScrollOffset() { | |
259 | + const { navStyle } = this; | |
260 | + return navStyle.transform | |
261 | + ? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1]) | |
262 | + : 0; | |
263 | + }, | |
264 | + setOffset(value) { | |
265 | + this.navStyle.transform = `translateX(-${value}px)`; | |
266 | + }, | |
267 | + scrollToActiveTab() { | |
268 | + if (!this.scrollable) return; | |
269 | + const nav = this.$refs.nav; | |
270 | + const activeTab = this.$el.querySelector(`.${prefixCls}-tab-active`); | |
271 | + if(!activeTab) return; | |
272 | + | |
273 | + const navScroll = this.$refs.navScroll; | |
274 | + const activeTabBounding = activeTab.getBoundingClientRect(); | |
275 | + const navScrollBounding = navScroll.getBoundingClientRect(); | |
276 | + const navBounding = nav.getBoundingClientRect(); | |
277 | + const currentOffset = this.getCurrentScrollOffset(); | |
278 | + let newOffset = currentOffset; | |
279 | + | |
280 | + if (navBounding.right < navScrollBounding.right) { | |
281 | + newOffset = nav.offsetWidth - navScrollBounding.width; | |
282 | + } | |
283 | + | |
284 | + if (activeTabBounding.left < navScrollBounding.left) { | |
285 | + newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left); | |
286 | + }else if (activeTabBounding.right > navScrollBounding.right) { | |
287 | + newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right; | |
288 | + } | |
289 | + | |
290 | + if(currentOffset !== newOffset){ | |
291 | + this.setOffset(Math.max(newOffset, 0)); | |
292 | + } | |
293 | + }, | |
294 | + updateNavScroll(){ | |
295 | + const navWidth = this.$refs.nav.offsetWidth; | |
296 | + const containerWidth = this.$refs.navScroll.offsetWidth; | |
297 | + const currentOffset = this.getCurrentScrollOffset(); | |
298 | + if (containerWidth < navWidth) { | |
299 | + this.scrollable = true; | |
300 | + if (navWidth - currentOffset < containerWidth) { | |
301 | + this.setOffset(navWidth - containerWidth); | |
302 | + } | |
303 | + } else { | |
304 | + this.scrollable = false; | |
305 | + if (currentOffset > 0) { | |
306 | + this.setOffset(0); | |
307 | + } | |
308 | + } | |
309 | + }, | |
310 | + handleResize(){ | |
311 | + this.updateNavScroll(); | |
225 | 312 | } |
226 | 313 | }, |
227 | 314 | watch: { |
... | ... | @@ -232,10 +319,18 @@ |
232 | 319 | this.updateBar(); |
233 | 320 | this.updateStatus(); |
234 | 321 | this.broadcast('Table', 'on-visible-change', true); |
322 | + this.$nextTick(function(){ | |
323 | + this.scrollToActiveTab(); | |
324 | + }); | |
235 | 325 | } |
236 | 326 | }, |
237 | 327 | mounted () { |
238 | 328 | this.showSlot = this.$slots.extra !== undefined; |
329 | + this.observer = elementResizeDetectorMaker(); | |
330 | + this.observer.listenTo(this.$refs.navWrap, this.handleResize); | |
331 | + }, | |
332 | + beforeDestroy() { | |
333 | + this.observer.removeListener(this.$refs.navWrap, this.handleResize); | |
239 | 334 | } |
240 | 335 | }; |
241 | 336 | </script> | ... | ... |
src/styles/components/tabs.less
... | ... | @@ -56,6 +56,29 @@ |
56 | 56 | |
57 | 57 | &-nav-right{ |
58 | 58 | float: right; |
59 | + margin-left: 5px; | |
60 | + } | |
61 | + | |
62 | + &-nav-prev{ | |
63 | + position:absolute; | |
64 | + line-height: 32px; | |
65 | + cursor: pointer; | |
66 | + left:0; | |
67 | + } | |
68 | + | |
69 | + &-nav-next{ | |
70 | + position:absolute; | |
71 | + line-height: 32px; | |
72 | + cursor: pointer; | |
73 | + right:0; | |
74 | + } | |
75 | + | |
76 | + &-nav-scrollable{ | |
77 | + padding: 0 12px; | |
78 | + } | |
79 | + | |
80 | + &-nav-scroll-disabled{ | |
81 | + display: none; | |
59 | 82 | } |
60 | 83 | |
61 | 84 | &-nav { | ... | ... |