【发布时间】:2020-02-28 03:01:16
【问题描述】:
我继承了一个 react/node/prismic 应用程序,它有一个 ScrollToTop.js 文件,以确保页面加载在顶部,当从我们的主导航菜单访问时它们会这样做。
此应用程序还有一个小侧导航,在您向下滚动之前它会一直隐藏(由 tabIndex 控制)。
错误:当您从侧面导航点击链接时,无论您在打开侧面导航时滚动了多远,结果页面都会出现。相反,我希望这些从顶部开始。
我们有一个用于整体布局的 Layout.js 文件,以及用于那个小侧导航的特定 SideNav.js。我是 react/javascript 的新手,我无法弄清楚如何(a)将 ScrollToTop 逻辑应用于这些 sidenav 链接或(b)为这种特殊情况添加额外的 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>
)
})
【问题讨论】:
-
我猜 ScrollToTop 没有更新也没有得到
location。我认为location也是一个对象,所以你需要比较location.pathname这是一个字符串。但是你需要使用withRouter看这篇帖子stackoverflow.com/a/58588297/7015138或者reacttraining.com/react-router/web/api/withRouter -
谢谢@Vl4dimyr!是的,这是另一个问题:这个分支用于更新一堆包版本,随着这些更新,我们开始得到“你不应该在
之外使用 或 withRouter()”。我删除了'withRouter',我正在尝试看看我是否可以通过其他方式让它工作...... -
我认为
Route的直接子组件或通过Route显示的组件将自动注入location,只需注销提供给Route的组件中的props可能这将起作用。
标签: javascript reactjs scrollto