Search code examples
javascriptreactjsdomgatsbynetlify

In React/Gatsby how can I animate a sticky header using CSS keyframes when I don't have access to the document object?


I have a sticky header made in React/Gatsby, it shows only after you scroll down for a bit. It works but the header pops up really abruptly so I tried to animate it. The problem is I'm trying to use CSS keyframes using the insertRule document property and hosting platforms like Netlify don't have access to the document object so whenever it builds, I get this error:

error "document" is not available during server side rendering.

I researched and I can't find any suitable solutions, jsdom doesn't seem like it would solve it and I tried using styled components, doesn't work.

Here's the keyframe CSS and style insertion using insertRule:

let styleSheet = document.styleSheets[0];

const keyframesFixed = `
    @keyframes headerAnimationFixed{
        0%{
            opacity: 0.7;
            transform: translateY(-100%);
        }
        100%{
            opacity: 1;
            transform: translateY(0);
        }
    }
`;

styleSheet.insertRule(keyframesFixed, styleSheet.cssRules.length);

And here is how I'm using the animation:

const fixedStyles = {
    position: 'fixed',
    zIndex: '200',
    top: '0',
    left: '0',
    animation: 'headerAnimationFixed 0.3s ease',
    transition: 'all ease 0.3s'
}

Using state, I determine wether the user has scrolled down far enough, if so, the fixedStyles are applied.

Is there another way I can use keyframes in a Header component without using the document object?

Thank you for your time!


Solution

  • As you can see (and as you pointed) in the docs, document and other global objects (such as window) are not available in the SSR. There are multiple solutions to bypass this issue, but in your case, the most simple is just to check if window is available to trigger the animations. If so, this will mean that you are not in the SSR. Something like this should work:

    if (typeof window != "undefined"){
    let styleSheet = document.styleSheets[0];
    
    const keyframesFixed = `
        @keyframes headerAnimationFixed{
            0%{
                opacity: 0.7;
                transform: translateY(-100%);
            }
            100%{
                opacity: 1;
                transform: translateY(0);
            }
        }
    `;
    
    styleSheet.insertRule(keyframesFixed, styleSheet.cssRules.length);
    }
    

    Alternatively, if your wrapper doesn't allow if statements (JSX), you can add a ternary condition:

    let styleSheet = typeof window != "undefined" ? document.styleSheets[0] : null;
    
    const keyframesFixed = `
        @keyframes headerAnimationFixed{
            0%{
                opacity: 0.7;
                transform: translateY(-100%);
            }
            100%{
                opacity: 1;
                transform: translateY(0);
            }
        }
    `;
    
    typeof window != "undefined" ? styleSheet.insertRule(keyframesFixed, styleSheet.cssRules.length) : null;