用el-scrollbar写一个可横向滚动的vue组件
组件 el-scrollbar组件 scrollWidth scrollLeft clientWidth 点击一次按钮更改scrollLeft或scrollRight elementui
·
组件展示效果图
该页面是一个step组件内嵌一个右下角的按钮组件
关键代码
首先,el-scrollbar组件包住一个被内容撑开的横向列
<el-scrollbar ref="scrollbar" class="scrollbar" v-if="list.length !== 0">
<div class="step">
<div class="event-item" v-for="item in list" :key="item.id">
<div class="item-card" @click="detail(item.id)">
......
</div>
</div>
</div>
</el-scrollbar>
获取el-scrollbar的scrollWidth(元素实际宽度,包括超出隐藏的部分),减去el-scrollbar的scrollLeft(元素距离左边窗口的距离),再减去el-scrollbar的clientWidth(元素包括padding、内容区的宽度,不含边框),得到可以向右移动的最边界位置。点击一次按钮,给scrollLeft或scrollRight赋一次值以更改位置。
// 滚动
handelScroll(action) {
var scrollbar = this.$refs.scrollbar.wrap;
// 点击按钮移动一次的步长
var width = 500;
// 向右可以移动的距离
let scrollRight =
scrollbar.scrollWidth -
scrollbar.scrollLeft -
scrollbar.clientWidth;
if (action == "left") {
if (scrollbar.scrollLeft > width) { // 判断滚动到最右时
scrollbar.scrollLeft -= width;
} else {
scrollbar.scrollLeft -= scrollbar.scrollLeft;
}
} else {
if (scrollRight > width) { // 判断滚动到最右时
scrollbar.scrollLeft += width;
} else {
scrollbar.scrollLeft += scrollRight;
}
}
}
is-horizontal隐藏el-scrollbar自身的滚动条
::v-deep .scrollbar {
.is-horizontal {
height: 0;
left: 0;
}
}
横向排列宽度由内容撑开需要父元素加入white-space:nowrap,子元素加入display: inline-block;
.step {
height: 540px;
white-space: nowrap;
padding-top: 50px;
.event-item {
width: 320px;
height: 100%;
display: inline-block;
}
}
step组件代码
<template>
<div class="base-step">
<div class="loading" v-if="subLoading">
<div class="loading-tip">
<i class="el-icon-loading"></i>
<div class="tip">
加载中
</div>
</div>
</div>
<el-scrollbar ref="scrollbar" class="scrollbar" v-if="list.length !== 0">
<div class="step">
<div class="event-item" v-for="item in list" :key="item.id">
<div class="item-card" @click="detail(item.id)">
<img :src="item.image ? item.image.split(',')[0] : require('@/assets/images/home/lack.png')" class="item-img" />
<div class="item-content">
<div class="info-box">
<div class="info-title">{{ item.name }}</div>
<div class="star-box">
<img v-for="star in item.star" :key="star" class="star" src="@/assets/images/heaven/icon_grade.png" />
</div>
<div class="info-item-box">
<div class="info-item">
<img class="info-item-icon" src="@/assets/images/heaven/2_icon_date.png" />
<div class="info-item-value">
{{ item.startTime && item.endTime ? item.startTime + '-' + item.endTime : "暂无信息" }}
</div>
</div>
</div>
<div class="info-item-box">
<div class="info-item">
<img class="info-item-icon" src="@/assets/images/heaven/2_icon_iphone.png" />
<div class="info-item-value">
{{ item.phone ? item.phone : "暂无信息" }}
</div>
</div>
</div>
<div class="info-item-box">
<div class="info-item">
<img class="info-item-icon" src="@/assets/images/heaven/2_icon_map.png" />
<div class="info-item-value address">
{{ item.adress ? item.adress : "暂无信息" }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
<div class="pagination-scroll" v-if="list.length !== 0">
<base-pagination direction="horizontal" type="scroll" @pageChange="handelScroll"></base-pagination>
</div>
<!-- 无数据 -->
<div class="empty" v-if="list.length === 0 && !subLoading">
<div class="block">
<template v-if="onLine">
<img class="image" src="@/assets/images/home/no_data.png" alt="">
<div class="tip">
{{ emptyTip }}
</div>
</template>
<template v-if="!onLine">
<img class="image" src="@/assets/images/home/no_net.png" alt="">
<div class="tip">
网络未连接,请检查网络配置
</div>
</template>
<div class="reload" @click="reLoadClick">
重新加载
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "baseStep",
data() {
return {
onLine: navigator.onLine,
subLoading: true
};
},
watch: {
loading(val) {
this.subLoading = val;
}
},
props: {
list: {
type: Array,
default: () => {
return []
}
},
detailRoute: {
type: String,
default: () => {
return ''
}
},
emptyTip: {
type: String,
default: '暂无数据'
},
loading: {
type: Boolean,
default: true
},
// 重载函数
reLoad: {
type: Function,
default: () => {}
}
},
mounted() {
window.addEventListener('online', this.updateOnlineStatus);// 网络由异常到正常时触发
window.addEventListener('offline', this.updateOnlineStatus);// 网络由正常常到异常时触发
},
methods: {
detail(id) {
if (this.detailRoute) {
this.$router.push(this.detailRoute + '?id=' + id)
}
},
// 滚动
handelScroll(action) {
var scrollbar = this.$refs.scrollbar.wrap;
var width = 500;
let scrollRight =
scrollbar.scrollWidth -
scrollbar.scrollLeft -
scrollbar.clientWidth;
if (action == "left") {
if (scrollbar.scrollLeft > width) { // 判断滚动到最右时
scrollbar.scrollLeft -= width;
} else {
scrollbar.scrollLeft -= scrollbar.scrollLeft;
}
} else {
if (scrollRight > width) { // 判断滚动到最右时
scrollbar.scrollLeft += width;
} else {
scrollbar.scrollLeft += scrollRight;
}
}
},
reLoadClick() {
this.reLoad();
},
updateOnlineStatus(e) {
const { type } = e;
this.onLine = type === 'online';
},
beforeDestroy(){
window.removeEventListener('online', this.updateOnlineStatus);
window.removeEventListener('offline', this.updateOnlineStatus);
}
},
};
</script>
<style lang="scss" scoped>
.base-step {
width: 100%;
height: 90%;
position: relative;
.loading {
width: 100%;
height: 100%;
display: flex;
align-items: center;
.loading-tip {
width: 100%;
text-align: center;
font-size: 24px;
color: #333;
.tip {
margin-top: 10px;
}
}
}
.step {
height: 540px;
white-space: nowrap;
padding-top: 50px;
.event-item {
width: 320px;
height: 100%;
display: inline-block;
&:nth-last-child(1) .line {
opacity: 0;
}
.item-title {
height: 10%;
line-height: 100%;
font-size: 24px;
margin-bottom: 20px;
}
.item-step {
width: 100%;
display: flex;
margin-bottom: 10%;
.dot {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #ff694c;
}
.line {
flex: 1;
height: 10px;
border-bottom: 2px solid #e8e8e8;
}
}
.item-card {
width: 90%;
height: 100%;
border-radius: 6px;
overflow: hidden;
background-color: #ffffff;
.item-img {
width: 100%;
height: 55%;
object-fit: cover;
}
.item-content {
margin: 10px;
height: 40%;
.info-box {
.info-title {
font-size: 22px;
-webkit-line-clamp: 1;
text-overflow: ellipsis;
overflow: hidden;
}
.star-box {
width: 100%;
height: 20px;
margin: 10px 0;
.star {
width: 20px;
height: 100%;
margin-right: 3px;
}
}
.info-item-box {
width: 100%;
padding: 3px 0;
display: flex;
.info-item {
padding-bottom: 15px;
box-sizing: border-box;
display: flex;
width: 100%;
flex-wrap: nowrap;
height: 100%;
padding: 0;
&:nth-child(even) {
padding-left: 20px;
}
.info-item-icon {
width: 30px;
height: 30px;
}
.info-item-value {
flex: 1;
padding-left: 10px;
box-sizing: border-box;
overflow: hidden;
white-space: normal;
text-overflow: ellipsis;
width: 100%;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
word-break: break-all;
line-height: 30px;
}
.address {
-webkit-line-clamp: 2;
}
}
}
}
}
}
}
}
.pagination-scroll {
position: absolute;
bottom: 0;
left: 0;
height: 100px;
width: 100%;
display: flex;
align-items: center;
}
.scrollbar {
height: 100%;
width: 100%;
}
::v-deep .scrollbar {
.is-horizontal {
height: 0;
left: 0;
}
}
.empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
.block {
width: 100%;
text-align: center;
.image {
width: 200px;
object-fit: contain;
}
.tip {
color: #333;
font-size: 24px;
margin-bottom: 30px;
}
.reload {
display: inline-block;
font-size: 23px;
color: #FF6B6B;
border: 2px solid #FF6B6B;
border-radius: 30px;
padding: 10px 20px;
}
}
}
}
</style>
按钮组件代码
<template>
<div class="base-pagination">
<!-- 垂直 -->
<div class="vertical" v-if="direction == 'vertical'">
<div @click="prevPage">
<img class="image" src="../../assets/images/home/btn_up.png" alt="" />
</div>
<div class="pager" v-if="type == 'pagination'">
{{ currPage }} / {{ totalPages }}
</div>
<div @click="nextPage">
<img class="image" src="../../assets/images/home/btn_down.png" alt="" />
</div>
</div>
<!-- 水平 -->
<div class="horizontal" v-else>
<div @click="prevPage">
<img class="image" src="../../assets/images/home/btn_left.png" alt="" />
</div>
<div class="pager" v-if="type == 'pagination'">
{{ currPage }} / {{ totalPages }}
</div>
<div @click="nextPage">
<img
class="image"
src="../../assets/images/home/btn_right.png"
alt=""
/>
</div>
</div>
</div>
</template>
这个按钮组件预留了翻页、纵向的功能
<script>
export default {
name: "basePagination",
data() {
return {
currPage: 0,
totalPages: 0, // 总页数
};
},
props: {
// 方向 垂直vertical | 水平horizontal
direction: {
type: String,
default: "vertical",
},
pager: {
type: Object,
default: () => {
return {
currPage: 0,
totalPages: 0,
};
},
},
// 类型 pagination 分页 | scroll 滚动
type: {
type: String,
default: "pagination",
},
},
mounted() {
if (this.type == "pagination") {
this.getPager();
}
},
methods: {
getPager() {
this.$nextTick(() => {
this.currPage = parseInt(this.pager.currPage);
this.totalPages = parseInt(this.pager.totalPages);
});
},
// 上一页
prevPage() {
if (this.type == "pagination") {
if (this.currPage > 1) {
this.currPage--;
this.$emit("pageChange", this.currPage);
}
} else if (this.type == "scroll") {
if (this.direction === "vertical") {
this.$emit("pageChange", "up");
} else if (this.direction === "horizontal") {
this.$emit("pageChange", "left");
}
}
},
// 下一页
nextPage() {
if (this.type == "pagination") {
if (this.currPage < this.totalPages) {
this.currPage++;
this.$emit("pageChange", this.currPage);
}
} else if (this.type == "scroll") {
if (this.direction === "vertical") {
this.$emit("pageChange", "down");
} else if (this.direction === "horizontal") {
this.$emit("pageChange", "right");
}
}
},
},
};
</script>
<style lang="scss" scoped>
.base-pagination {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
.vertical,
.horizontal {
text-align: center;
.image {
width: 80%;
object-fit: contain;
}
.pager {
font-size: 18px;
color: #666;
padding: 10px 0;
}
}
.horizontal {
height: 100%;
display: flex;
.image {
height: 100%;
object-fit: contain;
}
.pager {
font-size: 18px;
color: #666;
padding: 10px 0;
}
}
}
</style>
更多推荐
已为社区贡献1条内容
所有评论(0)