UNPKG

@integreat-app/react-sticky-headroom

Version:

ReactStickyHeadroom is a React Component for hiding the header when scrolling.

174 lines 8.28 kB
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