Last active
May 13, 2022 06:36
-
-
Save steveruizok/10bd915d37bd89dfcdad2b6a869c2a12 to your computer and use it in GitHub Desktop.
A Framer X hook to hide or show a header based on a scroll position.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react" | |
import { | |
Override, | |
motionValue, | |
transform, | |
useTransform, | |
MotionValue, | |
} from "framer" | |
type States = "open" | "closed" | "opening" | "closing" | |
/** | |
* ## useScrollHeader | |
* - `scrollY`: A motion value shared with a scroll's `contentOffsetY` prop | |
* - `options`: An object of options for the header | |
* - `closeHeight`: The header's height when closed | |
* - `closeDistance`: The scroll distance required to fully close the header | |
* - `closePadding: The extra scroll distance before starting to close | |
* - `openHeight`: The header's height when open | |
* - `openDistance`: The scroll distance required to fully open the header | |
* - `openPadding`: The extra scroll distance before starting to open | |
*/ | |
export const useScrollHeader: ( | |
scrollY: MotionValue<number>, | |
options?: { | |
closeHeight?: number | |
openHeight?: number | |
openDistance?: number | |
closeDistance?: number | |
closePadding?: number | |
openPadding?: number | |
} | |
) => MotionValue<number> = (scrollY, options = {}) => { | |
const { | |
closeHeight = 0, | |
openHeight = 128, | |
openDistance = 128, | |
closeDistance = 240, | |
closePadding = 0, | |
openPadding = 0, | |
} = options | |
const stateRef = React.useRef<States>("open") | |
const startRef = React.useRef(0) | |
const prevRef = React.useRef(0) | |
return useTransform(scrollY, (y) => { | |
console.log(y) | |
// Get change between this scrollY and last | |
const delta = y - prevRef.current | |
// Update prevRef | |
prevRef.current = y | |
// Get the current reference point for distance | |
const start = startRef.current | |
// Get distance between current and start | |
const distance = y - start | |
// Actions for the four possible states | |
const states = { | |
open: () => { | |
if (delta < 0) { | |
stateRef.current = "closing" | |
} | |
startRef.current = Math.min(y, 0) | |
return openHeight | |
}, | |
closed: () => { | |
if (delta > 0) { | |
stateRef.current = "opening" | |
} | |
startRef.current = y | |
return closeHeight | |
}, | |
closing: () => { | |
if (distance <= -closeDistance - closePadding) { | |
startRef.current = y | |
stateRef.current = "closed" | |
} else if (distance > 0) { | |
stateRef.current = "open" | |
} | |
return transform( | |
y + closePadding, | |
[start - closeDistance, start], | |
[closeHeight, openHeight] | |
) | |
}, | |
opening: () => { | |
if (distance >= openDistance + openPadding) { | |
startRef.current = y | |
stateRef.current = "open" | |
} else if (distance < 0) { | |
stateRef.current = "closed" | |
} | |
return transform( | |
y - openPadding, | |
[start, start + openDistance], | |
[closeHeight, openHeight] | |
) | |
}, | |
} | |
// Run the header's height | |
// depending on the header's state | |
return states[stateRef.current]() | |
}) | |
} | |
const mvScrollY = motionValue(0) | |
// Set this override on the scroll layer | |
export function Scroll(): Override { | |
return { | |
contentOffsetY: mvScrollY, | |
} | |
} | |
// Set this override on the header layer | |
export function Header(): Override { | |
const mvHeaderHeight = useScrollHeader(mvScrollY) | |
return { | |
height: mvHeaderHeight, | |
} | |
} |
Hey, sure, I updated the gist to include those overrides, too. Your design file should have three layers: a device frame, a scroll, and a frame for the header. Example here.
Wow - Thanks so much. I have been pulling out what little hair I have left trying to figure out something like this.
Top Man
… On 11 Feb 2021, at 11:24, Steve Ruiz ***@***.***> wrote:
@steveruizok commented on this gist.
Hey, sure, I updated the gist to include those overrides, too. Your design file should have three layers: a device frame, a scroll, and a frame for the header. Example here <https://framer.com/projects/Scroll-Example--b9zq9XiY73d8ZQFm5oDu-1omoT>.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub <https://gist.github.com/10bd915d37bd89dfcdad2b6a869c2a12#gistcomment-3627722>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ADSNLLGADZNUG352GN6QSM3S6OV5XANCNFSM4XOMPAAQ>.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Steve - just stumbled across this and it's just what I am after. Not quite sure how to implement it though. It's not an override and there isn't any actual 'Header' Any chance for some tips?