坏了,CSS真被他们玩出花来了

26,116 阅读3分钟

前言

事情是这样子的,本人由于摸鱼过多被临时抽调去支援公司的一个官网开发,其中有个任务是改造侧边栏导航,我心想着很简单嘛,两下搞完继续我的摸鱼大业😁.然后ui就丢给了一个网站让我照着这个做(龟龟现成的都有,这也太好了),网站在这里,让我们来看一下简单的交互.

iShot_2023-10-30_11.07.56.gif

看了一下大致就是滚动到特定位置后把导航栏固定,hover的时候显示导航列表,这不是小菜一碟.

滚动到特定位置固定导航

主要逻辑就是下面这段,简单来说就是监听滚动条位置,当滚动到特定高度时改变导航按钮的定位方式, css部分是用的@emotion/styled,文档可以看一下这里.

const [fixed, setFixed] = useState(false);
  useEffect(() => {
    const root = document.querySelector('#root');
    if (!root) return;
    const fn = () => {
      if (root.scrollTop > 451) {
        setFixed(true);
      } else {
        setFixed(false);
      }
    };
    root.addEventListener('scroll', fn);
    return () => {
      root.removeEventListener('scroll', fn);
    };
  }, []);
const StyledFixed = styled.div<{ fixed?: boolean }>`
  width: 0;
  position: ${(props) => (props.fixed ? 'fixed' : 'absolute')};
  top: ${(props) => (props.fixed ? '80px' : '531px')};
  left: 80px;
  z-index: 9;
  @media (max-width: 1800px) {
    left: 12px;
  }
`;

效果如下

iShot_2023-10-30_11.24.58.gif

hover显示导航列表

然后就是鼠标移入的时候显示导航列表了,我心想这还不简单,几行css就搞定了(简单描述下就是把导航列表放在按钮里面,给按钮加上hover效果),但是当我研究了一下腾讯网站的代码,我发现事情好像没那么简单.

image.png

导航按钮和列表是平级的,这样的话鼠标移上去列表显示,但列表显示的同时hover效果也没有了,列表就又隐藏了,就会导致闪烁的效果,gif图展示不够明显.

iShot_2023-10-30_11.49.33.gif

现在问题来了,如果是平级元素,那如何控制hover显示呢,答案就在他们的父元素身上,从下面2张图上可以看出,导航按钮以及他的父元素都加上了hover样式

image.png

image.png

默认情况下父元素宽度为0,防止误触发列表展示,hover状态下设置width:auto image.png

实现效果

到这里,我已经完全清楚了实现原理,完整的代码在下面

const LeftNav = observer(() => {
  const { selectedKeys, openKeys } = menuStore;
  const [fixed, setFixed] = useState(false);
  useEffect(() => {
    const root = document.querySelector('#root');
    if (!root) return;
    const fn = () => {
      if (root.scrollTop > 451) {
        setFixed(true);
      } else {
        setFixed(false);
      }
    };
    root.addEventListener('scroll', fn);
    return () => {
      root.removeEventListener('scroll', fn);
    };
  }, []);
  const onMenuClick = ({ key }: { key: string }) => {
    menuStore.selectedKeys = [key];
    if (key) {
      const dom = document.querySelector(`#${key}`);
      menuStore.scrollingKey = key;
      dom?.scrollIntoView({
        behavior: 'smooth'
      });
    }
  };

  const onOpenChange = (keys: string[]) => {
    menuStore.openKeys = keys.filter((i) => i !== openKeys[0]);
  };

  return (
    <StyledFixed fixed={fixed}>
      <div className='left-nav-btn'>
        <RightOutlinedIcon />
      </div>
      <div className='left-nav-list'>
        <StyledMenuWrap>
          <Menu selectedKeys={selectedKeys} openKeys={openKeys} mode='inline' items={MENU} onClick={onMenuClick} onOpenChange={onOpenChange} />
        </StyledMenuWrap>
      </div>
    </StyledFixed>
  );
});

const StyledFixed = styled.div<{ fixed?: boolean }>`
  width: 0;
  position: ${(props) => (props.fixed ? 'fixed' : 'absolute')};
  top: ${(props) => (props.fixed ? '80px' : '531px')};
  left: 80px;
  z-index: 9;
  &:hover {
    width: auto;
    .left-nav-btn {
      display: none;
    }
    .left-nav-list {
      transform: none;
      visibility: visible;
    }
  }
  @media (max-width: 1800px) {
    left: 12px;
  }
  .left-nav-btn {
    position: absolute;
    top: 80px;
    width: 40px;
    font-size: 14px;
    font-style: normal;
    font-weight: 500;
    line-height: 20px;
    padding: 12px;
    border-radius: 100px;
    border: 1px solid #fff;
    background: rgba(255, 255, 255, 0.8);
    box-shadow: 0px 4px 30px 0px rgba(12, 25, 68, 0.05);

    cursor: pointer;

    &::before {
      content: '页面导航';
      background: linear-gradient(139deg, #c468ef 5.3%, #2670ff 90.91%);
      background-clip: text;
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    &:hover {
      display: none;
      & + .left-nav-list {
        transform: none;
        visibility: visible;
      }
    }
    @media (min-width: 1799px) {
      display: none;
    }
  }
  .left-nav-list {
    position: relative;
    width: 172px;
    z-index: 1;
    transition: all 0.3s ease-in;
    @media (max-width: 1800px) {
      transform: translateX(-200px);
      visibility: hidden;
    }
  }
`;

最终的实现效果如下 iShot_2023-10-30_14.39.20.gif

至于腾讯网站中的滚动到对应模块高亮菜单的实现可以看看 IntersectionObserver 这个api,好了本次的分享就到此为止了,感谢各位大佬的阅读与点赞😁,你可以说我菜,因为我是真的菜.