-
-
Save julianocomg/296469e414db1202fc86 to your computer and use it in GitHub Desktop.
| /** | |
| * @author Juliano Castilho <[email protected]> | |
| */ | |
| 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> |
would you ever consider publishing this on npm?
Here's an ES6 version of this:
import React, { Component, PropTypes } from 'react';
class Affix extends Component {
static propTypes = {
offset: PropTypes.number,
};
static defaultProps = {
offset: 0,
};
constructor() {
super();
this.state = {
affix: false,
};
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll = () => {
const affix = this.state.affix;
const offset = this.props.offset;
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if (!affix && scrollTop >= offset) {
this.setState({
affix: true,
});
}
if (affix && scrollTop < offset) {
this.setState({
affix: false,
});
}
};
render() {
const affix = this.state.affix ? 'affix' : '';
const { className, ...props } = this.props;
return (
<div {...props} className={`${className || ''} ${affix}`}>
{this.props.children}
</div>
);
}
}
export default Affix;
Also there's a syntax error in the original here:
https://gist.github.com/julianocomg/296469e414db1202fc86#file-affixwrapper-jsx-L72
Hey there! I am a React newbie so this question may seem naive, but I'll shoot anyway: how does the render() function make sure the target element is fixed at the top/certain level? Just by adding affix to class?
For future readers: @shuchenliu, yep. .affix is a bootstrap class that enables position: fixed, see the bootstrap docs at http://getbootstrap.com/javascript/#affix for details. You still need to add your own css rules to make sure that it has the right position.
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>
);
}
This was really useful. Thanks.
There is one improvement to be had though. Firefox doesn't recognise
document.body.scrollTop. It does recognisedocument.documentElement.scrollTop, which is apparently the more standards compliant way.However, Chrome and Safari don't recognise
document.documentElement.scrollTop, so you need to have both: