用el-scrollbar写一个可横向滚动的vue组件

组件展示效果图

在这里插入图片描述
该页面是一个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
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值