Sticky Header

Hi, I would like to implement a resizable sticky header like the one found at https://frontity.org/

Thks ! :hugs:

I found a solution, I would like to share it and I await your comments :smile:

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?