ScrollNav.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <view class="component-view-scroll-nav" :style="[{position:tabStyle.position,left:tabStyle.left,top:tabStyle.top,bottom:tabStyle.bottom,right:tabStyle.right}]">
  3. <scroll-view scroll-x :scroll-with-animation="animate" enable-flex class="navbar" :scroll-left="navStyle.scrollLeft" :skip-hidden-item-layout="true" :style="{height,background}">
  4. <view class="nav-wrapper">
  5. <view @click="changeTab(key)" :class="['nav-item',{'active':key === modelKey}]" v-for="(item,key) in scrollData" :key="key" :style="{background:fontStyle[key === modelKey?'active':'default']['background']}">
  6. <text :class="['item-label',{animate}]" v-text="item.label" :style="[{fontSize:fontStyle[key === modelKey?'active':'default']['fontSize'],color:fontStyle[key === modelKey?'active':'default']['color']}]"></text>
  7. </view>
  8. <view :class="['line',{animate}]" :style="[{'width':navStyle.width+'px','left':navStyle.left+'px','height':lineStyle.height}]">
  9. <view class="s" :style="[{width:lineStyle.width,'backgroundColor':lineStyle.backgroundColor}]"></view>
  10. </view>
  11. </view>
  12. </scroll-view>
  13. </view>
  14. </template>
  15. <script>
  16. const propStyleMap = {
  17. line:{
  18. color:'backgroundColor',
  19. height:'height',
  20. width:'width'
  21. },
  22. font:{
  23. default:{
  24. defaultColor:'color',
  25. defaultSize:'fontSize',
  26. defaultBackground:'background'
  27. },
  28. active:{
  29. activeColor:'color',
  30. activeSize:'fontSize',
  31. activeBackground:'background'
  32. }
  33. },
  34. fixed:{
  35. top:'top',
  36. left:'left',
  37. bottom:'bottom',
  38. right:'right'
  39. }
  40. };
  41. export default {
  42. components: {},
  43. props: {
  44. scrollData:{
  45. default:[],
  46. type:Array
  47. },
  48. activeKey:{
  49. default:0,
  50. type:Number
  51. },
  52. animate:{
  53. default:true,
  54. type:Boolean
  55. },
  56. height:{
  57. default:'70rpx',
  58. type:String
  59. },
  60. line:{
  61. default:()=>({}),
  62. type:Object
  63. },
  64. font:{
  65. default:()=>({}),
  66. type:Object
  67. },
  68. background:{
  69. default:'transparent',
  70. type:String
  71. },
  72. fixed:{
  73. default:()=>({}),
  74. type:Object
  75. }
  76. },
  77. emits:['tabClick','change','update:activeKey','rendered'],
  78. // mixins: [require('@/components/mixins/Component').default],
  79. data() {
  80. return {
  81. modelKey:null,
  82. navStyle:{
  83. width:0,
  84. left:0,
  85. scrollLeft:0
  86. }
  87. };
  88. },
  89. created() {
  90. },
  91. mounted() {},
  92. unmounted() {},
  93. computed: {
  94. lineStyle:{
  95. get(){
  96. return this.formatProp('line');
  97. }
  98. },
  99. fontStyle:{
  100. get(){
  101. const font = this.font;
  102. var style = {};
  103. for(var key in propStyleMap.font){
  104. style[key] = {};
  105. var cfg = propStyleMap.font[key];
  106. for(var ck in font){
  107. ck in cfg && (style[key][cfg[ck]] = font[ck]);
  108. }
  109. }
  110. return style;
  111. }
  112. },
  113. tabStyle:{
  114. get(){
  115. const formated = this.formatProp('fixed');
  116. Object.keys(formated).length && Object.assign(formated,{
  117. position:'fixed'
  118. });
  119. return formated;
  120. }
  121. }
  122. },
  123. watch: {
  124. activeKey:{
  125. handler(n){
  126. n !== this.modelKey && this.render(n);
  127. },
  128. deep:true,
  129. immediate:true
  130. }
  131. },
  132. methods: {
  133. init(){
  134. },
  135. load(){
  136. },
  137. formatProp(prop){
  138. const oThis = this[prop];
  139. var props = {};
  140. for(var key in oThis){
  141. key in propStyleMap[prop] && (props[propStyleMap[prop][key]] = oThis[key]);
  142. }
  143. return props;
  144. },
  145. $document(self, select, cfg) {
  146. return new Promise((res, rej) => {
  147. if (!self || !select) rej('$document:params error');
  148. uni.createSelectorQuery().in(self).select(select).fields(Object.assign({
  149. id: true,
  150. dataset: true,
  151. rect:true,
  152. size: true,
  153. scrollOffset: true,
  154. context:true
  155. },cfg),data=>res(data)).exec();
  156. })
  157. },
  158. async render(e){
  159. this.move = true;
  160. this.modelKey = e;
  161. await this.$nextTick();
  162. await this.changeStyle(e);
  163. await new Promise(r=>setTimeout(r,300));
  164. this.move = false;
  165. },
  166. async changeTab(e){
  167. !this.move && (async()=>{
  168. this.$emit('tabClick',e);
  169. if(this.modelKey !== e){
  170. this.$emit('update:activeKey',e);
  171. this.$emit('change',e);
  172. await this.render(e);
  173. }
  174. })();
  175. },
  176. async changeStyle(e){
  177. var active = await this.$document(this,'.nav-item.active'),
  178. scroll = await this.$document(this,'.navbar');
  179. if(active && scroll){
  180. var left = active.left+scroll.scrollLeft;
  181. Object.assign(this.navStyle,{
  182. width:active.width,
  183. left,
  184. ...(this.animate ? {scrollLeft:left - scroll.width/2 + active.width/2} : null)
  185. });
  186. }
  187. }
  188. },
  189. directives: {},
  190. errorCaptured() {},
  191. renderTracked() {},
  192. renderTriggered() {},
  193. };
  194. </script>
  195. <style lang='stylus'>
  196. $animate = {transition:all .3s linear};
  197. .component-view-scroll-nav
  198. width 100%;z-index 999;
  199. .navbar
  200. overflow auto;display flex;width 100%;height 70rpx;position relative;background-color #fff;
  201. .nav-wrapper
  202. display flex;position relative;width 100%;
  203. .line
  204. position absolute;bottom 0;height 4rpx;
  205. &.animate
  206. transition all .3s linear;
  207. .s
  208. width 100%;height 100%;margin 0 auto;background-color #02c4c7;border-radius 2rpx;max-width:100%;transition @transition;
  209. .nav-item
  210. display flex;align-items center;padding 0 30rpx;white-space nowrap;width 100%;justify-content center;
  211. &.active
  212. .item-label
  213. color #02c4c7;
  214. &.animate
  215. transition color .3s linear;
  216. .item-label
  217. font-size 24rpx;color #333;
  218. </style>