Skip to content

Instantly share code, notes, and snippets.

@julianocomg
Last active May 17, 2024 04:24
Show Gist options
  • Save julianocomg/296469e414db1202fc86 to your computer and use it in GitHub Desktop.
Save julianocomg/296469e414db1202fc86 to your computer and use it in GitHub Desktop.
A simple affix React component.
/**
* @author Juliano Castilho <julianocomg@gmail.com>
*/
var React = require('react');
var AffixWrapper = React.createClass({
/**
* @type {Object}
*/
propTypes: {
offset: React.PropTypes.number
},
/**
* @return {Object}
*/
getDefaultProps() {
return {
offset: 0
};
},
/**
* @return {Object}
*/
getInitialState() {
return {
affix: false
};
},
/**
* @return {void}
*/
handleScroll() {
var affix = this.state.affix;
var offset = this.props.offset;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if (!affix && scrollTop >= offset) {
this.setState({
affix: true
});
}
if (affix && scrollTop < offset) {
this.setState({
affix: false
});
}
},
/**
* @return {void}
*/
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
},
/**
* @return {void}
*/
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
},
render() {
var affix = this.state.affix ? 'affix' : '';
var {className, offset, ...props} = this.props;
return (
<div {...props} className={className + ' ' + affix)}>
{this.props.children}
</div>
);
}
});
module.exports = AffixWrapper;
<AffixWrapper className="some-cool-element" id="lalala" offset={200}>
<div>Put whatever you want here</div>
</AffixWrapper>
@d8660091
Copy link

d8660091 commented Oct 9, 2019

A functional component with react hook alternative that preserve the width

import React from 'react';

export default function Affix(props: {
  top: number;
  children: React.ReactNode;
  offset?: number;
  className?: string;
}) {
  const element = React.createRef<HTMLDivElement>();
  let oldStyles = {
    position: '',
    top: '',
    width: '',
  };
  // Offset could make the element fixed ealier or later
  const { offset = 0 } = props;

  const checkPosition = (distanceToBody: number, width: number) => {
    const scrollTop = window.scrollY;

    if (distanceToBody - scrollTop < props.top + offset) {
      if (element.current.style.position != 'fixed') {
        for (let key in oldStyles) {
          oldStyles[key] = element.current.style[key];
        }
        element.current.style.position = 'fixed';
        element.current.style.width = width + 'px';
        element.current.style.top = props.top + 'px';
      }
    } else {
      // reset to default
      for (let key in oldStyles) {
        element.current.style[key] = oldStyles[key];
      }
    }
  };

  React.useEffect(() => {
    if (typeof window.scrollY === 'undefined') {
      // don't work in IE
      return;
    }

    const distanceToBody = window.scrollY + element.current.getBoundingClientRect().top;
    const handleScroll = () => {
      requestAnimationFrame(() => {
        checkPosition(distanceToBody, element.current.clientWidth);
      });
    };

    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  });

  return (
    <div ref={element} style={{ zIndex: 1 }} className={props.className}>
      {props.children}
    </div>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment