返回

解锁 Tab 滚动居中奥秘:深入原理,代码实战

前端

Tab 滚动居中:让你的移动端应用更交互更美观

在移动端应用中,Tab 滚动居中是一种让用户体验更加流畅和愉悦的交互效果。通过这种交互,用户可以轻松地左右滑动 Tab 栏,让选中的 Tab 始终位于屏幕中央,既美观又实用。

Tab 滚动的原理

Tab 滚动居中的原理很简单:通过调整 Tab 栏容器的 scrollLeft 值来实现。scrollLeft 表示容器可视区域的左侧边界距离容器内容左侧边界的距离。当我们向左滑动 Tab 栏时,scrollLeft 值会增加,从而让右边的 Tab 进入可视区域;反之,向右滑动 Tab 栏时,scrollLeft 值会减小,从而让左边的 Tab 进入可视区域。

滚动距离的计算

为了让选中的 Tab 始终处于屏幕中央,我们需要计算出 Tab 滚动距离 scrollDistance,即 Tab 栏容器可视区域的中心点到选中 Tab 中心点的距离。scrollDistance 的计算公式为:

scrollDistance = (containerWidth - tabWidth) / 2 - selectedTabIndex * tabWidth

其中:

  • containerWidth:Tab 栏容器的宽度
  • tabWidth:单个 Tab 的宽度
  • selectedTabIndex:选中 Tab 的索引

代码实现

掌握了 Tab 滚动居中的原理后,我们就可以在代码中轻松实现这种效果了。以下是用 Vue、React 和微信小程序实现 Tab 滚动居中的代码示例:

Vue

<template>
  <div class="tab-container">
    <ul class="tab-list">
      <li v-for="tab in tabs" @click="selectTab(tab.index)" :class="{ 'tab-item': true, 'tab-item--active': tab.index === selectedTabIndex }">{{ tab.label }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tabs: [
        { label: 'Tab 1', index: 0 },
        { label: 'Tab 2', index: 1 },
        { label: 'Tab 3', index: 2 },
      ],
      selectedTabIndex: 0,
    };
  },
  methods: {
    selectTab(index) {
      this.selectedTabIndex = index;

      // 计算滚动距离
      const containerWidth = this.$refs.tabContainer.offsetWidth;
      const tabWidth = this.$refs.tabList.children[0].offsetWidth;
      const scrollDistance = (containerWidth - tabWidth) / 2 - index * tabWidth;

      // 设置容器的 scrollLeft 值
      this.$refs.tabContainer.scrollLeft = scrollDistance;
    },
  },
};
</script>

<style>
.tab-container {
  width: 100%;
  overflow-x: scroll;
  -webkit-overflow-scrolling: touch;
}

.tab-list {
  display: flex;
  list-style-type: none;
  padding: 0;
}

.tab-item {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100px;
  height: 50px;
  margin-right: 10px;
  background-color: #fff;
  cursor: pointer;
}

.tab-item--active {
  background-color: #000;
  color: #fff;
}
</style>

React

import React, { useState, useRef } from 'react';

const Tab = (props) => {
  const { label, index, selectedTabIndex, selectTab } = props;

  return (
    <li className="tab-item" onClick={() => selectTab(index)} style={{ width: 100, height: 50, marginRight: 10, backgroundColor: selectedTabIndex === index ? '#000' : '#fff', color: selectedTabIndex === index ? '#fff' : '#000' }}>
      {label}
    </li>
  );
};

const TabContainer = () => {
  const [selectedTabIndex, setSelectedTabIndex] = useState(0);
  const tabContainerRef = useRef(null);

  const selectTab = (index) => {
    setSelectedTabIndex(index);

    // 计算滚动距离
    const containerWidth = tabContainerRef.current.offsetWidth;
    const tabWidth = tabContainerRef.current.children[0].offsetWidth;
    const scrollDistance = (containerWidth - tabWidth) / 2 - index * tabWidth;

    // 设置容器的 scrollLeft 值
    tabContainerRef.current.scrollLeft = scrollDistance;
  };

  return (
    <div className="tab-container" ref={tabContainerRef} style={{ width: '100%', overflowX: 'scroll', WebkitOverflowScrolling: 'touch' }}>
      <ul className="tab-list">
        {['Tab 1', 'Tab 2', 'Tab 3'].map((label, index) => <Tab key={index} label={label} index={index} selectedTabIndex={selectedTabIndex} selectTab={selectTab} />)}
      </ul>
    </div>
  );
};

export default TabContainer;

微信小程序

Page({
  data: {
    tabs: [
      { label: 'Tab 1', index: 0 },
      { label: 'Tab 2', index: 1 },
      { label: 'Tab 3', index: 2 },
    ],
    selectedTabIndex: 0,
  },
  methods: {
    selectTab(e) {
      const index = e.currentTarget.dataset.index;
      this.setData({
        selectedTabIndex: index,
      });

      // 计算滚动距离
      const containerWidth = this.data.windowWidth;
      const tabWidth = this.data.tabWidth;
      const scrollDistance = (containerWidth - tabWidth) / 2 - index * tabWidth;

      // 设置容器的 scrollLeft 值
      this.$nextTick(() => {
        this.createSelectorQuery().select('.tab-container').scrollOffset().exec((res) => {
          wx.pageScrollTo({
            scrollTop: 0,
            scrollLeft: scrollDistance,
          });
        });
      });
    },
  },
  onLoad() {
    const that = this;
    wx.getSystemInfo({
      success(res) {
        that.setData({
          windowWidth: res.windowWidth,
        });

        // 计算 Tab 宽度
        const query = wx.createSelectorQuery();
        query.select('.tab-item').boundingClientRect((res) => {
          that.setData({
            tabWidth: res.width,
          });
        }).exec();
      },
    });
  },
});

结语

掌握了 Tab 滚动居中的原理和代码实现后,你就可以在自己的项目中轻松应用这种交互效果了。通过调整 scrollLeft 值,你可以实现多种不同的效果,例如让选中的 Tab 始终位于容器左侧或右侧,或者让多个 Tab 同时滚动居中。希望本文能帮助你更好地理解 Tab 滚动居中的原理和应用技巧,助力你打造更具交互性和美观性的移动端应用。

常见问题解答

1. 如何让选中的 Tab 始终位于容器的右侧?

scrollDistance = containerWidth - tabWidth * (selectedTabIndex + 1);

2. 如何让多个 Tab 同时滚动居中?

可以将多个 Tab 的 scrollLeft 值设置为相同的距离,例如:

scrollDistance = (containerWidth - tabWidth * 3) / 2;

3. 如何在不同设备上保持 Tab 滚动居中的效果?

可以通过监听 resize 事件并重新计算 scrollDistance 值来实现,例如:

window.addEventListener('resize', () => {
  // 重新计算 scrollDistance 值
});

4. 如何让 Tab 滚动居中的效果更加流畅?

可以使用 requestAnimationFrame 函数来实现更流畅的滚动效果,例如:

requestAnimationFrame(() => {
  container.scrollLeft = scrollDistance;
});

5. 如何在 Tab 滚动居中的同时实现其他交互效果?

可以将 Tab 滚动居中的交互与其他交互效果结合起来,例如:

  • 在 Tab 滚动居中的基础上添加动画效果,让 Tab 切换更加流畅。
  • 在 Tab 滚动居中的基础上添加手势控制,让用户可以通过手势滑动 Tab 栏来切换 Tab。