uniapp 微信小程序 tabs选项卡组件,可以设置固定顶部-吸顶(fixed),随页面滚动自动切换选项(仿淘宝详情),附带选项切换动画
可以设置tabs贴顶,(固定在顶部)点击选项,页面滚动到指定类型位置页面滚动,typeTabs滚动到指定的类型this.$refs.tabs.clickType:调用子组件的方法,第三个值传true,防止页面点击回调多次出发,导致滚动错乱onReady:页面加载完成后根据ID获取对应选项在页面中的top值,用于页面滚动时的判断依据onPageScroll:使用timer节流,防止计算.......
·
预览效果
typeTabs组件代码
<template>
<view>
<scroll-view v-show="isShow" :class="{'fixed':isFixed}" scroll-x="true"
class="scroll-view d-flex bg-white position-relative">
<view class="item text-center nowarp" v-for="(item,index) in sources" :key="index"
:style="'width:'+(!isScroll?(100/sources.length)+'%':'auto')"
:class="{'active font-weight-bold':type==item.type}" @click="clickType(item,index)">
{{item.name}}
</view>
<!-- 滚动条 -->
<view class="line-box" :style="'left:'+activeLeft+'px;'"></view>
</scroll-view>
<!-- 当固定顶部时,占位元素,高度可修改 -->
<view v-show="isFixed && isShow" :style="'height:'+height+'px'"></view>
</view>
</template>
<script>
export default {
name: "typeTabs",
props: {
sources: Array, //数据源
isShow: { //是否显示(默认展示,不用可以删除)
type: Boolean,
default: true
},
isFixed: { //是否固定在顶部
type: Boolean,
default: false
},
isScroll: { //是否可以横向滚动 值false:每项宽度为(100/sources.length)%
type: Boolean, //值true: 宽度自适应
default: false
},
currentType: { //默认选项,不传为第一项
type: Object,
default: () => {
{}
}
}
},
data() {
return {
activeLeft: 0, //滚动条的X值
type: "", //当前选项
height: "44"
};
},
mounted: function() {
//赋默认值
this.type = this.currentType ? this.currentType.type : this.sources.length ? this.sources[0].type : ""
setTimeout(() => {
this.$nextTick(() => {
this.getActiveElementY();
})
}, 200)
if (this.isFixed) {
uni.createSelectorQuery().in(this)
.select(".fixed") //目标节点
.boundingClientRect((target) => {
if (target.height) {
this.height = target.height;
}
})
.exec();
}
},
methods: {
clickType(item, index, isUpate) {
this.type = item.type;
setTimeout(() => {
if (!isUpate) {
this.$emit("clickItem", item, index);
}
this.$nextTick(() => {
this.getActiveElementY();
})
}, 200)
},
// 获取当前选项和scroll-view的scrollLeft值计算得出滚动条位置
getActiveElementY(element = '.item.active') {
let query = uni.createSelectorQuery().in(this);
var promise1 = new Promise((resolve, reject) => {
query.select(".scroll-view").fields({
scrollOffset: true
}, res => {
if (res) {
resolve(res.scrollLeft)
}
resolve(0)
}).exec();
})
var promise2 = new Promise((resolve, reject) => {
query.select(element).boundingClientRect(async res => {
if (res) {
resolve(res.left + (res.width / 2))
}
resolve(0)
}).exec();
})
Promise.all([promise1, promise2].map(item => item.catch(error => ""))).then(res => {
var left = 0
res.map(item => {
left += item
})
this.activeLeft = left;
})
}
}
}
</script>
<style lang="scss" scoped>
.line-box {
position: absolute;
width: 56rpx;
height: 4rpx;
// background-color: #2B323C;
background-color: #0D6ED9;
// bottom: 48rpx;
top: 74rpx;
transform: translateX(-50%);
transition: all 0.1s;
}
.item {
display: inline-block;
max-width: 400rpx;
padding: 16rpx 40rpx;
}
.item.active {
color: #0D6ED9;
}
.fixed {
position: fixed;
top: 0;
/* #ifdef H5 */
top: 44px;
/* #endif */
left: 0;
right: 0;
z-index: 2;
box-shadow: 0rpx 2rpx 6rpx 0rpx rgba(0, 0, 0, 0.06);
}
</style>
使用方式
<typeTabs ref="tabs" :sources="typeList" @clickItem="clickType" :isFixed="true" :isScroll="true"></typeTabs>
点击选项,页面滚动到指定类型位置
methods: {
clickType(item) {
this.isScroll = true;
var _this = this;
uni.createSelectorQuery()
.select(".container") //对应外层节点
.boundingClientRect((container) => {
uni.createSelectorQuery()
.select("#" + item.type) //目标节点
.boundingClientRect((target) => {
uni.pageScrollTo({
duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错
scrollTop: target.top - container.top -
44, //滚动到实际距离是元素距离顶部的距离减去最外层盒子的滚动距离
complete: ()=>{
setTimeout(() => {
_this.isScroll = false;
},100)
}
});
})
.exec();
})
.exec();
}
}
页面滚动,typeTabs滚动到指定的类型
this.$refs.tabs.clickType:调用子组件的方法,第三个值传true,防止页面点击回调多次出发,导致滚动错乱
if (event.scrollTop + 44 >= item.top) {:44可根据实际情况修改,也可以通过节点信息计算
onPageScroll: function(event) {
if (this.isScroll || this.timer) return
this.timer = setTimeout(() => {
this.typeList.map((item, index) => {
if (item.isActive) return;
if (event.scrollTop + 44 >= item.top) {
if (!item.isActive) {
item.isActive = true;
this.$refs.tabs.clickType(item, index, true);
item.isActive = false;
}
}
})
this.timer = null;
}, 50)
}
父页面代码
onReady:页面加载完成后根据ID获取对应选项在页面中的top值,用于页面滚动时的判断依据
onPageScroll:使用timer节流,防止计算量过大
isScroll: 用于防止选项点击事件与页面滚动事件冲突,如果有其他解决方案可以联系我
<template>
<view class="container">
<typeTabs ref="tabs" :sources="typeList" @clickItem="clickType" :isFixed="true" :isScroll="true"></typeTabs>
<view class="content-box border-top-24" id="person">
<view class="font-36 font-weight-bold title-box">服务对象</view>
<view class="line">
<view class="label-box">头像</view>
<view>XXX</view>
</view>
<view class="line">
<view class="label-box">姓名</view>
<view>陈爷爷</view>
</view>
<view class="line">
<view class="label-box">性别</view>
<view>男</view>
</view>
<view class="line">
<view class="label-box">身份证号</view>
<view>234234234234213421X</view>
</view>
<view class="line">
<view class="label-box">手机号</view>
<view>XXXXXX</view>
</view>
<view class="line">
<view class="label-box">居住地址</view>
<view>XXXXXX</view>
</view>
</view>
<view class="content-box border-top-24" id="project">
<view class="font-36 font-weight-bold title-box">服务项目</view>
<view class="line">
<view class="label-box">服务项目</view>
<view>XXXXXX</view>
</view>
<view class="line">
<view class="label-box">标准时长</view>
<view>XXXXXX</view>
</view>
<view class="line">
<view class="label-box">服务费用</view>
<view>XXXXXX</view>
</view>
</view>
<view class="content-box border-top-24" id="info">
<view class="font-36 font-weight-bold title-box">服务信息</view>
<view class="line">
<view class="label-box">开始时间</view>
<view>2022年4月6日 10:46</view>
</view>
<view class="line">
<view class="label-box">开始地址</view>
<view>XXXXXX</view>
</view>
<view class="line">
<view class="label-box">服务时长</view>
<view>XXXXXX</view>
</view>
<view class="line">
<view class="label-box">结束时间</view>
<view>2022年4月6日 10:46</view>
</view>
<view class="line">
<view class="label-box">结束地址</view>
<view>XXXXXX</view>
</view>
</view>
<view class="content-box border-top-24" id="video">
<view class="font-36 font-weight-bold title-box border-bottom-2">录音录频</view>
<view class="pt-40 d-flex font-24 desc-color">
<view
class="operation-box mr-32 d-flex flex-direction-column align-items-center justify-content-center">
<image mode="widthFix" class="icon" src="@/static/images/audio.png" alt=""></image>
<view class="mt-16">开始录音</view>
</view>
<view class="operation-box d-flex flex-direction-column align-items-center justify-content-center">
<image mode="widthFix" class="icon" src="@/static/images/video.png" alt=""></image>
<view class="mt-16">开始录频</view>
</view>
</view>
</view>
<view class="content-box border-top-24" id="release">
<view class="font-36 font-weight-bold title-box border-bottom-2">服务晒单</view>
<view class="pt-40">
<textarea maxlength="-1" class="w-100" placeholder-class="placeholder server"
placeholder="说说我的服务感受......" />
</view>
</view>
</view>
</template>
<script>
import typeTabs from "@/components/typeTabs/typeTabs.vue"
export default {
data() {
return {
timer: null,
isShow: false,
isFixed: false,
isScroll: false,
form: {},
typeList: [{
type: "person",
name: "服务对象"
}, {
type: "project",
name: "服务项目"
}, {
type: "info",
name: "服务信息"
}, {
type: "video",
name: "录音录频"
}, {
type: "release",
name: "服务晒单"
}],
}
},
components: {
typeTabs,
},
onReady: function() {
this.typeList.map(async item => {
item.top = await new Promise((reslove, reject) => {
uni.createSelectorQuery()
.select(".container") //对应外层节点
.boundingClientRect((container) => {
uni.createSelectorQuery()
.select("#" + item.type) //目标节点
.boundingClientRect((target) => {
reslove(target.top)
})
.exec();
})
.exec();
})
return item;
})
},
onPageScroll: function(event) {
if (this.isScroll || this.timer) return
this.timer = setTimeout(() => {
this.typeList.map((item, index) => {
if (item.isActive) return;
if (event.scrollTop + 44 >= item.top) {
if (!item.isActive) {
item.isActive = true;
this.$refs.tabs.clickType(item, index, true);
item.isActive = false;
}
}
})
this.timer = null;
}, 50)
},
methods: {
clickType(item) {
this.isScroll = true;
var _this = this;
uni.createSelectorQuery()
.select(".container") //对应外层节点
.boundingClientRect((container) => {
uni.createSelectorQuery()
.select("#" + item.type) //目标节点
.boundingClientRect((target) => {
uni.pageScrollTo({
duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错
scrollTop: target.top - container.top -
44, //滚动到实际距离是元素距离顶部的距离减去最外层盒子的滚动距离
complete: ()=>{
setTimeout(() => {
_this.isScroll = false;
},100)
}
});
})
.exec();
})
.exec();
}
}
}
</script>
<style lang="scss" scoped>
.content-box {
padding: 0 40rpx;
.title-box {
padding: 28rpx 0;
}
.line {
display: flex;
align-items: center;
justify-content: space-between;
text-align: right;
padding: 28rpx 0;
border-top: 2rpx solid #F7F7FE;
.head-image {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
}
.label-box {
min-width: 160rpx;
color: #556172;
text-align: left;
}
.border-left-2 {
border-left: 2rpx solid #F7F7FE;
}
}
.operation-box {
width: 176rpx;
height: 176rpx;
background-color: #F6F7FA;
border-radius: 8rpx;
margin-right: 40rpx;
margin-bottom: 40rpx;
position: relative;
overflow: hidden;
image.icon {
width: 52rpx;
}
image.close {
width: 32rpx;
position: absolute;
right: 0;
top: 0;
}
image.img-box {
max-width: 100%;
}
}
}
.placeholder.server {
font-size: 24rpx;
}
textarea {
height: 440rpx;
}
</style>
代码中出现的样式,如果出现效果异常,可根据class类名意思添加对应样式
<style>
.scroll-view{
white-space: nowrap;
}
.font-weight-bold{
font-weight: bold;
}
.nowarp {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.font-36 {
font-size: 36rpx;
}
.d-flex {
display: flex;
}
.d-inline-flex {
display: inline-flex;
}
.border-bottom-2 {
border-bottom: 2rpx solid $bg-color;
}
.border-top-24 {
border-top: 24rpx solid $bg-color;
}
.mt-16 {
margin-top: 16rpx;
}
.mr-16{
margin-right: 16rpx;
}
.mr-32{
margin-right: 32rpx;
}
.pl-40 {
padding-left: 40rpx;
}
</style>
更多推荐
已为社区贡献1条内容
所有评论(0)