-
-
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: