Auto Focus - 自动聚焦

一个基于滚动视图的自动聚焦Hook,支持横向和纵向滚动,可以实现列表项的自动定位和居中展示。

预览

使用示例

import useAutoFoucsScroll from '@/hooks/useAutoFoucsScroll';
import { cn } from '@/utils';
import { ScrollView, View } from '@tarojs/components';
import { getWindowInfo } from '@tarojs/taro';
import { useState } from 'react';

const data = new Array(20).fill(0).map((_, index) => `${index}`);

const { windowWidth } = getWindowInfo();

export default function Page() {
  const containerWidth = windowWidth * 0.8;

  const { id, scrollPosition, itemSize, handleChangeIndex } = useAutoFoucsScroll({
    id: 'scroll-view',
    itemNum: 5,
    containerSize: containerWidth,
    scrollType: 'x',
    // backspace: 1,
  });

  const [currentIndex, setCurrentIndex] = useState(0);

  const handleClickItem = (index: number) => {
    setCurrentIndex(index);
    handleChangeIndex(index);
  };

  return (
    <View className="container">
      <View className="rounded bg-white p-1">
        <ScrollView
          showScrollbar={false}
          enhanced
          className="whitespace-nowrap"
          scrollX
          id={id}
          scrollWithAnimation
          scrollLeft={scrollPosition}
          style={{ width: containerWidth }}
        >
          {data.map((item, index) => (
            <View
              className="inline-flex items-center justify-center p-1"
              style={{ width: itemSize, height: itemSize }}
              key={item}
            >
              <View
                onClick={() => handleClickItem(index)}
                key={item}
                className={cn(
                  'inline-flex size-full items-center justify-center bg-green-300 text-white',
                  currentIndex === index && 'bg-red-300'
                )}
              >
                {item}
              </View>
            </View>
          ))}
        </ScrollView>
      </View>
    </View>
  );
}

下载

1

添加依赖

npm i clsx tailwind-merge
2

添加工具函数

// @/utils
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

// 合并 Tailwind CSS 类名和 clsx 类名
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// 根据屏幕宽度计算响应式值
export const pt = (num: number) => {
  const { screenWidth } = getWindowInfo();
  return num * (screenWidth / 375);
};

源代码

import { createSelectorQuery, nextTick, useReady } from '@tarojs/taro';
import { useState } from 'react';

interface UseAutoFoucsScrollProps {
  id: string;
  // 滚动方向
  scrollType?: 'x' | 'y';
  // 回退的距离
  backspace?: number;
  // 容器的大小
  containerSize: number;
  // 子项的数量
  itemNum: number;
}

const useAutoFoucsScroll = (props: UseAutoFoucsScrollProps) => {
  const { id, scrollType = 'x', backspace = 0, containerSize, itemNum } = props;

  const itemSize = containerSize / itemNum;

  const [scrollViewSize, setScrollViewSize] = useState(0);

  const [scrollPosition, setScrollPosition] = useState(0);

  const getScrollViewRect = () => {
    const query = createSelectorQuery().select(`#${id}`);

    nextTick(() => {
      query
        .boundingClientRect(res => {
          const width = Array.isArray(res) ? res[0].width : res.width;
          const height = Array.isArray(res) ? res[0].height : res.height;

          setScrollViewSize(scrollType === 'x' ? width : height);
        })
        .exec();
    });
  };

  const getItemCenterPosition = (index: number) => {
    return index * itemSize - scrollViewSize / 2 + itemSize / 2 + backspace * itemSize;
  };

  const handleChangeIndex = (index: number) => {
    if (scrollViewSize) {
      setScrollPosition(getItemCenterPosition(index));
    }
  };

  useReady(() => {
    getScrollViewRect();
  });

  return { id, scrollPosition, itemSize, getScrollViewRect, handleChangeIndex };
};