在 React 中用过渡覆盖导航栏

overlay navbar with transition in React

我在使用应该从顶部滑入的叠加导航栏时遇到问题。

不知道为什么,但如果我单击汉堡菜单,则会应用正确的 css class,但不会发生转换(它的作用类似于 show/hidden) . 但是,如果我从开发工具更改顶部 属性 值,它会按预期工作。

我错过了什么?

这是反应组件


import React, { useState } from "react";
import NavMenuIcon from "../../public/svg/nav.svg";
import styles from '../../styles/Navbar.module.scss'
import Link from 'next/link'

function Navbar() {
    const [menu, setToggle] = useState(false);
    const toggleMenu = () => setToggle(!menu);

    const Menu = props => (
        <div className={ props.toggle ? `${styles.menu__container} ${styles.toggle}` : `${styles.menu__container}` }>
          <div className={styles.menu__content}>
            <ul className={styles.menu__list}>
              <li className={styles.menu__item} onClick={toggleMenu}> <Link href="/"><span>Home</span></Link></li>
              <li className={styles.menu__item} onClick={toggleMenu}> <Link href="/services"><span>Services</span></Link></li>
              <li className={styles.menu__item} onClick={toggleMenu}> <Link href="/services#contact"><span>Contact</span></Link></li>
            </ul>
          </div>
        </div>
    );

    return (
      <div className={styles.navbar}>
        <i>Meuartelie</i>
        <NavMenuIcon className={styles.hamburger} onClick={toggleMenu} />
        <Menu toggle={menu}></Menu>
      </div>
    );
}

export default Navbar;

这是 css

@import './variables.scss';

.navbar {
  box-sizing: border-box;
  position: fixed;
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 5%;
  z-index: 5;
}

.menu__container {
  font-family: 'Jokerman Std';
  position: absolute;
  top: -300px;
  left: 0;
  width: 100vw;
  height: 300px;
  z-index: -1;
  background-image:  url("../public/png/welcome_mobile.png");
  background-color: black;
  background-size: cover;
  overflow: hidden;
  transition: 850ms;
}

.menu__content {
  font-size: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 20%;
}

.menu__container.toggle {
  top: 0;
  transition: all 1s ease-in;
}

.menu__list {
  width: 80%;
  text-align: center;
  list-style: none;
  padding: 0px;
  line-height: 2;
  margin: 0px;
  margin-bottom: 20px;
}

.menu__item {
  transition: padding-left .7s ease, color .7s ease;
  /* underline css*/
  text-decoration: none; 
  position: relative; 
}

.menu__item:hover {
  color: $base-color;
  padding-left: 20px;
  cursor: pointer;
}


.menu__item:after {
  content: '';
  height: 2px;
  /* adjust this to move up and down. you may have to adjust the line height of the paragraph if you move it down a lot. */
  bottom: 0px; 
  /* center - (optional) use with adjusting width   */
  margin: 0 auto;
  left: 0;
  right: 0;
  width: 40%;
  background: #fff;
  /* optional animation */
  -o-transition:.5s;
  -ms-transition:.5s;
  -moz-transition:.5s;
  -webkit-transition:.5s;
  transition: .5s;
}
     

/* optional hover classes used with anmiation */
.menu__item:hover:after {
  background: $base-color;
}

.hamburger {
  z-index: 1000;
  cursor: pointer;
}

这与 React 如何确定要重新渲染的内容有关。 每当 React 在渲染树中确定一个新引用时,它会自动重新渲染该元素。
CSS 另一方面,转换将从起始状态转换为结束状态。 如果开始和结束相同,则不应用过渡。

那么实际发生了什么:

  1. 按钮被点击。
  2. 渲染树已修改。具体来说 const Menu 被分配了一个新的引用。
  3. React 从 dom 中删除了所有旧元素,在本例中为 <div class="menu__container">
  4. React 将新元素添加到 dom,在本例中为 <div class="menu_container toggle">
  5. 浏览器呈现元素并应用初始 css 规则。该元素接收 top:0 作为初始状态。由于删除了旧元素,因此没有过渡。

如何修复:
确保 React 知道我们仍在渲染相同的元素,因此它将更新现有的 dom 元素,而不是删除和添加新元素。
有几种方法可以做到这一点,推荐的方法是从渲染函数中提取元素。然后引用变为静态

//extracted from render function:
const Menu = (props) => (
  <div key='1' className={ props.toggle ? `menu__container toggle` : `menu__container` }>
    <div className={'menu__content'}>
      <ul className={'menu__list'}>
        <li className={'menu__item'} onClick={props.toggleMenu}> <Link href="/"><span>Home</span></Link></li>
        <li className={'menu__item'} onClick={props.toggleMenu}> <Link href="/services"><span>Services</span></Link></li>
        <li className={'menu__item'} onClick={props.toggleMenu}> <Link href="/services#contact"><span>Contact</span></Link></li>
      </ul>
    </div>
  </div>
);


function Navbar() {
  const [menu, setToggle] = useState(false);
  const toggleMenu = () => setToggle(!menu);
 
  return (
    <div className={'navbar'}>
      <i>Meuartelie</i>
      <button className={'hamburger'} onClick={toggleMenu} >☰</button>
      <Menu toggle={menu} toggleMenu={toggleMenu}></Menu>
    </div>
  );
}

有时从渲染函数中提取组件不方便,例如如果组件是动态的created/determined。
然后,您可以使用钩子 useMemo 告诉 React 存储引用,并且仅在依赖数组中的道具发生变化时才重新计算该值。

function NavbarAndMenuMemoized() {
  const [menu, setToggle] = useState(false);
  const toggleMenu = () => setToggle(!menu);
 
  // useMemo will not recalculate Menu each render, instead it will keep the same reference for Menu.
  const Menu = useMemo(()=>{
    return(props) => (
      <div key='1' className={ props.toggle ? `menu__container toggle` : `menu__container` }>
        <div className={'menu__content'}>
          <ul className={'menu__list'}>
            <li className={'menu__item'} onClick={toggleMenu}> <Link href="/"><span>Home</span></Link></li>
            <li className={'menu__item'} onClick={toggleMenu}> <Link href="/services"><span>Services</span></Link></li>
            <li className={'menu__item'} onClick={toggleMenu}> <Link href="/services#contact"><span>Contact</span></Link></li>
          </ul>
        </div>
      </div>
    );
  }, [])

  return (
    <div className={'navbar'}>
      <i>Meuartelie</i>
      <button className={'hamburger'} onClick={toggleMenu} >☰</button>
      <Menu toggle={menu} ></Menu>
    </div>
  );
}

live demo Example