Hi, I would like to implement a resizable sticky header like the one found at https://frontity.org/
Thks !
Hi, I would like to implement a resizable sticky header like the one found at https://frontity.org/
Thks !
I found a solution, I would like to share it and I await your comments
import React, { useState, useEffect, useRef } from 'react';
import { connect, styled } from 'frontity';
import Nav from './nav';
const Header = () => {
const [height, setHeight] = useState(0);
const ref = useRef();
useEffect(() => {
setHeight(ref.current.getBoundingClientRect().height);
}, []);
const [scrollTop, setScrollTop] = useState(0);
useEffect(() => {
const onScroll = (e) => {
setScrollTop(e.target.documentElement.scrollTop);
};
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
}
});
return (
<Container className={scrollTop > height ? 'sticky' : null}>
<div ref={ref} className='header-top'>
<Nav />
</div>
<div className='header-sticky'>
<Nav blue/>
</div>
</Container>
);
};
export default connect(Header);
const Container = styled.div`
top: 0;
position: relative;
overflow: hidden;
& img {
padding: 1em;
}
&.sticky {
position: sticky
}
& .header-top {
transition: .225s ease-out;
background: #4269e4;
& a {
color: #fff
}
}
&.sticky .header-top {
opacity: .5;
transform: translateY(-100%);
transition: none;
}
& .header-sticky {
position: absolute;
top: 0;
left: 0;
right: 0;
opacity: .5;
transform: translateY(-100%);
background: #fff;
}
&.sticky .header-sticky {
opacity: 1;
transform: translateY(0);
transition: .35s ease-out;
}
`;
Hmm… I don’t think that’s a good solution. Subscribing to scroll
and causing a React re-render on each event it’s not going to be great performance-wise because you are causing a lot of reflows. You can see a list of things that cause a reflow here: https://gist.github.com/paulirish/5d52fb081b3570c81e3a
I think it’d be better to create add the menu twice, one static at the top and one fixed that only appears when you scroll down. You can control when to hide or show the fixed one using an useInView
hook.
We had our own useInView
hook but we are now recommending this one which has more features: https://www.npmjs.com/package/react-intersection-observer
This is a rough implementation of how I would do it. Once the relative menu disappears from the screen, the other one appears.
const Menus = () => {
const [ref, inView] = useInView();
return (
<>
<Menu position="relative" ref={ref} />
{!inView && <Menu position="fixed" />
</>
);
}
const Menu = styled(MenuItems)`
position: ${props => props.position}
...
`;
If you want to animate the entrance of the fixed menu, you can use react-spring
.
cc: @David may we have your opinion here?
I just found this article on Twitter, it might be helpful: https://dev.to/ibrahima92/build-a-sticky-navigation-bar-with-react-3bjh
Is this a good solution?
According to what @luisherranz thinks it’s better to use useInView
What do you think?
Yes, I think that using the Intersection Observer both improves the performance and it’s simpler to code. But I’m not an expert. @David and @orballo are much better than me on this type of performance issues!
I can’t figure out how to use React Spring
This is the code:
<>
<div ref={ref}>
<TopNavbar />
</div>
{ !inView && <Navbar /> }
</>
Where TopNavbar
and Navbar
are components,
I need to animate the entrance of Navbar
This is cool: https://github.com/KyleAMathews/react-headroom
The scroll event is denounced using request animation frame, which should reduce the reflows.
I think you can use useTransition
for that: https://www.react-spring.io/docs/hooks/use-transition
Look for the “mount/unmount single-component reveals” example.
I have not tested, but I guess it could be implemented like this (using useTransition
as @luisherranz suggested) :
const Navbars = () => {
const [ref, inView] = useInView();
const transitions = useTransition(!inView, null, {
from: { position: "fixed", top: 0, transform: "translateY(-100%)" },
enter: { transform: "translateY(0)" },
leave: { transform: "translateY(-100%)" }
});
return (
<>
<div ref={ref}>
<TopNavbar />
</div>
{transitions.map(
({ item, key, props }) =>
item && (
<animated.div key={key} style={props}>
<Navbar />
</animated.div>
)
)}
</>
);
};
Hi,
I have tried the method @David suggested, and it works to an extent. The only problem is that the sticky header is see-thrug no matter what color i choose.
Do anyone know how to fix it?
I added a z-index of 1, and then it worked.
Nice, @kasper. Could you share which theme you did this with and the code snippet? I’d be very curious to see.
Hi @spiral272,
I ended up trashing the solution, but I dug this out of the commit history.
Since this, I’m pretty sure useTransition
has hade a breaking update that needs to be taken into consideration.
The theme I used was my own, I use it on my current website, aamodtgroup.com.