手动实现长tab横向滑动的两种方案对比

478 阅读3分钟

1.背景

  • 背景:在B-data的二期人群资产中,有一个交互模式是横向排列的几张卡片,最多为5张。当超过容器的展示区域时,需要展示滑动的icon,可支持左右滑动。如果容器可以全部展示卡片,则不展示滑动icon。如下图所示

  1.全部展示时,不展示icon   2.tab区域超过容器时展示icon

  • 技术定位:初级

  • 目标群体:低级前端开发工程师(本人)

  • 技术应用场景:前端实现手动滑动的场景

  • 整体思路:本文将通过总结两种实现方案,从而分析各自的优缺点以及应用场景。总的来说,就是讨论实现手动滑动,tansform: translateX() 和 scrollLeft两种方法的优缺点。

2.方案一:translateX

2.1 方案描述

代码逻辑:通过判断容器的宽度和滑动区域的宽度来判断是否支持滑动,通过指定滑动区域的transform属性的translateX的值来实现滑动

下面流程图展示了具体的判断逻辑以及需要声明的常量和变量

1.获取container的宽度(指定值)containerWidth 2.获取scroller的宽度,通过ref(如果指定宽度,会失去自适应的特性)scrollerWidth 3.根据1和2计算常量1——最大可偏移值(非负数,最小为0)maxTranslateWidth = (scrollerWidth - containerWidth) < 0 ? 0 : (scrollerWidth - containerWidth) 4.声明变量1,scroller的横向偏移绝对值,初始值为0。scrollWidth = 0 3.声明变量1:左滑按钮是否显示,isShowLeftBtn = maxTranslateWidth > 0 && scrollWidth > 0 4.声明变量2:右滑按钮是否显示,isShowRightBtn = maxTranslateWidth > 0 && scrollWidth < maxTranslateWidth 5.实现滑动按钮点击事件 6.css添加滑动特效

暂时无法在飞书文档外展示此内容

参数列表:罗列代码中涉及的参数和含义

参数描述初始值
containerWidth容器的宽度指定值
scrollerWidth滑动区域的宽度指定值或者自适应值
maxTranslateWidth最大可滑动的距离,滑动区域的宽度减去容器宽度scrollerWidth - containerWidth0
scrollWidth滑动区域横向滑动的绝对值0
isShowLeftBtn是否展示左滑按钮false
isShowRightBtn是否展示右滑按钮false

2.2 实现过程

Html

<div class="assets-card-tabs" ref="containerElement">
  <div class="scroller" ref="scrollerElement" :style="{ transform: `translateX(-${scrollWidth}px)` }">
  // ...
  </div>
  <div
    v-if="isShowLeftBtn"
    @click.stop.prevent="onScroll(-240)"
    class="scroll-btn scroll-btn-left flex justify-center align-center"
  >
    <i class="biz-icon-arrow-left"></i>
  </div>
  <div
    v-if="isShowRightBtn"
    @click.stop.prevent="onScroll(240)"
    class="scroll-btn flex justify-center align-center"
  >
    <i class="biz-icon-arrow-right"></i>
  </div>
</div>

Ts

const containerElement = ref<HTMLElement | null>(null)
const scrollerElement = ref<HTMLElement | null>(null)
const containerWidth = ref<number>(0)
const scrollerWidth = ref<number>(0)
onMounted(() => {
  // 获取容器的宽度
  containerWidth.value = containerElement.value ? containerElement.value.clientWidth : 0
// 获取滑动区域的宽度
  scrollerWidth.value = scrollerElement.value ? scrollerElement.value.clientWidth : 0
})
// 计算最大可滑动的距离
const maxTranslateWidth = computed<number>(() => scrollerWidth.value - containerWidth.value < 0 ? 0 : scrollerWidth.value - containerWidth.value)
// 声明滑动变量
const scrollWidth = ref<number>(0)
// 声明是否展示左滑按钮变量。
const isShowLeftBtn = computed<boolean>(() => maxTranslateWidth.value > 0 && scrollWidth.value > 0)
// 声明是否展示右滑按钮变量
const isShowRightBtn = computed<boolean>(() => maxTranslateWidth.value > 0 && scrollWidth.value < maxTranslateWidth.value)
// 实现滑动方法
function onScroll(width: number) {
  width = width > maxTranslateWidth.value ? maxTranslateWidth.value : width
  scrollWidth.value = scrollWidth.value + width < 0 ? 0 : scrollWidth.value + width
}

