首页 > 解决方案 > 如何在计算属性中使用 window.innerWidth - NuxtJS

问题描述

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

基本例子

<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>

使用GridLayoutpages.vue 上的组件

<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>

不起作用,它会windows is note definedif (this.mq() === 'small')

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

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

process.client 与没有 process.client

使用 process.client 条件时在布局上跳转/延迟

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

标签: javascriptvue.jsvuejs2nuxt.js

解决方案


我怀疑这是因为 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 计算属性中产生一些意想不到的行为。


推荐阅读