Я унаследовал приложение реагировать / узел / призма, в котором есть файл ScrollToTop.js, чтобы убедиться, что страницы загружаются вверху, что они делают при доступе из нашего главного меню навигации.
Это приложение также имеет небольшую боковую навигацию, которая скрыта до прокрутки вниз (управляется с помощью tabIndex).
Ошибка: , когда вы щелкаете ссылку с боковой навигационной панели, результирующая страница поднимается так далеко, как вы прокручивали ее при открытии боковой навигационной панели. Вместо этого я хочу, чтобы это началось сверху.
У нас есть файл Layout.js для общего макета и специальный файл SideNav.js для этой небольшой боковой навигации. Я новичок в реагировании / javascript, и я не смог выяснить, как (а) применить логику ScrollToTop к этим ссылкам sidenav или (б) добавить window.scrollTo(0,0)
для этого особого случая. Кто-нибудь может порекомендовать как / где это можно обновить?
SideNav.js:
import React from 'react'
import CTAButton from './CTAButton'
import { Link } from 'react-router-dom'
import { Link as PrismicLink } from 'prismic-reactjs'
import PrismicConfig from '../../../prismic-configuration'
import PropTypes from 'prop-types'
import * as PrismicTypes from '../Utils/Types'
const matchPaths = {
'about': 'About us',
'projects': 'Projects',
'news': 'News'
}
class SideNav extends React.Component {
constructor(props) {
super(props)
this.state = {
expanded: false
}
this.handleKey = this.handleKey.bind(this)
this.expandMenu = this.expandMenu.bind(this)
this.keypressed = undefined
this.main = undefined
this.bug = undefined
this.toggle = false
this.windowTop = 0
}
checkMobile() {
if (window.innerWidth <= 800) {
this.setState({scrollThreshold: 50})
}
}
componentDidMount() {
this.checkMobile()
this.keypressed = this.handleKey
window.addEventListener('keydown', this.keypressed)
this.main = document.getElementsByClassName('top-nav-logo')[0]
this.bug = document.getElementsByClassName('side-nav-bug-wrap')[0]
}
componentWillUnMount() {
window.removeEventListener('keydown', this.keypressed)
}
handleKey(e) {
if (e.keyCode === 27 && this.state.expanded) {
this.expandMenu()
}
}
expandMenu() {
const el = document.scrollingElement || document.documentElement
if (!this.state.expanded) {
this.windowTop = el.scrollTop
} else {
el.scrollTop = this.windowTop
}
this.setState({
expanded: !this.state.expanded
})
document.body.classList.toggle('nav-lock')
}
render() {
const expanded = this.state.expanded ? 'expanded' : 'contracted'
const tabIndex = this.state.expanded ? '1' : '-1'
if (this.state.scrollThreshold === 50 && this.state.expanded) {
this.props.removeListener()
this.main.setAttribute('style', 'display:none')
this.bug.setAttribute('style', 'display:none; position:absolute')
}
else if (this.state.scrollThreshold === 50 && !this.state.expanded) {
this.props.addListener()
this.main.setAttribute('style', 'display:inline')
this.bug.setAttribute('style', 'display:flex; position:fixed')
}
const menu = this.props.menu.map((menuItem, index) => {
const menuLink = PrismicLink.url(menuItem.link, PrismicConfig.linkResolver)
const label = menuItem.label[0].text
let marker
if (typeof this.props.location !== 'undefined') {
// Match label to window location to move indicator dot
marker = label === matchPaths[this.props.location] ? this.props.location : 'inactive'
}
return (
<li key={index} className="side-nav-li">
<Link to={label} className="side-nav-link" onClick={this.expandMenu} tabIndex={tabIndex}>{label}</Link>
<div className={`side-nav-marker ${marker}`}/>
</li>
)
})
return (
<div className='side-nav-wrapper'>
<Link to='/' className={`side-nav-logo-main ${this.props.visibility}`} alt=""/>
<div className={`side-nav-bug-wrap ${this.props.visibility}`} onClick={this.expandMenu}>
<div className='side-nav-bug-icon' />
</div>
<div className={`side-nav ${expanded}`}>
<div className={'side-nav-menu-wrap'}>
<div className="side-nav-control-wrap">
<Link to="/" className="side-nav-logo" alt="Count Me In logo" onClick={this.expandMenu} tabIndex={tabIndex}/>
<button className="side-nav-exit" onClick={this.expandMenu} tabIndex={tabIndex}/>
</div>
<nav>
<ul className="side-nav-menu-items">
{ menu }
<CTAButton />
</ul>
</nav>
</div>
</div>
<div className={`side-nav-overlay ${expanded}`} onClick={this.expandMenu}/>
</div>
)
}
}
SideNav.propTypes = {
removeListener: PropTypes.func,
addListener: PropTypes.func,
location: PropTypes.string,
visibility: PropTypes.string,
menu: PropTypes.arrayOf(PropTypes.shape({
label: PrismicTypes.PrismicTextTypes,
link: PropTypes.shape({
id: PropTypes.string,
isBroken: PropTypes.bool,
lang: PropTypes.string,
link_type: PropTypes.string,
slug: PropTypes.string,
tags: PropTypes.array,
type: PropTypes.string,
uid: PropTypes.string
})
}))
}
export default SideNav
Layout.js:
const matchPaths = {
'about': 'About us',
'projects': 'Projects',
'news': 'News'
}
class Layout extends React.Component {
constructor(props) {
super(props)
this.state = {
location: undefined,
displayMobile: false,
visibility: 'offscreen',
scrollThreshold: 100
}
this.onMobileClick = this.onMobileClick.bind(this)
this.logoClick = this.logoClick.bind(this)
this.scrollCheck = this.scrollCheck.bind(this)
this.addScrollCheck = this.addScrollCheck.bind(this)
this.removeScrollCheck = this.removeScrollCheck.bind(this)
this.addChildScroll = this.addChildScroll.bind(this)
this.removeChildScroll = this.removeChildScroll.bind(this)
this.checkCount = 0
this.childCheckCount = 0
this.scrollCheckToggle = true
this.childCheckToggle = true
}
getBodyScrollTop () {
const el = document.scrollingElement || document.documentElement
return el.scrollTop
}
// Need to do this on didMount so window object is available.
componentDidMount() {
const loc = window.location.pathname.substr(1)
this.setState({ location: loc })
//Hide the loader and display the site content.
setTimeout(function() {
document.body.classList.add('is-loaded')
}, 50)
this.addScrollCheck()
if (this.getBodyScrollTop() > this.state.scrollThreshold) {
this.setState({
visibility: 'onscreen'
})
}
}
addChildScroll() {
if (this.childCheckCount < 1 ) {
window.addEventListener('scroll', this.scrollCheck)
this.childCheckCount++
this.childCheckToggle = true
}
}
removeChildScroll() {
if (this.childCheckCount === 1) {
window.removeEventListener('scroll', this.scrollCheck)
this.childCheckCount--
this.childCheckToggle = false
}
}
addScrollCheck() {
if (this.checkCount < 1 ) {
window.addEventListener('scroll', this.scrollCheck)
this.checkCount++
this.scrollCheckToggle = true
}
}
removeScrollCheck() {
if (this.checkCount === 1) {
window.removeEventListener('scroll', this.scrollCheck)
this.checkCount--
this.scrollCheckToggle = false
}
}
componentWillUnMount() {
this.removeScrollCheck()
}
scrollCheck() {
const scrollPos = this.getBodyScrollTop()
const newVis = scrollPos > this.state.scrollThreshold ? 'onscreen' : 'offscreen'
const curVis = this.state.visibility
newVis !== curVis && (
this.setState({
visibility: scrollPos > this.state.scrollThreshold ? 'onscreen' : 'offscreen'
})
)
}
// Need to do it again on didUpdate to handle nav updates
componentDidUpdate() {
let loc = window.location.pathname
loc = loc.substr(1)
if (loc !== this.state.location) {
this.setState({ location: loc })
}
}
// this class assignment sets all the mobile menu style changes & transitions
onMobileClick() {
// but we only want to do this setting on mobile
if (window.innerWidth < 800) {
this.scrollCheckToggle ? this.removeScrollCheck() : this.addScrollCheck()
this.setState({
displayMobile: this.state.displayMobile === true ? false : true
})
const appContainer = document.getElementsByClassName('app-container')[0]
appContainer.classList.toggle('locked')
}
}
logoClick() {
if (this.state.displayMobile === true) {
this.setState({displayMobile: false})
const appContainer = document.getElementsByClassName('app-container')[0]
appContainer.classList.toggle('locked')
}
}
render() {
const mobileDisplay = this.state.displayMobile === true ? '-active' : '-inactive'
const menu = this.props.menu.map((menuItem, index) => {
const menuLink = PrismicLink.url(menuItem.link, PrismicConfig.linkResolver)
const label = menuItem.label[0].text
let marker
if (typeof this.state.location !== 'undefined') {
// Match label to window location to move indicator dot
marker = label === matchPaths[this.state.location] ? this.state.location : 'inactive'
}
return (
<li key={index} className="top-nav-li">
<Link to={label} className="top-nav-link" onClick={this.onMobileClick}>{label}</Link>
<div className={`top-nav-marker ${marker}`} />
</li>
)
})
return (
<div className="app-container">
<Loader />
<SideNav menu={this.props.menu} location={this.state.location} visibility={this.state.visibility} addListener={this.addChildScroll} removeListener={this.removeChildScroll}/>
<header className='top-nav-container CONSTRAIN'>
<Link to="/" className={`top-nav-logo ${this.state.visibility}`} onClick={this.logoClick} alt="Count Me In logo"/>
<div className="top-nav-mobile-wrapper">
<div className={'top-nav-mobile-title'} onClick={this.onMobileClick} tabIndex="0">
<span className={`top-nav-mobile-title-text${mobileDisplay}`}>Menu</span>
<div className={`top-nav-mobile-icon${mobileDisplay}`} />
</div>
</div>
<nav className={`top-nav-menu-container${mobileDisplay}`}>
<ul className="top-nav-ul">
{menu}
<CTAButton/>
</ul>
</nav>
</header>
<main>
{this.props.children}
</main>
<Footer projects={this.props.projects} footerData={this.props.footerData} />
</div>
)
}
}
Layout.propTypes = {
children: PropTypes.node,
menu: PropTypes.arrayOf(PropTypes.shape({
label: PrismicTypes.PrismicTextTypes,
link: PropTypes.shape({
id: PropTypes.string,
isBroken: PropTypes.bool,
lang: PropTypes.string,
link_type: PropTypes.string,
slug: PropTypes.string,
tags: PropTypes.array,
type: PropTypes.string,
uid: PropTypes.string
})
})),
projects: PropTypes.arrayOf(PropTypes.shape({
data: PropTypes.shape({
project_name: PrismicTypes.PrismicTextTypes,
learn_more_link: PropTypes.shape({
link_type: PropTypes.string,
target: PropTypes.string,
url: PropTypes.string
})
})
})),
footerData: PropTypes.arrayOf(PropTypes.shape({
label: PrismicTypes.PrismicTextTypes,
link: PropTypes.shape({
link_type: PropTypes.string,
target: PropTypes.string,
url: PropTypes.string
})
}))
}
export default Layout
ScrollToTop.js:
import React from 'react'
class ScrollToTop extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0)
}
}
componentDidMount() {
window.scrollTo(0, 0)
}
render() {
return this.props.children
}
}
export default (ScrollToTop)
router.js:
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import ScrollToTop from './app/Utils/ScrollToTop'
import routes from './routes'
export default (({prismicCtx, PRISMIC_UNIVERSAL_DATA}) => {
return (
<ScrollToTop>
<Switch>
{routes(prismicCtx, PRISMIC_UNIVERSAL_DATA).map((route, index) => {
const copyRoute = Object.assign({}, route)
if (copyRoute.render) delete copyRoute.component
return <Route key={`route-${index}`} {...copyRoute} />
})}
</Switch>
</ScrollToTop>
)
})