Next.js 中奇怪的命令式 onScroll 路由行为(仅在服务器上呈现)

Posted

技术标签:

【中文标题】Next.js 中奇怪的命令式 onScroll 路由行为(仅在服务器上呈现)【英文标题】:Strange imperative onScroll routing behavior in Next.js (only rendering on server) 【发布时间】:2018-05-21 22:59:02 【问题描述】:

我正在尝试在滚动处理程序中使用 Next.js 命令式路由 api 进行页面导航。当我附加到窗口时,它几乎是完美的,除了滚动位置重置时有一个颜色“闪烁”(如果导航到上一页的顶部,需要继续从新路线的底部向上滚动,并且颜色过渡需要是无缝的,这似乎是不可能的,因为浏览器的毫秒数与 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>

所以,我使用了一个具有自己滚动行为的容器,灵感来自 react-horizo​​ntal-scroll,这似乎很有希望。但是这个新代码发生了一件奇怪的事情。新路由会在客户端呈现片刻,当从服务器获取新路由时页面会刷新。见 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 的“预取”,但它似乎绝对没有预取任何东西。我主要很困惑为什么会发生服务器端页面加载。即使在 express 中使用通配符请求处理程序,页面也会重新加载(但会重定向到自身?),所以我认为这不是服务器配置的问题。

我错过了什么,互联网的神谕?

【问题讨论】:

【参考方案1】:

滚动事件没有触发。因为我的身体在滚动而不是 documentElement。

我刚刚从我的 body 标签中删除了 height: 100%,然后滚动事件开始触发。

//in your CSS don't set height to percent
div
    height: 100%

【讨论】:

【参考方案2】:

对于任何可能有同样问题的人来说,虽然它很模糊,但问题最终与 Next 的“样式化 JSX”渲染方法有关。

显然,即使在客户端页面加载中,出于某种原因,Styled JSX 也需要半秒左右的时间来加载。您通常不会注意到这一点,除非像整个应用程序的背景颜色这样引人注目的东西在您的 &lt;style jsx&gt;``&lt;style&gt; 标记内。将所有样式放在常规的旧 html 样式 attr 中解决了“flash”问题。

【讨论】:

【参考方案3】:

预取选项是在后台下载页面的 JS 代码,因此当您想要进行页面更改时,它已经下载,但仅此而已,如果您希望它预取已经呈现的 HTML 或您需要的数据手动实现。

【讨论】:

以上是关于Next.js 中奇怪的命令式 onScroll 路由行为(仅在服务器上呈现)的主要内容,如果未能解决你的问题,请参考以下文章

Firefox中奇怪的textarea行为:添加空格后文本中断

类中奇怪的异步/等待行为

UIImageView 中奇怪的对齐行为

列表理解中奇怪的 lambda 行为

Oracle中奇怪的SQL执行结果[关闭]

倒计时项目中奇怪的 setInterval 问题