微信自带的顶部导航栏是无法支持自定义icon和增加元素的,在开发小程序的时候自带的根本满足不了需求,分享一个封装好的组件,支持自定义icon、扩展dom,适配安卓、ios、h5,全面屏。
我用的是京东的Taro多端编译框架写的小程序,原生的也可以适用,用到的微信/taro的api做调整就行,实现效果如下。


1、NavBar.js
1import Taro from '@tarojs/taro'; 2import React, { 3 4 Component } from 'react' 5import { 6 7 View } from '@tarojs/components'; 8import { 9 10 isFunction } from '../../utils/index' //判断是否为函数,可以用loadsh的_isFunction,也可以自己封装。 11import './NavBar.less'; 12 13 14function getSystemInfo () { 15 16 17 if (Taro.globalSystemInfo && !Taro.globalSystemInfo.ios) { 18 19 20 return Taro.globalSystemInfo; 21 } else { 22 23 24 // h5环境下忽略navbar 25 if (!isFunction(Taro.getSystemInfoSync)) { 26 27 28 return null; 29 } 30 let systemInfo = Taro.getSystemInfoSync() || { 31 32 33 model: '', 34 system: '' 35 }; 36 let ios = !!(systemInfo.system.toLowerCase().search('ios') + 1); 37 let rect; 38 try { 39 40 41 rect = Taro.getMenuButtonBoundingClientRect ? Taro.getMenuButtonBoundingClientRect() : null; 42 if (rect === null) { 43 44 45 throw 'getMenuButtonBoundingClientRect error'; 46 } 47 //取值为0的情况 有可能width不为0 top为0的情况 48 if (!rect.width || !rect.top || !rect.left || !rect.height) { 49 50 51 throw 'getMenuButtonBoundingClientRect error'; 52 } 53 } catch (error) { 54 55 56 let gap; //胶囊按钮上下间距 使导航内容居中 57 let width = 96; //胶囊的宽度 58 if (systemInfo.platform === 'android') { 59 60 61 gap = 8; 62 width = 96; 63 } else if (systemInfo.platform === 'devtools') { 64 65 66 if (ios) { 67 68 69 gap = 5.5; //开发工具中ios手机 70 } else { 71 72 73 gap = 7.5; //开发工具中android和其他手机 74 } 75 } else { 76 77 78 gap = 4; 79 width = 88; 80 } 81 if (!systemInfo.statusBarHeight) { 82 83 84 //开启wifi的情况下修复statusBarHeight值获取不到 85 systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20; 86 } 87 rect = { 88 89 90 //获取不到胶囊信息就自定义重置一个 91 bottom: systemInfo.statusBarHeight + gap + 32, 92 height: 32, 93 left: systemInfo.windowWidth - width - 10, 94 right: systemInfo.windowWidth - 10, 95 top: systemInfo.statusBarHeight + gap, 96 width: width 97 }; 98 console.log('error', error); 99 console.log('rect', rect); 100 } 101 102 let navBarHeight = ''; 103 if (!systemInfo.statusBarHeight) { 104 105 106 //开启wifi和打电话下 107 systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20; 108 navBarHeight = (function () { 109 110 111 let gap = rect.top - systemInfo.statusBarHeight; 112 return 2 * gap + rect.height; 113 })(); 114 115 systemInfo.statusBarHeight = 0; 116 systemInfo.navBarExtendHeight = 0; //下方扩展4像素高度 防止下方边距太小 117 } else { 118 119 120 navBarHeight = (function () { 121 122 123 let gap = rect.top - systemInfo.statusBarHeight; 124 return systemInfo.statusBarHeight + 2 * gap + rect.height; 125 })(); 126 if (ios) { 127 128 129 systemInfo.navBarExtendHeight = 4; //下方扩展4像素高度 防止下方边距太小 130 } else { 131 132 133 systemInfo.navBarExtendHeight = 0; 134 } 135 } 136 137 systemInfo.navBarHeight = navBarHeight; //导航栏高度不包括statusBarHeight 138 systemInfo.capsulePosition = rect; //右上角胶囊按钮信息bottom: 58 height: 32 left: 317 right: 404 top: 26 width: 87 目前发现在大多机型都是固定值 为防止不一样所以会使用动态值来计算nav元素大小 139 systemInfo.ios = ios; //是否ios 140 Taro.globalSystemInfo = systemInfo; //将信息保存到全局变量中,后边再用就不用重新异步获取了 141 //console.log('systemInfo', systemInfo); 142 return systemInfo; 143 } 144} 145let globalSystemInfo = getSystemInfo(); 146class NavBar extends Component { 147 148 149 constructor(props) { 150 151 152 super(props); 153 this.state = { 154 155 156 configStyle: this.setStyle(globalSystemInfo) 157 }; 158 } 159 static options = { 160 161 162 multipleSlots: true, 163 addGlobalClass: true 164 }; 165 166 UNSAFE_componentWillMount () { 167 168 169 //获取高度 170 // let query = Taro.createSelectorQuery().in(this.$scope) 171 // query.select('.lxy-nav-bar').boundingClientRect(rect=>{ 172 173 174 // const navHeight = rect.height 175 // this.props.personalHomeMod.changeState('navHeight',navHeight) 176 // // console.log('navHeight',toJS(this.props.personalHomeMod.state)) 177 // }).exec() 178 } 179 componentDidShow () { 180 181 182 if (globalSystemInfo.ios) { 183 184 185 globalSystemInfo = getSystemInfo(); 186 this.setState({ 187 188 189 configStyle: this.setStyle(globalSystemInfo) 190 }); 191 } 192 } 193 handleBackClick () { 194 195 196 if (isFunction(this.props.onBack)) { 197 198 199 this.props.onBack(); 200 } else { 201 202 203 const pages = Taro.getCurrentPages(); 204 if (pages.length >= 2) { 205 206 207 Taro.navigateBack({ 208 209 210 delta: this.props.delta 211 }); 212 } 213 } 214 } 215 handleGoHomeClick () { 216 217 218 if (isFunction(this.props.onHome)) { 219 220 221 this.props.onHome(); 222 } 223 } 224 handleSearchClick () { 225 226 227 if (isFunction(this.props.onSearch)) { 228 229 230 this.props.onSearch(); 231 } 232 } 233 static defaultProps = { 234 235 236 extClass: '', 237 background: 'rgba(255,255,255,1)', //导航栏背景 238 color: '#000000', 239 title: '', 240 searchText: '点我搜索', 241 searchBar: false, 242 back: false, 243 home: false, 244 iconTheme: 'black', 245 delta: 1 246 }; 247 248 setStyle (systemInfo) { 249 250 251 const { 252 253 statusBarHeight, navBarHeight, capsulePosition, navBarExtendHeight, ios, windowWidth } = systemInfo; 254 const { 255 256 back, home, title, color } = this.props; 257 let rightDistance = windowWidth - capsulePosition.right; //胶囊按钮右侧到屏幕右侧的边距 258 let leftWidth = windowWidth - capsulePosition.left; //胶囊按钮左侧到屏幕右侧的边距 259 260 let navigationbarinnerStyle = [ 261 `color:${ 262 263 color}`, 264 //`background:${background}`, 265 `height:${ 266 267 navBarHeight + navBarExtendHeight}px`, 268 `padding-top:${ 269 270 statusBarHeight}px`, 271 `padding-right:${ 272 273 leftWidth}px`, 274 `padding-bottom:${ 275 276 navBarExtendHeight}px` 277 ].join(';'); 278 let navBarLeft; 279 if ((back && !home) || (!back && home)) { 280 281 282 navBarLeft = [ 283 `width:${ 284 285 capsulePosition.width}px`, 286 `height:${ 287 288 capsulePosition.height}px`, 289 `margin-left:0px`, 290 `margin-right:${ 291 292 rightDistance}px` 293 ].join(';'); 294 } else if ((back && home) || title) { 295 296 297 navBarLeft = [ 298 `width:${ 299 300 capsulePosition.width}px`, 301 `height:${ 302 303 capsulePosition.height}px`, 304 `margin-left:${ 305 306 rightDistance}px` 307 ].join(';'); 308 } else { 309 310 311 navBarLeft = [`width:auto`, `margin-left:0px`].join(';'); 312 } 313 return { 314 315 316 navigationbarinnerStyle, 317 navBarLeft, 318 navBarHeight, 319 capsulePosition, 320 navBarExtendHeight, 321 ios, 322 rightDistance 323 }; 324 } 325 326 render () { 327 328 329 const { 330 331 332 navigationbarinnerStyle, 333 navBarLeft, 334 navBarHeight, 335 capsulePosition, 336 navBarExtendHeight, 337 ios, 338 rightDistance 339 } = this.state.configStyle; 340 const { 341 342 343 title, 344 background, 345 backgroundColorTop, 346 back, 347 home, 348 searchBar, 349 searchText, 350 iconTheme, 351 extClass 352 } = this.props; 353 let nav_bar__center; 354 if (title) { 355 356 357 nav_bar__center = <text>{ 358 359 title}</text>; 360 } else if (searchBar) { 361 362 363 nav_bar__center = ( 364 <View 365 className='lxy-nav-bar-search' 366 style={ 367 368 `height:${ 369 370 capsulePosition.height}px;`} 371 onClick={ 372 373 this.handleSearchClick.bind(this)} 374 > 375 <View className='lxy-nav-bar-search__icon' /> 376 <View className='lxy-nav-bar-search__input'>{ 377 378 searchText}</View> 379 </View> 380 ); 381 } else { 382 383 384 /* eslint-disable */ 385 nav_bar__center = this.props.renderCenter; 386 /* eslint-enable */ 387 } 388 return ( 389 <View 390 className={ 391 392 `lxy-nav-bar ${ 393 394 ios ? 'ios' : 'android'} ${ 395 396 extClass}`} 397 style={ 398 399 `background: ${ 400 401 backgroundColorTop ? backgroundColorTop : background};height:${ 402 403 navBarHeight + 404 navBarExtendHeight}px;`} 405 > 406 <View 407 className={ 408 409 `lxy-nav-bar__placeholder ${ 410 411 ios ? 'ios' : 'android'}`} 412 style={ 413 414 `padding-top: ${ 415 416 navBarHeight + navBarExtendHeight}px;`} 417 /> 418 <View 419 className={ 420 421 `lxy-nav-bar__inner ${ 422 423 ios ? 'ios' : 'android'}`} 424 style={ 425 426 `background:${ 427 428 background};${ 429 430 navigationbarinnerStyle};`} 431 > 432 <View className='lxy-nav-bar__left' style={ 433 434 navBarLeft}> 435 { 436 437 back && !home && ( 438 <View 439 onClick={ 440 441 this.handleBackClick.bind(this)} 442 className={ 443 444 `lxy-nav-bar__button lxy-nav-bar__btn_goback ${ 445 446 iconTheme}`} 447 /> 448 )} 449 { 450 451 !back && home && ( 452 <View 453 onClick={ 454 455 this.handleGoHomeClick.bind(this)} 456 className={ 457 458 `lxy-nav-bar__button lxy-nav-bar__btn_gohome ${ 459 460 iconTheme}`} 461 /> 462 )} 463 { 464 465 back && home && ( 466 <View className={ 467 468 `lxy-nav-bar__buttons ${ 469 470 ios ? 'ios' : 'android'}`}> 471 <View 472 onClick={ 473 474 this.handleBackClick.bind(this)} 475 className={ 476 477 `lxy-nav-bar__button lxy-nav-bar__btn_goback ${ 478 479 iconTheme}`} 480 /> 481 <View 482 onClick={ 483 484 this.handleGoHomeClick.bind(this)} 485 className={ 486 487 `lxy-nav-bar__button lxy-nav-bar__btn_gohome ${ 488 489 iconTheme}}`} 490 /> 491 </View> 492 )} 493 { 494 495 !back && !home && this.props.renderLeft} 496 </View> 497 <View className='lxy-nav-bar__center' style={ 498 499 `padding-left: ${ 500 501 rightDistance}px`}> 502 { 503 504 nav_bar__center} 505 </View> 506 <View className='lxy-nav-bar__right' style={ 507 508 `margin-right: ${ 509 510 rightDistance}px`}> 511 { 512 513 this.props.renderRight} 514 </View> 515 </View> 516 </View> 517 ); 518 } 519} 520 521export default NavBar; 522
2、NavBar.less
1view, 2text, 3scroll-view, 4input, 5button, 6image, 7cover-view { 8 9 10 box-sizing: border-box; 11} 12page { 13 14 15 /* prettier-ignore */ 16 --height: 44PX; /* 4*2+32 */ 17 /* prettier-ignore */ 18 --right: 97PX; /* 10+87 */ 19 /* prettier-ignore */ 20 --navBarExtendHeight: 4PX; 21 /* prettier-ignore */ 22 --navBarHeight: 68PX; 23 box-sizing: border-box; 24} 25.lxy-nav-bar .ios { 26 27 28 /* prettier-ignore */ 29 --height: 44PX; /* 4*2+32 */ 30 /* prettier-ignore */ 31 --right: 97PX; /* 10+87 */ 32 /* prettier-ignore */ 33 --navBarExtendHeight: 4PX; 34 box-sizing: border-box; 35} 36.lxy-nav-bar .android { 37 38 39 /* prettier-ignore */ 40 --height: 48PX; /* 8*2+32 */ 41 /* prettier-ignore */ 42 --right: 96PX; /* 10+87 */ 43 /* prettier-ignore */ 44 --navBarExtendHeight: 4PX; 45 box-sizing: border-box; 46} 47.lxy-nav-bar .devtools { 48 49 50 /* prettier-ignore */ 51 --height: 42PX; /* 5*2+32 */ 52 /* prettier-ignore */ 53 --right: 88PX; /* 10+87 */ 54 /* prettier-ignore */ 55 --navBarExtendHeight: 4PX; 56 box-sizing: border-box; 57} 58.lxy-nav-bar__inner { 59 60 61 position: fixed; 62 top: 0; 63 left: 0; 64 z-index: 5001; 65 /* prettier-ignore */ 66 height: var(--navBarHeight); 67 display: flex; 68 align-items: center; 69 padding-right: var(--right); 70 width: 100%; 71 /* prettier-ignore */ 72 padding-top: 20PX; 73 /* prettier-ignore */ 74 padding-bottom:4PX; 75 .placeholder { 76 77 78 position: absolute; 79 top: 0; 80 left: 0; 81 width: 100%; 82 } 83} 84.lxy-nav-bar__inner .lxy-nav-bar__left { 85 86 87 position: relative; 88 width: var(--right); 89 /* prettier-ignore */ 90 height: 32PX; 91 /* padding-left: 10PX; */ 92 /* prettier-ignore */ 93 margin-left:10PX; 94 display: flex; 95 align-items: center; 96} 97.lxy-nav-bar__buttons { 98 99 100 height: 100%; 101 width: 100%; 102 display: flex; 103 align-items: center; 104 /* prettier-ignore */ 105 border-radius: 16PX; 106 border: 1px solid rgba(204, 204, 204, 0.6); 107 position: relative; 108} 109.lxy-nav-bar__buttons.android { 110 111 112 border: 1px solid rgba(234, 234, 234, 0.6); 113} 114.lxy-nav-bar__buttons::after { 115 116 117 position: absolute; 118 content: ''; 119 width: 1px; 120 /* prettier-ignore */ 121 height: 18.4PX; 122 background: rgba(204, 204, 204, 0.6); 123 left: 50%; 124 top: 50%; 125 transform: translate(-50%, -50%); 126} 127.lxy-nav-bar__buttons.android::after { 128 129 130 background: rgba(234, 234, 234, 0.6); 131} 132.lxy-nav-bar__button { 133 134 135 width: 50%; 136 height: 100%; 137 display: flex; 138 /* prettier-ignore */ 139 font-size: 12PX; 140 background-repeat: no-repeat; 141 background-position: center center; 142 background-size: 1em 2em; 143} 144 145.lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_goback:active, 146.lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_gohome:active { 147 148 149 opacity: 0.5; 150} 151.lxy-nav-bar__inner .lxy-nav-bar__center { 152 153 154 /* prettier-ignore */ 155 font-size: 17PX; 156 /* prettier-ignore */ 157 line-height: 17PX; 158 text-align: center; 159 position: relative; 160 flex: 1; 161 display: -webkit-box; 162 display: -webkit-flex; 163 display: flex; 164 align-items: center; 165 justify-content: center; 166 /* prettier-ignore */ 167 padding-left: 10PX; 168 text { 169 170 171 margin-top: -2px; 172 font-size:34px; 173 font-weight:550; 174 line-height:44px; 175 } 176} 177.lxy-nav-bar__inner .lxy-nav-bar__loading { 178 179 180 font-size: 0; 181} 182.lxy-nav-bar__inner .lxy-nav-bar__loading .lxy-loading { 183 184 185 margin-left: 0; 186} 187.lxy-nav-bar__inner .lxy-nav-bar__right { 188 189 190 /* prettier-ignore */ 191 margin-right: 10PX; 192} 193.lxy-nav-bar__placeholder { 194 195 196 height: var(--navBarHeight); 197 background: #f8f8f8; 198 position: relative; 199 z-index: 50; 200 visibility: hidden; 201} 202 203.lxy-nav-bar-search { 204 205 206 width: 100%; 207 height: 100%; 208 display: flex; 209 justify-content: center; 210 align-items: center; 211 width: 100%; 212 /* prettier-ignore */ 213 height: 32PX; 214 /* prettier-ignore */ 215 border-radius: 16PX; 216 position: relative; 217 background: #f6f6f6; 218} 219 220.lxy-nav-bar-search__input { 221 222 223 height: 100%; 224 display: flex; 225 align-items: center; 226 color: #999; 227 /* prettier-ignore */ 228 font-size: 15PX; 229 /* prettier-ignore */ 230 line-height: 15PX; 231} 232.lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_goback { 233 234 235 background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E"); 236} 237.lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_goback.white { 238 239 240 background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z' fill='%23ffffff'/%3E%3C/svg%3E"); 241} 242.lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_gohome { 243 244 245 background-image: url("data:image/svg+xml,%3Csvg t='1565752242401' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='4326' width='48' height='48'%3E%3Cpath d='M537.13744054-106.18008097999996c-12.56871953-12.56871953-31.42179957-12.56871953-50.27488108 0L28.10427953 195.46919964000006c-12.56871953 12.56871953-18.85308003 25.13744054-18.85308003 37.70616005l0 609.58292171c0 25.13744054 18.85308003 43.99052059 43.9905191 43.9905191l301.6492806 0c18.85308004 0 31.42179957-12.56871953 31.42180105-31.42179957l0-314.21800013c0-18.85308004 12.56871953-31.42179957 31.42179956-31.42180105l188.53080038 0c18.85308004 0 31.42179957 12.56871953 31.42179956 31.42180105l0 314.21800013c0 18.85308004 12.56871953 31.42179957 31.42180105 31.42179957L970.7582814 886.7488005c25.13744054 0 43.99052059-18.85308003 43.9905191-43.9905191L1014.7488005 233.17535969000005c0-12.56871953-6.2843605-25.13744054-18.85308003-37.70616005l-458.75827993-301.64928062z' fill='%23000000' p-id='4327'%3E%3C/path%3E%3C/svg%3E"); 246 /* prettier-ignore */ 247 background-size: 17PX 34PX; 248 margin-top: 10px; 249} 250.lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_gohome.white { 251 252 253 background-image: url("data:image/svg+xml,%3Csvg t='1565752242401' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='4326' width='48' height='48'%3E%3Cpath d='M537.13744054-106.18008097999996c-12.56871953-12.56871953-31.42179957-12.56871953-50.27488108 0L28.10427953 195.46919964000006c-12.56871953 12.56871953-18.85308003 25.13744054-18.85308003 37.70616005l0 609.58292171c0 25.13744054 18.85308003 43.99052059 43.9905191 43.9905191l301.6492806 0c18.85308004 0 31.42179957-12.56871953 31.42180105-31.42179957l0-314.21800013c0-18.85308004 12.56871953-31.42179957 31.42179956-31.42180105l188.53080038 0c18.85308004 0 31.42179957 12.56871953 31.42179956 31.42180105l0 314.21800013c0 18.85308004 12.56871953 31.42179957 31.42180105 31.42179957L970.7582814 886.7488005c25.13744054 0 43.99052059-18.85308003 43.9905191-43.9905191L1014.7488005 233.17535969000005c0-12.56871953-6.2843605-25.13744054-18.85308003-37.70616005l-458.75827993-301.64928062z' fill='%23ffffff' p-id='4327'%3E%3C/path%3E%3C/svg%3E"); 254 /* prettier-ignore */ 255 background-size: 17PX 34PX; 256 margin-top: 10px; 257} 258.lxy-nav-bar-search__icon { 259 260 261 /* prettier-ignore */ 262 width: 22PX; 263 /* prettier-ignore */ 264 height: 22PX; 265 display: flex; 266 align-items: center; 267 justify-content: center; 268 background-image: url("data:image/svg+xml,%3Csvg t='1565691512239' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='1240' width='48' height='48'%3E%3Cpath d='M819.2 798.254545L674.909091 653.963636c46.545455-48.872727 74.472727-114.036364 74.472727-186.181818 0-151.272727-123.345455-274.618182-274.618182-274.618182-151.272727 0-274.618182 123.345455-274.618181 274.618182 0 151.272727 123.345455 274.618182 274.618181 274.618182 65.163636 0 128-23.272727 174.545455-62.836364l144.290909 144.290909c2.327273 2.327273 6.981818 4.654545 11.636364 4.654546s9.309091-2.327273 11.636363-4.654546c6.981818-6.981818 6.981818-18.618182 2.327273-25.6zM235.054545 467.781818c0-132.654545 107.054545-239.709091 239.709091-239.709091 132.654545 0 239.709091 107.054545 239.709091 239.709091 0 132.654545-107.054545 239.709091-239.709091 239.709091-132.654545 0-239.709091-107.054545-239.709091-239.709091z' fill='%23999999' p-id='1241'%3E%3C/path%3E%3C/svg%3E"); 269 background-repeat: no-repeat; 270 background-size: cover; 271} 272input{ 273 274 275 padding:0 20px !important; 276 text-align: start !important; 277} 278
属性:

slot:
