I'm having issues with an overlay navbar which is supposed to slide in from top.
Not sure why, but if i click the hamburger menu, the proper css class is applied, but the transition doesn't occur (it acts like a show/hidden).
However, if i change the top property value from the dev tool, it works as expected.
what am i missing?
Here's the react component
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;
and here's the 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;
}
This has to do with how react determines what to rerender.
Whenever react determines a new reference in the render tree, it automatically rerenders that element.
CSS transitions on the other hand will transform from a starting state, to the end state.
If the start and end are the same, no transition is applied.
So what's actually happening:
The button gets clicked.
The render tree is modified. specifically const Menu is assigned a new reference.
React removed any old elements from the dom, in this case <div class="menu__container">
React adds the new elements to the dom, in this case <div class="menu_container toggle">
the browser renders the element and applies the initial css rules. the element receives the top:0 as the initial state. there is no transition since the old element was removed.
How to fix it:
Make sure you React knows we're still rendering the same element, so it will update the existing dom element, instead of removing and adding a new element.
several ways to do this, the recommended way is to extract the element from the render function. The reference becomes static then
//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>
);
}
Sometimes its inconvenient to extract the component from the render function, for example If the components is dynamically created/determined.
You can then use a the hook useMemo to tell react to store the reference, and only recalulate the value if a prop in the dependency-array changes.
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
Related
I'm new to React. I have this image ("c-tile-img") inside a div ("c-tile-holder") and what I want to do is when I hover over the div or the image element I want to translate on Y-axis slightly like a hover effect. I used React useState hook to achieve this but when I run it nothing is triggered. Here is what I've so far...
**
Tile.jsx
**
const Tile = () => {
const[isMousedOver, setMouseOver] = useState(false);
function handleMouseOver() {
setMouseOver(true);
}
function handleMouseOut() {
setMouseOver(false);
}
return (
<React.Fragment>
<div className="c-tiles">
<a href="/work/help-scout/" className="c-tile c-tile-hs" aria-label="Help Scout Case Study"
style={{translate: isMousedOver ? '50': '0'}}
onMouseOut={handleMouseOut}
onMouseOver={handleMouseOver}>
<span className="c-tile-category">Mobile</span>
<h2 className="c-tile-title">Help Scout</h2>
<div className="c-tile-holder">
<img className="c-tile-img c-tile-img-hs lazy loaded" alt="Help Scout" aria-label="Help Scout image" data-ll-status="loaded"
src={headshot}/>
</div>
</a>
My CSS File
Tile.css
.c-tile-holder {
position: relative;
max-width: 100%;
margin: 0 auto;
text-align: center;
}
.c-tile-img-hs {
max-width: 80%;
}
.c-tile-img {
position: relative;
transition: all .4s ease-in-out;
max-width: 100%;
display: inline-block;
}
img {
vertical-align: middle;
}
img {
border-style: none;
}
I have a problem with CSS or somethings
I want to click at the profile to show a popup "logout" but it doesn't show anything.
At first, I thought there is a mistake at my CSS, but I searched many times for this solution but it still doesn't work.
import React,{useRef,useEffect} from 'react'
const profileActionRef = useRef(null)
const toggleProfileActions = () => profileActionRef.current.classList.toggle('show__popup')
<div className='profile'>
<motion.img
whileTap={{scale:1.2}}
src={userlogo}
alt=''
onClick={toggleProfileActions}
/>
<div className="profile__actions"
ref={profileActionRef}
onClick={toggleProfileActions}>
{ currentUser ? (<span onClick={logout} >Logout</span> ):( <div>
<Link to='/signup'>Sigup</Link>
<Link to='/login'>Login</Link>
</div>)
}
</div>
</div>
my CSS
.nav__icons .profile .profile__actions{
position: absolute;
top: 98%;
left: 0;
width: 150px;
z-index: 10;
padding: 15px;
display: flex;
align-items: center;
flex-direction: column;
background: var(--card-bg-01);
line-height: 30px;
cursor: pointer;
display: none;
}
.show__popup{
display: block;
}
I have no idea how to fix this. If you can tell me how to fix this it will helpful.
It's not a react issue, it's a css issue: your first css selector is more precise, it has a weight of 3 (https://specificity.keegan.st/), but the .show__popup has a weight of only 1 since it's only a single class. So it's apply before the first selector and the display is erased.
To fix that you can add more weight to your second selector:
.nav__icons .profile .profile__actions .show__popup
I have written a simple navbar that has a scroll animation.
Example:
<div className={"nav active"}>
Here CSS on active is not working. Even if I write .active or nav.active in my CSS file, I have no result. In my code I have used a on scroll event listener so when I scroll down, the navbar appears black and when I go on the navbar, it disappears. But the problem is that CSS is working in nav, but not working in
active and as a result black color in active is not appearing.
const [show , handleshow]= useState(false);
const transitionNavBar = () => {
if (window.scroll > 100){
handleshow(true);
}else{
handleshow(false);
}
};
useEffect(() => {
window.addEventListener("scroll",transitionNavBar);
return () => window.removeEventListener("scroll", transitionNavBar);
}, []);
return (
<div className={`nav ${show && "active"}`}>
<div className="nav_contents">
<img className='nav_logo'
src="http://assets.stickpng.com/images/580b57fcd9996e24bc43c529.png"
alt=""
/>
<img className='nav_avatar' src="https://i.pinimg.com/originals/0d/dc/ca/0ddccae723d85a703b798a5e682c23c1.png"
alt=""
/>
</div>
</div>
)
}
.nav{
position: fixed;
top: 0;
padding: 20px;
width: 100%;
height: 30px;
z-index: 1;
}
.nav.active{
background-color: #111;
}
.nav_contents{
display: flex;
justify-content: space-between;
}
.nav_logo{
position: fixed;
top: 10px;
left: 0%;
width: 80px;
object-fit: contain;
padding-left: 20px;
cursor: pointer;
}
.nav_avatar{
position: fixed;
cursor: pointer;
right: 20px;
width: 30px;
height: 30px;
}
Instead of this
<div className={`nav ${show && "active"}`}>
Try this:
<div className={`nav ${show ? "active":""}`}>
I think you need to write your CSS in a seperate file, if you are not using styled components for example.
Write your CSS classes inside a seperate file and then import the css file inside your JSX/JS.
For example:
import "./navbar.css"
Edit: Sorry, just reread and noticed your nav is working.
As the other posts suggest, you would probably need to use a conditional operator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
I don't know what was a problem but i uninstall ns install vs code again my issue was solved I really don't know how it worked but it worked.
I don't really know why transforming the X position while appending an additional class to make the slide effect isn't working as expected in React, as it does while using vanilla javascript.
This is the code example
.inputModal {
position: absolute;
width: 500px;
height: 150px;
display: inline-flex;
justify-content: center;
align-items: center;
padding: 10px 20px;
background-color: rgba(25, 26, 26, 0.74);
left: -8px;
top: 30%;
box-shadow: 10px 15px 20px rgba(0, 0, 0, 0.5);
transform: translateX(-93%);
transition: all 0.3s ease-in-out;
}
.InputModal.show {
transform: translateX(0px);
}
And the simple Component which should append the class show on button click
function InputModal() {
const [toggleBtn, setToggleBtn] = useState(true);
return (
<div className={toggleBtn ? "inputModal" : "InputModal show"}>
<Input />
<div
className="toggleBtn"
onClick={() => {
setToggleBtn(!toggleBtn);
}}
>
<i className="fas fa-align-justify"></i>
</div>
</div>
);
}
Once I click the toggle button(div) the show class is appended on to the div, but the div is losing all the previous set stylings and gets messed up
You should append instead of change the classes, just like:
function InputModal() {
const [toggleBtn, setToggleBtn] = useState(true);
return (
<div className={`inputModal ${toggleBtn ? "" : "InputModal show"}`}>
<Input />
<div
className="toggleBtn"
onClick={() => {
setToggleBtn(!toggleBtn);
}}
>
<i className="fas fa-align-justify"></i>
</div>
</div>
);
}
I am trying to animate a side menu, but for some reason it just wont work!
I read on a few answers that using a map to map the <li> items could be the issue (and I have tried with just a few items with static text), but that doesnt seem to be the problem.
Additionally, I have also put the generated items into a state, and wrapped the set with an useEffect to prevent retriggering of the map, still doesn't work. Only the transition is not working, the menu is appearing where it must appear, closes itself when I click the menu button, etc.
EDIT: I think it is worth mentioning that while working on it on my development server, when I am updating my css file (its not refreshing the page), when I change some of the styles that make the sidebar move, it moves with animation, so the animation is there, it is just not working when I am clicking the open/close button but it just appears instantly.
P.S. uuid() generates unique key for each element, so that is not an issue either.
export default function SideBar({ names, closeSidebarOnClick, show }) {
const [robots, setRobots] = useState();
useEffect(() => {
setRobots(GenerateNavItems());
}, names);
function GenerateNavItems() {
return names.map((robotName) => {
return (
<li key={uuid()} className={`nav-item`} onClick={() => {closeSidebarOnClick();}}>
{robotName}
</li>
);
});
}
return (
<nav className={`sidebar ${show ? "show-sidebar" : ""}`}>
<ul>{robots}</ul>
</nav>
);
}
.nav-item {
padding-left: 15px;
text-decoration: none;
font-weight: bold;
}
.nav-item:hover {
color: red;
cursor: pointer;
}
.toggle-sidebar-button {
user-select: none;
cursor: pointer;
padding-left: 1.5rem;
padding-top: 0.5rem;
border: none;
background: transparent;
z-index: 300;
/* z-index: 50; */
}
.toggle-sidebar-button:active {
border: none;
}
.toggle-sidebar-button-line {
width: 35px;
height: 5px;
background-color: black;
margin: 6px 0;
}
.sidebar {
height: 100%;
position: fixed;
top: 0;
left: 0;
width: 200px;
z-index: 200;
background-color: white;
transform: translateX(-100%);
transition: transform 1s ease;
}
.sidebar.show-sidebar {
transform: translateX(0);
}
.sidebar ul {
height: 100%;
list-style: none;
padding-left: 3rem;
padding-top: 3rem;
display: flex;
flex-direction: column;
}
<div id="root">
<div class="content row">
<div class="toggle-sidebar-button">
<div class="toggle-sidebar-button-line"></div>
<div class="toggle-sidebar-button-line"></div>
<div class="toggle-sidebar-button-line"></div>
</div>
<nav class="sidebar ">
<ul>
<li class="nav-item">ROBOT 1</li>
<li class="nav-item">ROBOT 2</li>
</ul>
</nav>
<div class="rendered-robot col">
<div>CHOOSE ROBOT</div>
</div>
</div>
</div>
It's possible that as you are re-rendering the nav component so the transition is not been visible. I suggest you to try this experiment - In closeSidebarOnClickdont don't let this do a re-render and just use the - document.getElementsByTagName('nav')[0].classList.add('show-sidebar');
There is a way to create a React.createRef() or useRef as mentioned here but this again is not to be used frequently.
Well, it may be a bad practice but when it comes to css animations what is the alternative we have? I use these animations via redux all the time :) I think the dom must not be manipulated directly frequently but since this is a nav bar and likely there is only one nav in the entire app, this much shall be ok. Let me know in case you find a typical react way of getting css animations work.
I was having this same issue, and uuid() WAS the problem. Simply using a different type of key solved the transition issue.