UNPKG

react-fullpage

Version:

An implementation of fullpage.js in react based on react-fullpage

435 lines (365 loc) 10.9 kB
import React, { Component, Children, cloneElement } from 'react'; import PropTypes from 'prop-types'; class SectionsContainer extends Component { state = { activeSection: this.props.activeSection, scrollingStarted: false, sectionScrolledPosition: 0, windowHeight: 0 }; resetScrollTimer; childrenLength; getChildContext() { return { verticalAlign: this.props.verticalAlign, sectionClassName: this.props.sectionClassName, sectionPaddingTop: this.props.sectionPaddingTop, sectionPaddingBottom: this.props.sectionPaddingBottom }; } componentWillUnmount() { this.clearResetScrollTimer(); this.removeDefaultEventListeners(); this.removeMouseWheelEventHandlers(); this.removeOverflowFromBody(); } componentDidMount() { this.childrenLength = this.props.children.length; this.handleResize(); window.addEventListener('resize', this.handleResize); if (!this.props.scrollBar) { this.addCSS3Scroll(); this.handleAnchor(); window.addEventListener('hashchange', this.handleAnchor, false); if (this.props.arrowNavigation) { window.addEventListener('keydown', this.handleArrowKeys); } if (this.props.touchNavigation) { this.handleTouchNav(); } } } componentWillReceiveProps(nextProps) { if (this.props.activeSection !== nextProps.activeSection) { this.setState({ activeSection: nextProps.activeSection }); this.setAnchor(nextProps.activeSection); this.handleSectionTransition(nextProps.activeSection); this.addActiveClass(); } } removeDefaultEventListeners = () => { window.removeEventListener('resize', this.handleResize); window.removeEventListener('hashchange', this.handleAnchor); if (this.props.arrowNavigation) { window.removeEventListener('keydown', this.handleArrowKeys); } } addCSS3Scroll = () => { this.addOverflowToBody(); this.addMouseWheelEventHandlers(); } addActiveClass = () => { this.removeActiveClass(); let hash = window.location.hash.substring(1); let activeLinks = document.querySelectorAll(`a[href="#${hash}"]`); for (let i = 0; i < activeLinks.length; i++) { activeLinks[i].className = activeLinks[i].className + (activeLinks[i].className.length > 0 ? ' ' : '') + `${this.props.activeClass}`; } } removeActiveClass = () => { let activeLinks = document.querySelectorAll( `a:not([href="#${this.props.anchors[this.state.activeSection]}"])` ); for (let i = 0; i < activeLinks.length; i++) { activeLinks[i].className = activeLinks[i].className.replace( /\b ?active/g, '' ); } } addChildrenWithAnchorId = () => { let index = 0; return Children.map(this.props.children, child => { let id = this.props.anchors[index]; index++; if (id) { return cloneElement(child, { id: id }); } else { return child; } }); } addOverflowToBody = () => { document.querySelector('body').style.overflow = 'hidden'; } removeOverflowFromBody = () => { document.querySelector('body').style.overflow = 'initial'; } addMouseWheelEventHandlers = () => { window.addEventListener('mousewheel', this.handleMouseWheel, false); window.addEventListener('DOMMouseScroll', this.handleMouseWheel, false); } removeMouseWheelEventHandlers = () => { window.removeEventListener('mousewheel', this.handleMouseWheel); window.removeEventListener('DOMMouseScroll', this.handleMouseWheel); } handleMouseWheel = event => { const e = window.event || event; const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail)); const activeSection = this.state.activeSection - delta; if ( this.state.scrollingStarted || activeSection < 0 || this.childrenLength === activeSection ) { return false; } this.setAnchor(activeSection); this.handleSectionTransition(activeSection); this.addActiveClass(); } handleResize = () => { const position = 0 - this.state.activeSection * window.innerHeight; this.setState({ scrollingStarted: true, windowHeight: window.innerHeight, sectionScrolledPosition: position }); this.resetScroll(); } handleSectionTransition = (index) => { const position = 0 - index * this.state.windowHeight; if ( !this.props.anchors.length || index === -1 || index >= this.props.anchors.length ) { return false; } this.setState({ scrollingStarted: true, activeSection: index, sectionScrolledPosition: position }); this.resetScroll(); this.handleScrollCallback(); } handleArrowKeys = (e) => { const event = window.event ? window.event : e; const activeSection = event.keyCode === 38 || event.keyCode === 37 ? this.state.activeSection - 1 : event.keyCode === 40 || event.keyCode === 39 ? this.state.activeSection + 1 : -1; if ( this.state.scrollingStarted || activeSection < 0 || this.childrenLength === activeSection ) { return false; } this.setAnchor(activeSection); this.handleSectionTransition(activeSection); this.addActiveClass(); } handleTouchNav = () => { let that = this; let touchsurface = document.querySelector('.' + this.props.className), swipedir, startX, startY, dist, distX, distY, threshold = 50, restraint = 100, allowedTime = 1000, elapsedTime, startTime, handleswipe = function(swipedir) { console.log(swipedir); }; touchsurface.addEventListener( 'touchstart', function(e) { let touchobj = e.changedTouches[0]; swipedir = 'none'; dist = 0; startX = touchobj.pageX; startY = touchobj.pageY; startTime = new Date().getTime(); }, false ); touchsurface.addEventListener( 'touchmove', function(e) { e.preventDefault(); }, false ); touchsurface.addEventListener( 'touchend', function(e) { let touchobj = e.changedTouches[0]; distX = touchobj.pageX - startX; distY = touchobj.pageY - startY; elapsedTime = new Date().getTime() - startTime; if (elapsedTime <= allowedTime) { if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint) { swipedir = distY < 0 ? 'up' : 'down'; let direction = swipedir === 'down' ? that.state.activeSection - 1 : swipedir === 'up' ? that.state.activeSection + 1 : -1; let hash = that.props.anchors[direction]; if (!that.props.anchors.length || hash) { window.location.hash = '#' + hash; } that.handleSectionTransition(direction); } } handleswipe(swipedir); }, false ); } handleAnchor = () => { const hash = window.location.hash.substring(1); const activeSection = this.props.anchors.indexOf(hash); if (this.state.activeSection !== activeSection) { this.handleSectionTransition(activeSection); this.addActiveClass(); } } setAnchor = (index) => { const hash = this.props.anchors[index]; if (!this.props.anchors.length || hash) { window.location.hash = '#' + hash; } } handleScrollCallback = () => { if (this.props.scrollCallback) { setTimeout(() => this.props.scrollCallback(this.state), 0); } } resetScroll = () => { this.clearResetScrollTimer(); this.resetScrollTimer = setTimeout(() => { this.setState({ scrollingStarted: false }); }, this.props.delay + 300); } clearResetScrollTimer = () => { if (this.resetScrollTimer) { clearTimeout(this.resetScrollTimer); } } renderNavigation = () => { let navigationStyle = { position: 'fixed', zIndex: '10', right: '20px', top: '50%', transform: 'translate(-50%, -50%)' }; const anchors = this.props.anchors.map((link, index) => { const anchorStyle = { display: 'block', margin: '10px', borderRadius: '100%', backgroundColor: '#556270', padding: '5px', transition: 'all 0.2s', transform: this.state.activeSection === index ? 'scale(1.3)' : 'none' }; return ( <a href={`#${link}`} key={index} className={this.props.navigationAnchorClass || 'Navigation-Anchor'} style={this.props.navigationAnchorClass ? null : anchorStyle} /> ); }); return ( <div className={this.props.navigationClass || 'Navigation'} style={this.props.navigationClass ? null : navigationStyle}> {anchors} </div> ); } render() { let containerStyle = { height: '100%', width: '100%', position: 'relative', transform: `translate3d(0px, ${ this.state.sectionScrolledPosition }px, 0px)`, transition: `all ${this.props.delay}ms ease` }; return ( <div> <div className={this.props.className} style={containerStyle}> {this.props.scrollBar ? this.addChildrenWithAnchorId() : this.props.children} </div> {this.props.navigation && !this.props.scrollBar ? this.renderNavigation() : null} </div> ); } } SectionsContainer.defaultProps = { scrollCallback: null, delay: 1000, verticalAlign: false, scrollBar: false, navigation: true, className: 'SectionContainer', sectionClassName: 'Section', anchors: [], activeClass: 'active', sectionPaddingTop: '0', sectionPaddingBottom: '0', arrowNavigation: true, activeSection: 0, touchNavigation: true }; SectionsContainer.propTypes = { scrollCallback: PropTypes.func, delay: PropTypes.number, verticalAlign: PropTypes.bool, scrollBar: PropTypes.bool, navigation: PropTypes.bool, className: PropTypes.string, sectionClassName: PropTypes.string, navigationClass: PropTypes.string, navigationAnchorClass: PropTypes.string, activeClass: PropTypes.string, sectionPaddingTop: PropTypes.string, sectionPaddingBottom: PropTypes.string, arrowNavigation: PropTypes.bool, activeSection: PropTypes.number, touchNavigation: PropTypes.bool }; SectionsContainer.childContextTypes = { verticalAlign: PropTypes.bool, sectionClassName: PropTypes.string, sectionPaddingTop: PropTypes.string, sectionPaddingBottom: PropTypes.string }; export default SectionsContainer;