【问题标题】:how to use window.innerWidth within computed property - NuxtJS如何在计算属性中使用 window.innerWidth - NuxtJS
【发布时间】:2019-11-27 19:32:29
【问题描述】:

我正在开发一个 nuxt.js 项目,我需要确定 computed 属性中的样式并基于 screen size 应用到 div,如下例所示:

基本示例

<template>
  <div :style="css"></div>
</template>

<script>
export default {
  computed: {
    css () {
      let width = window.innerWidth

      // ... mobile { ... styles }
      // ... desktop { ... styles }

      // ... if width is less than 700, return mobile
      // ... if width greater than 700, return desktop
    }
  }
}
</script>

真实例子

<template>
  <div :style="css">
    <slot />
  </div>
</template>

<script>
export default {
  props: {
    columns: String,
    rows: String,
    areas: String,
    gap: String,
    columnGap: String,
    rowGap: String,
    horizontalAlign: String,
    verticalAlign: String,
    small: Object,
    medium: Object,
    large: Object
  },
  computed: {
    css () {
      let small, medium, large, infinty

      large = this.generateGridStyles(this.large)
      medium = this.generateGridStyles(this.medium)
      small = this.generateGridStyles(this.small)
      infinty = this.generateGridStyles()

      if (this.mq() === 'small' && this.small) return Object.assign(infinty, small)

      if (this.mq() === 'medium' && this.medium) return Object.assign(infinty, medium)

      if (this.mq() === 'large' && this.large) return Object.assign(infinty, large)

      if (this.mq() === 'infinty') return infinty

    }
  },
  methods: {
    generateGridStyles (options) {
      return {
        'grid-template-columns': (options !== undefined) ? options.columns : this.columns,
        'grid-template-rows': (options !== undefined) ? options.rows : this.rows,
        'grid-template-areas': (options !== undefined) ? options.areas : this.areas,
        'grid-gap': (options !== undefined) ? options.gap : this.gap,
        'grid-column-gap': (options !== undefined) ? options.columnGap : this.columnGap,
        'grid-row-gap': (options !== undefined) ? options.rowGap : this.rowGap,
        'vertical-align': (options !== undefined) ? options.verticalAlign : this.verticalAlign,
        'horizontal-align': (options !== undefined) ? options.horizontalAlign : this.horizontalAlign,
      }
    },
    mq () {
      let width = window.innerWidth

      if (width < 600) return 'small'
      if (width > 600 && width < 992) return 'medium'
      if (width > 992 && width < 1200) return 'large'
      if (width > 1200) return 'infinty'
    }
  }
}
</script>

<style lang="scss" scoped>
div {
  display: grid;
}
</style>

利用 pages.vue 上的 GridLayout 组件

<template>
  <GridLayout
    columns="1fr 1fr 1fr 1fr"
    rows="auto"
    gap="10px"
    verital-align="center"
    :small="{
      columns: '1fr',
      rows: 'auto auto auto auto',
    }"
  >
    <h1>1</h1>
    <h1>2</h1>
    <h1>3</h1>
    <h1>3</h1>
  </GridLayout>
</template>

<script>
import { GridLayout } from '@/components/bosons'

export default {
  layout: 'blank',
  components: {
    GridLayout
  },
}
</script>


<style lang="scss" scoped>
h1 {
  background: #000;
  color: #fff;
}
</style>

不起作用,它会在if (this.mq() === 'small') 中生成错误windows is note defined

这在pure Vue.js 中完美运行,但我知道它在 Nuxt.js 上不起作用,因为它是服务器端渲染,它非常有意义,但我怎样才能让它工作?

我得到的最接近的是将样式代码移动到 mounted 方法中或将样式代码包装在 if (process.client) {...} 中,但任何替代方案都会在内容中生成某个 delayjump,例如:

process.client vs without the process.client

jump / delay on the layout when uses process.client condition

我怎样才能让它毫不拖延地工作?在安装 Vue.js 的默认行为之前,我怎么能拥有屏幕宽度?

