You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tailwind移动端字母导航栏滚动异常:无法显示并滚动至A

问题:移动端字母导航菜单默认从J开始,无法滚动到A

我开发了一个基于React的Next.js应用,采用Tailwind CSS构建。页面组件展示按瑞典字母表A到Ö排序的人员列表,移动端交互要求字母列表单行显示,支持箭头或手指滑动滚动。

问题现象:在移动端视图下,字母列表默认从字母J开始显示而非A,推测是内容溢出屏幕导致,用户无法滚动到字母A。

需求:让字母列表可完整滚动至起始字母A,或直接默认从A开始显示。


解决方案

1. 强制初始滚动到最左侧

在组件挂载时,将滚动容器的scrollLeft设为0,确保默认从第一个字母(A)开始显示。

2. 修复容器对齐方式

将字母列表容器的justify-center改为justify-start,避免内容溢出时默认居中显示,让列表从左侧起始位置排列。

3. 优化状态更新时机

在activeLetters变化时重新检测滚动状态并重置位置,避免过滤后的字母列表变化导致的位置异常。


修改后的LetterMenu组件代码

'use client';

import Link from 'next/link';
import React, { useEffect, useRef, useState } from 'react';
import { cn } from '@/utils/classnames';
import NavigateIcon from './icons/NavigateIcon';

const Letters = [
  'a',
  'b',
  'c',
  'd',
  'e',
  'f',
  'g',
  'h',
  'i',
  'j',
  'k',
  'l',
  'm',
  'n',
  'o',
  'p',
  'q',
  'r',
  's',
  't',
  'u',
  'v',
  'w',
  'x',
  'y',
  'z',
  'å',
  'ä',
  'ö',
];

type Props = {
  currentElementIndexInViewport: number;
  activeLetters: string[];
};

const LetterMenu: React.FC<Props> = ({ currentElementIndexInViewport, activeLetters }) => {
  const carousel = useRef<HTMLUListElement>(null);
  const letterMenuRef = useRef<HTMLDivElement>(null);
  const [isSticky, setIsSticky] = useState(false);
  const [isAtStart, setIsAtStart] = useState(true);
  const [isAtEnd, setIsAtEnd] = useState(false);

  const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
    event.preventDefault();
    const targetId = event.currentTarget.getAttribute('href')?.substring(1);
    const targetElement = targetId ? document.getElementById(targetId) : null;

    if (targetElement) {
      targetElement.scrollIntoView({
        behavior: 'smooth',
      });
    }
  };

  const updateButtonVisibility = () => {
    if (carousel.current) {
      const isStart = carousel.current.scrollLeft === 0;
      const isEnd =
        carousel.current.scrollLeft + carousel.current.offsetWidth === carousel.current.scrollWidth;
      setIsAtStart(isStart);
      setIsAtEnd(isEnd);
    }
  };

  const handleClickNext = () => {
    carousel.current?.scrollBy(300, 0);
    updateButtonVisibility();
  };

  const handleClickPrev = () => {
    carousel.current?.scrollBy(-300, 0);
    updateButtonVisibility();
  };

  useEffect(() => {
    // 强制滚动到最左侧,默认显示A
    if (carousel.current) {
      carousel.current.scrollLeft = 0;
    }
    updateButtonVisibility();
    
    const handleScroll = () => {
      if (letterMenuRef.current) {
        setIsSticky(window.scrollY >= letterMenuRef.current.offsetTop);
      }
    };

    const handleCarouselScroll = () => {
      updateButtonVisibility();
    };

    window.addEventListener('scroll', handleScroll);
    carousel.current?.addEventListener('scroll', handleCarouselScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
      carousel.current?.removeEventListener('scroll', handleCarouselScroll);
    };
  }, [activeLetters]); // 当activeLetters变化时重新执行

  return (
    <div
      ref={letterMenuRef}
      className={cn('sticky top-0 z-10 w-screen bg-white', {
        'shadow-custom': isSticky,
      })}
    >
      {!isAtStart && (
        <button
          className="prev space-between absolute left-0 top-1/2 -translate-y-1/2 cursor-pointer content-center rounded-full px-2"
          onClick={handleClickPrev}
          aria-label="vänster"
        >
          <div className="inline-flex items-center justify-center rounded-full border border-transparent bg-black bg-opacity-20 px-4 shadow-lg">
            <NavigateIcon direction="left" variant="secondary" />
          </div>
        </button>
      )}
      {/* Vi har här valt att ha skräddarsydd inline-styling i CSS då det endast gäller denna komponenten */}
      <ul
        ref={carousel}
        className="carousel flex justify-start gap-[0.624rem] overflow-x-auto scroll-smooth py-4"
      >
        {Letters.filter((letter) => activeLetters.includes(letter)).map((letter, letterIndex) => (
          <li key={`nav_${letter}`}>
            <Link
              href={`#${letter}`}
              onClick={handleClick}
              className={`focus:ring-blue-500 rounded-full font-livvic text-xl font-bold uppercase underline focus:outline-none focus:ring-2 focus:ring-greenblue-primary sm:p-4 ${
                activeLetters[currentElementIndexInViewport] === letter
                  ? 'active bg-greenblue-1.45 p-2 no-underline'
                  : ''
              }`}
              style={{
                width: '2.1rem',
                height: '2.1rem',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
              }}
              tabIndex={0}
            >
              {letter}
            </Link>
          </li>
        ))}
      </ul>

      {!isAtEnd && (
        <button
          className="next absolute right-0 top-1/2 -translate-y-1/2 cursor-pointer content-center justify-center rounded-full px-4 shadow"
          onClick={handleClickNext}
          aria-label="höger"
        >
          <NavigateIcon direction="right" variant="secondary" />
        </button>
      )}
    </div>
  );
};

export default LetterMenu;

内容的提问来源于stack exchange,提问作者Christer Johansson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 19:45:25