@integreat-app/react-sticky-headroom
Version:
ReactStickyHeadroom is a React Component for hiding the header when scrolling.
174 lines • 8.28 kB
JavaScript
function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
import React from 'react';
import styled from '@emotion/styled';
import { keyframes } from '@emotion/react';
const DIRECTION_UP = 'up';
const DIRECTION_DOWN = 'down';
const MODE_UNPINNED = 'unpinned';
const MODE_PINNED = 'pinned';
const MODE_STATIC = 'static';
const TRANSITION_NONE = 'none';
const TRANSITION_NORMAL = 'normal';
const TRANSITION_PINNED_TO_STATIC = 'pinned-to-static';
const HeaderWrapper = /*#__PURE__*/ styled("div", {
target: "e1o9mv740",
label: "HeaderWrapper"
})("position:", (props)=>props.$positionStickyDisabled ? 'static' : 'sticky', ";top:", (props)=>props.$top, "px;z-index:", (props)=>props.$zIndex, ";transform:translateY(", (props)=>props.$translateY, "px);animation-duration:0.2s;animation-timing-function:ease-out;", (props)=>props.$transition === TRANSITION_NORMAL && !props.$static ? 'transition: transform 0.2s ease-out;' : '', " animation-name:", (props)=>props.$transition === TRANSITION_PINNED_TO_STATIC && props.$animateUpFrom !== null ? keyframesMoveUpFrom(props.$animateUpFrom) : 'none', ";", (props)=>props.$static ? 'transition: none;' : '');
const keyframesMoveUpFrom = (from)=>/*#__PURE__*/ keyframes("from{transform:translateY(", Math.max(from, 0), "px)}to{transform:translateY(0)}", "keyframesMoveUpFrom");
var _React_PureComponent;
class Headroom extends (_React_PureComponent = React.PureComponent) {
/**
* @returns {number} the current scrollTop position of the window
*/ getScrollTop() {
const parent = this.props.parent;
if (parent && parent.scrollTop !== undefined && parent !== document.documentElement) {
return parent.scrollTop;
}
if (parent !== document.documentElement) {
console.warn('Could not determine scrollTop from parent for StickyHeadroom. Defaulting to window.pageYOffset.');
}
if (window.pageYOffset === undefined) {
console.error('window.pageYOffset is undefined. Defaulting to 0.');
return 0;
}
return window.pageYOffset;
}
componentDidMount() {
this.addScrollListener(this.props.parent);
}
addScrollListener(parent) {
if (parent === window.document.documentElement) {
window.addEventListener('scroll', this.handleEvent);
} else if (parent) {
parent.addEventListener('scroll', this.handleEvent);
} else {
console.debug("'parent' prop of Headroom is null. Assuming, it will be set soon...");
}
}
removeScrollListener(parent) {
if (parent === window.document.documentElement) {
window.removeEventListener('scroll', this.handleEvent);
} else if (parent) {
parent.removeEventListener('scroll', this.handleEvent);
}
}
componentDidUpdate(prevProps) {
if (prevProps.parent !== this.props.parent) {
this.removeScrollListener(prevProps.parent);
this.addScrollListener(this.props.parent);
}
}
componentWillUnmount() {
this.removeScrollListener(this.props.parent);
}
/**
* If we're already static and pinStart + scrollHeight >= scrollTop, then we should stay static.
* If we're not already static, then we should set the header static, only when pinStart >= scrollTop (regardless of
* scrollHeight, so the header doesn't jump up, when scrolling upwards to the trigger).
* Else we shouldn't set it static.
* @param scrollTop the currentScrollTop position
* @param direction the current direction
* @returns {boolean} if we should set the header static
*/ shouldSetStatic(scrollTop, direction) {
if (this.state.mode === MODE_STATIC || this.state.mode === MODE_PINNED && direction === DIRECTION_DOWN) {
return this.props.pinStart + this.props.scrollHeight >= scrollTop;
} else {
return this.props.pinStart >= scrollTop;
}
}
/**
* Determines the mode depending on the scrollTop position and the current direction
* @param {number} scrollTop
* @param {string} direction
* @returns {string} the next mode of Headroom
*/ determineMode(scrollTop, direction) {
if (this.shouldSetStatic(scrollTop, direction)) {
return MODE_STATIC;
} else {
return direction === DIRECTION_UP ? MODE_PINNED : MODE_UNPINNED;
}
}
/**
* @returns {TransitionType} determines the kind of transition
*/ determineTransition(mode, direction) {
// Handle special case: If we're pinned and going to static, we need a special transition using css animation
if (this.state.mode === MODE_PINNED && mode === MODE_STATIC) {
return TRANSITION_PINNED_TO_STATIC;
}
// If mode is static, then no transition, because we're already in the right spot
// (and want to change transform and top properties seamlessly)
if (mode === MODE_STATIC) {
return this.state.transition === TRANSITION_NONE ? TRANSITION_NONE : TRANSITION_PINNED_TO_STATIC;
}
// mode is not static, transition when moving upwards or when we've lastly did the transition
return direction === DIRECTION_UP || this.state.transition === TRANSITION_NORMAL ? TRANSITION_NORMAL : TRANSITION_NONE;
}
static calcStickyTop(mode, height, scrollHeight) {
return mode === MODE_PINNED ? height : height - scrollHeight;
}
render() {
const { children, scrollHeight, positionStickyDisabled, zIndex, className } = this.props;
const { mode, transition, animateUpFrom } = this.state;
const transform = mode === MODE_UNPINNED ? -scrollHeight : 0;
const ownStickyTop = mode === MODE_STATIC ? -scrollHeight : 0;
return /*#__PURE__*/ React.createElement(HeaderWrapper, {
className: className,
$translateY: transform,
$top: ownStickyTop,
$transition: transition,
$positionStickyDisabled: !!positionStickyDisabled,
$static: mode === MODE_STATIC,
$animateUpFrom: animateUpFrom,
$zIndex: zIndex
}, children);
}
constructor(...args){
super(...args), _define_property(this, "state", {
mode: MODE_STATIC,
transition: TRANSITION_NONE,
animateUpFrom: null
}), /** the very last scrollTop which we know about (to determine direction changes) */ _define_property(this, "lastKnownScrollTop", 0), /**
* Checks the current scrollTop position and updates the state accordingly
*/ _define_property(this, "update", ()=>{
const currentScrollTop = this.getScrollTop();
const newState = {};
if (currentScrollTop === this.lastKnownScrollTop) {
return;
}
const direction = this.lastKnownScrollTop < currentScrollTop ? DIRECTION_DOWN : DIRECTION_UP;
newState.mode = this.determineMode(currentScrollTop, direction);
newState.transition = this.determineTransition(newState.mode, direction);
const { onStickyTopChanged, height, scrollHeight, pinStart } = this.props;
if (this.state.mode === MODE_PINNED && newState.mode === MODE_STATIC) {
// animation in the special case from pinned to static
newState.animateUpFrom = currentScrollTop - pinStart;
}
if (onStickyTopChanged && newState.mode !== this.state.mode && height) {
onStickyTopChanged(Headroom.calcStickyTop(newState.mode, height, scrollHeight));
}
this.setState(newState);
this.lastKnownScrollTop = currentScrollTop;
}), _define_property(this, "handleEvent", ()=>{
window.requestAnimationFrame(this.update);
});
}
}
_define_property(Headroom, "defaultProps", {
pinStart: 0,
zIndex: 1,
parent: window.document.documentElement
});
export default Headroom;
//# sourceMappingURL=index.js.map