При нажатии на ссылку с ориентированной боковой навигацией открывается прокручиваемая страница вниз - PullRequest
0 голосов
/ 01 ноября 2019

Я унаследовал приложение реагировать / узел / призма, в котором есть файл 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>
  )
})

1 Ответ

0 голосов
/ 04 ноября 2019

Рассматривая различные этапы жизненного цикла реакции, я добавил то же поведение scrollTo для componentWillUpdate (в ScrollToTop.js), и эти страницы снова загружаются из этого меню вверху!

    componentWillUpdate() {
    window.scrollTo(0, 0)
}
...