Странное императивное поведение маршрутизации onScroll в Next.js (только рендеринг на сервере)

Я пытаюсь использовать API императивной маршрутизации Next.js в обработчике прокрутки для навигации по страницам. Когда я прикрепляюсь к окну, оно почти идеально, за небольшим исключением, что при сбросе положения прокрутки появляется цветная «вспышка» (необходимо продолжать прокручивать вверх от нижней части нового маршрута, если перейти к верхней части предыдущей страницы, и переход цвета должен быть бесшовным, что кажется невозможным из-за миллисекунд браузера с scrollTop в 0). Код:

export default class ScrollOMatic extends Component {
  constructor(props) {
    super(props)
    this.state = {
      prevRoute: '',
      nextRoute: '',
      isEndOfScroll: false
    }
    binder(this, ['handleScroll'])
  }
  componentWillMount() {
    if (typeof window !== 'undefined') {
      console.log(window)
    }
  }
  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll)
    const {
      prevRoute,
      nextRoute
    } = this.props.routeData
    this.setState({
      prevRoute,
      nextRoute
    })
    Router.prefetch('us')
    Router.prefetch('work')
    Router.prefetch('services')
    Router.prefetch('converse')
  }
  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll)
  }
  handleScroll(e) {
    e.preventDefault()
    let {
      scrollTop,
      scrollHeight
    } = e.srcElement.scrollingElement
    scrollHeight = scrollHeight / 2
    const scrollTiplier = scrollTop / scrollHeight
    if (scrollTop === 0) {
      Router.push(this.state.prevRoute)
      window.scrollTo(0, scrollHeight - 1, {
        duration: 0
      })
    }
    if (scrollTiplier === 1) {
      Router.push(this.state.nextRoute)
      window.scrollTo(0, 1, {
        duration: 0
      })
    }
  }
  render() {
    return ( 
      <div className = 'scroll-o-matic' >{ this.props.children }</div>
    )
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Итак, я использую контейнер с собственным поведением прокрутки, вдохновленным реакцией горизонтальной прокрутки, которая кажется многообещающей. Но с этим новым кодом происходит странная вещь. Новый маршрут на мгновение отображается на стороне клиента, и страница обновляется по мере получения нового маршрута с сервера. См. gif и код: введите здесь описание изображения

// inspired by react-horizontal-scroll
import { Motion, spring, presets } from 'react-motion'
import raf from 'raf'
import Router from 'next/router'
import { fadeColor, binder } from '../../lib/_utils'
import { setScrollState } from '../../lib/_navRules'

export default class ScrollOMatic extends Component {
  constructor (props) {
    super(props)
    this.state = {
      prevRoute: '',
      nextRoute: '',
      animValues: 0
    }
    binder(this, ['handleWheel', 'resetMin', 'resetMax', 'navigator', 'canIscroll', 'getLayoutData'])
  }
  componentDidMount () {
    const { prevRoute, nextRoute } = this.props.routeData
    this.setState({
      prevRoute,
      nextRoute
    })

    const prevRouteName = prevRoute === '/' ? 'index' : prevRoute.replace('/', '')
    const nextRouteName = prevRoute === '/' ? 'index' : nextRoute.replace('/', '')
    Router.prefetch(prevRouteName)
    Router.prefetch(nextRouteName)
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.children !== nextProps.children) { this.resetMin() }
  }

  shouldComponentUpdate (nextProps, nextState) {
    if (true &&
      this.calculate.timer !== void 0 &&
      this.props.children === nextProps.children &&
      this.state.animValues === nextState.animValues) {
      return false
    }
    if (true &&
      this.props.children === nextProps.children &&
      this.canIscroll() === false) {
      return false
    }
    return true
  }

  componentDidUpdate () { this.calculate() }

  getLayoutData () {
    const scrollOMatic = DOM.findDOMNode(this.scrollOMatic)
    const scrollTray = DOM.findDOMNode(this.scrollTray)
    const max = scrollOMatic.scrollHeight
    const win = scrollOMatic.offsetHeight
    const currentVal = this.state.animValues
    const bounds = -(max - win)
    const trayTop = scrollTray.offsetTop
    const trayOffsetHeight = scrollTray.offsetHeight
    const trayScrollHeight = scrollTray.scrollHeight
    const scrollOMaticRect = scrollOMatic.getBoundingClientRect()
    const scrollOMaticTop = scrollOMaticRect.top
    const scrollOMaticHeight = scrollOMaticRect.height
    const scrollOMaticOffsetHeight = scrollOMatic.offsetHeight
    return {
      currentVal,
      bounds,
      scrollTray,
      trayTop,
      trayOffsetHeight,
      trayScrollHeight,
      scrollOMatic,
      scrollOMaticTop,
      scrollOMaticHeight,
      scrollOMaticOffsetHeight,
      scrollOMaticRect
    }
  }

  calculate () {
    const layout = this.getLayoutData()
    clearTimeout(this.calculate.timer)
    this.calculate.timer = setTimeout(() => {
      const max = layout.trayScrollHeight
      const win = layout.scrollOMaticOffsetHeight
      const currentVal = this.state.animValues
      const bounds = -(max - win)
      if (currentVal >= 1) {
        this.resetMin()
      } else if (currentVal <= bounds) {
        const x = bounds + 1
        this.resetMax(x)
      }
    })
  }

  resetMin () { this.setState({ animValues: 0 }) }
  resetMax (x) { this.setState({ animValues: x }) }

  canIscroll () {
    const layout = this.getLayoutData()
    return layout.trayOffsetTop < layout.scrollOMaticTop ||
      layout.trayOffsetHeight > layout.scrollOMaticHeight
  }

  handleWheel (e) {
    e.preventDefault()
    const rawData = e.deltaY ? e.deltaY : e.deltaX
    const mouseY = Math.floor(rawData)
    const animationVal = this.state.animValues
    const newAnimationVal = (animationVal + mouseY)
    const newAnimationValNeg = (animationVal - mouseY)

    if (!this.canIscroll()) return

    const layout = this.getLayoutData()
    const { currentVal, scrollOMaticHeight, trayScrollHeight } = layout
    const isEndOfPage = -(currentVal - scrollOMaticHeight) + 1 === trayScrollHeight

    this.navigator()

    const scrolling = () => {
      this.state.scrollInverted
        ? this.setState({ animValues: newAnimationValNeg })
        : this.setState({ animValues: newAnimationVal })
    }
    raf(scrolling)
  }
  
  navigator () {
    const layout = this.getLayoutData()
    const { currentVal, scrollOMaticHeight, trayScrollHeight } = layout
    const shouldBeNextRoute = -(currentVal - scrollOMaticHeight) + 1 >= trayScrollHeight
    // const shouldBePrevRoute = this.state.animValues < 0

    if (shouldBeNextRoute) {
      Router.push(this.state.nextRoute)
    }
    // if (shouldBePrevRoute) {
    //   Router.push(this.state.prevRoute)      
    // }
  }

  render () {
    const springConfig = presets.noWobble
    return (
      <div style={{ position: 'relative', width: '100vw', height: '100vh' }} className='scroll-o-matic' onWheel={this.handleWheel}
        ref={scrollOMatic => { this.scrollOMatic = scrollOMatic }}>
        <Motion style={{ z: spring(this.state.animValues, springConfig) }}>
          { ({ z }) => (
            <div className='scroll-tray' ref={(scrollTray) => { this.scrollTray = scrollTray }}
              style={{
                height: '300vh',
                width: '100vw',
                // top: '-100vh',
                transform: `translate3d(0,${z.toFixed(3)}px,0)`,
                willChange: 'transform',
                display: 'inline-flex',
                position: 'absolute'
              }}>
              { this.props.children }
            </div>
          )}
        </Motion>
        <style jsx>{`
          .scroll-o-matic {
            background-color: ${this.state.currentColor};
          }
        `}</style>
      </div>
    )
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

код сервера:

const express = require('express')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const port = process.env.PORT || 3000
const handle = app.getRequestHandler()

app.prepare()
  .then(() => {
    const server = express()
    server.use('/static', express.static('static'))

    server.get('/', (req, res) => {
      return app.render(req, res, '/', req.query)
    })
    server.get('/us', (req, res) => {
      return app.render(req, res, '/us', req.query)
    })
    server.get('/work', (req, res) => {
      return app.render(req, res, '/work', req.query)
    })
    server.get('/services', (req, res) => {
      return app.render(req, res, '/services', req.query)
    })
    server.get('/converse', (req, res) => {
      return app.render(req, res, '/converse', req.query)
    })

    server.get('*', (req, res) => {
      return handle(req, res, '/', req.query)
    })

    server.listen(port, (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
  })

Может быть, я не понимаю «предварительную выборку» Next, но, похоже, она ничего не выполняет. Я в основном смущен, почему происходит загрузка страницы на стороне сервера. Даже при использовании обработчика запросов с подстановочными знаками в экспрессе страница перезагружается (но перенаправляет на себя?), Так что я не думаю, что это проблема с конфигурацией сервера.

Чего мне не хватает, оракулов интернета?


person Will Meier    schedule 07.12.2017    source источник


Ответы (3)


Параметры предварительной загрузки загружают код JS страницы в фоновом режиме, поэтому, когда вы хотите изменить страницу, она уже загружена, но не более того, если вы ожидаете, что она предварительно загрузит уже обработанный HTML или данные, которые вам нужны для ручной реализации этого .

person Sergio Xalambrí    schedule 15.12.2017

Для всех, у кого может быть такая же проблема, какой бы неясной она ни была, проблема оказалась связанной с методами рендеринга Next «Styled JSX».

По-видимому, даже при загрузке страницы на стороне клиента есть полсекунды или около того момента, когда Styled JSX по какой-то причине должен загрузиться. Чего вы обычно не замечаете, если только внутри вашего тега <style jsx>{``}<style> не находится что-то столь драматичное, как фоновый цвет всего приложения. Помещение всех стилей в обычный старый html-стиль attr решило «вспышку».

person Will Meier    schedule 25.01.2018

событие прокрутки не срабатывало. так как мое тело прокручивалось вместо documentElement.

Я только что удалил height: 100% из моего тега body, а затем началось событие прокрутки.

//in your CSS don't set height to percent
div{
    height: 100%
}
person Maduekwe Pedro    schedule 16.09.2020