【问题讨论】:

    标签: javascript vue.js vuejs2 nuxt.js


    【解决方案1】:

    我怀疑这是因为 Nuxt 框架试图在没有窗口对象的服务器端计算它。您需要通过检查process.client 来确保它在浏览器中计算它:

    export default {
      computed: {
        css () {
          if (process.client) {
            let width = window.innerWidth
    
            // ... mobile { ... styles }
            // ... desktop { ... styles }
    
            // ... if width is less than 700, return mobile
            // ... if width greater than 700, return desktop
          } else {
            return { /*empty style object*/ }
          }
        }
      }
    }
    

    关于延迟,它有点“hacky”,但如果 window 不可用,您可以返回 null 并在计算属性可用时简单地显示。在它变得可见之前你仍然会有延迟,因为问题的根源在于样式会在下一次 DOM 更新时应用。

    <template>
        <div :style="css" v-show="css">
        </div>
    </template>
    
    <script>
    export default {
      computed: {
        css () {
          if (process.client) {
            let width = window.innerWidth
    
            // ... mobile { ... styles }
            // ... desktop { ... styles }
    
            // ... if width is less than 700, return mobile
            // ... if width greater than 700, return desktop
          } else {
            return null
          }
        }
      }
    }
    </script>
    

    或者,当 css 应用于下一次 DOM 更新时,您可以将数据属性与 Vue.$nextTick() 一起使用(但本质上是相同的):

    <template>
        <div :style="css" v-show="reveal">
        </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          reveal: false
        }
      },
      computed: {
        css () {
          if (process.client) {
            let width = window.innerWidth
    
            // ... mobile { ... styles }
            // ... desktop { ... styles }
    
            // ... if width is less than 700, return mobile
            // ... if width greater than 700, return desktop
          } else {
            return { /*empty style object*/ }
          }
        }
      },
      mounted() {
        this.$nextTick(() => {
          this.reveal = true
        });
      }
    }
    </script>
    

    但是,从您的问题来看,您似乎想要应用响应式布局。最好的方法是将 scope 这个放到你的 style 标签中并使用 css 断点。这将解决延迟问题并解耦您的风格和逻辑。

    <template>
        <div class="my-responsive-component">
        </div>
    </template>
    
    <script>
    export default {
      computed: { /* nothing to see here! */ }
    }
    </script>
    
    <style lang="css" scoped>
    .my-responsive-component {
        height: 100px;
        width: 100px;
    }
    
    @media only screen and (max-width: 700px) {
        .my-responsive-component { background: yellow; }
    }
    
    @media only screen and (min-width: 700px) {
        .my-responsive-component { background: cyan; }
    }
    </style>
    

    顺便说一句,对于计算属性,请完整使用正确的 if/else 语句。使用 if (!process.client) return { /* empty style object */} 之类的东西有时会在 Vue 计算属性中产生一些意想不到的行为。

    【讨论】:

    • 我对其进行了测试并且它可以工作,但是由于if (process.client),它会延迟应用样式,当样式逻辑在mountd 内时也是如此。我需要先准备好样式到达mounted。我试过this option,但代码似乎不完整,我无法让它工作,你能帮我吗?我没有使用 store,我想存储在全局函数中,例如 this.$mq
    • 从您的问题来看,您似乎只想拥有一个响应式布局。您最好的选择是将断点直接写入您的 css 并且不具有依赖于计算属性的样式...这不是您的选择吗?还有其他/更好的选择,但我不确定您的项目是如何设置的或您的目标是什么。
    • 在这种情况下不,因为它是一个可重用的组件,很快样式化将是动态的,并且将具有一定程度的复杂性,以至于仅分配 CSS 类无法解决问题,需要用脚本来完成。
    • 我的直觉告诉我这不是正确的方法,但我不知道你在做什么,所以我相信你! :) 关于延迟,我想你可以在下一个刻度设置计算属性后显示......这有点“hacky”但它可能会起作用。我会编辑我的答案
    • 我真的很高兴能帮助我 :) 我测试过,它又可以工作了,但仍然有延迟,只是不同。如果您只使用if (process.client),它会显示未重读的内容,并延迟应用样式。如果将if (process.client)v-show =" css " 一起使用,它将仅在样式准备好时显示div,但与其他div/DOM 相比仍然存在延迟。总之,延迟问题还在继续
    【解决方案2】:

    这是由于服务器渲染而发生的一个老问题,如以下 github 上的问题所述。

    .vue 文件

    <script>
    if (process.browser) {
      require('aframe')
    }
    export default {
    
    }
    </script>
    

    nuxt.config.js

      build: {
        vendor: ['aframe']
      }
    

    参考:
    https://github.com/nuxt/nuxt.js/issues/30#issuecomment-264348589
    https://nuxtjs.org/faq/window-document-undefined/

    【讨论】:

      猜你喜欢
      • 2018-11-02
      • 1970-01-01
      • 2019-08-24
      • 2019-11-12
      • 2021-03-04
      • 2022-01-14
      • 1970-01-01
      • 2019-12-24
      • 2017-09-17
      相关资源
      最近更新 更多