Css

.assets-card-tabs {
  display: flex;
  width: calc(100vw - 280px);
  flex-wrap: nowrap;
  overflow-x: hidden;
  padding-right: 48px;
  //position: relative;
  .scroll-btn {
    z-index: 999;
    position: absolute;
    width: 48px;
    height: 48px;
    right: 0;
    top: 50%;
    transform: translateY(-24px);
    border-radius: 24px;
    background: #ffffff;
    font-size: 20px;
    font-weight: 700;
    box-shadow: 0px 0px 5px rgba(51, 51, 51, 0.05),
      0px 12px 28px rgba(51, 51, 51, 0.04);
    cursor: pointer;
  }
  .scroll-btn-left {
    left: 0;
  }
  .scroller {
    display: flex;
    flex-wrap: nowrap;
    // 添加滑动特效
    transition: all .3s;
  }
  &::-webkit-scrollbar {
    height: 0 !important;
  }
}

2.3 性能分析

该方案的核心点在于css方法实现滑动

transform: translateX(-${scrollWidth}px)

该方法不会使页面产生Layout重排,因此对于页面性能而言更好

2.4 总结

优点

  • 性能好
  • 有过渡动画

缺点

  • 代码量太多了

  • 不太好理解

3.方案二:scrollLeft

3.1 方案描述

代码逻辑:通过元素的scorllLeft来判断是否能够滑动,通过指定元素的scorllLeft来实现左右滑动

注意:只有可滑动的元素,指定起scrollLeft时才会生效,当scrollLeft不可滑动或者大于最大可滑动值的时候,scrollLeft都为0。因此,判断元素是否可滑动的方法为,获取该元素节点,为其scrollLeft赋值,再判断scrollLeft是否为0。判断完毕后scrollLeft再赋值为0.

暂时无法在飞书文档外展示此内容

参数列表:罗列代码中涉及的参数和含义

参数含义
scrollLeft元素向左滑动的距离

3.2 实现过程

HTML

<div class="assets-card-tabs" ref="containerElement">
  <div class="scroller" ref="scrollerElement">
  // ...
  </div>
  <div
    v-if="isShowLeftBtn"
    @click.stop.prevent="onScroll(-240)"
    class="scroll-btn scroll-btn-left flex justify-center align-center"
  >
    <i class="biz-icon-arrow-left"></i>
  </div>
  <div
    v-if="isShowRightBtn"
    @click.stop.prevent="onScroll(240)"
    class="scroll-btn flex justify-center align-center"
  >
    <i class="biz-icon-arrow-right"></i>
  </div>
</div>

JS

const assetsTabs = ref<HTMLElement | null>(null)
const canScroll = ref<boolean>(false)
const hasScroll = ref<boolean>(false)
onMounted(() => {
  if (assetsTabs.value) {
    assetsTabs.value.scrollLeft = 1
    if (assetsTabs.value.scrollLeft !== 0) {
      canScroll.value = true
    } else {
      canScroll.value = false
    }
    assetsTabs.value.scrollLeft = 0
  }
})
function onScroll(width: number) {
  if (!assetsTabs.value) return
  assetsTabs.value.scrollLeft += width
  if (assetsTabs.value?.scrollLeft) {
    hasScroll.value = true
  } else {
    hasScroll.value = false
  }
}

3.3 性能分析

每次获取scollLeft会导致页面的重排,对页面的流畅使用有影响。

3.4 总结

优点

  • 代码少
  • 逻辑容易理解

缺点

  • 会导致页面重排,性能不好
  • 没有过渡动画,页面使用体验不好

4.总结

是选择性能好的,还是代码少的,这是个问题。但是总的来说,第一种方法可以实现左右按钮的展示以及有过渡动画,还是第一种更好。