how to use window.innerWidth within computed property - NuxtJS
I suspect it's because the Nuxt framework is attempting to compute it on the server-side where there is no window object. You need to make sure that it computes it in the browser by checking 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*/ }
}
}
}
}
Regarding the delay, it's a little bit "hacky" but you could return null if window
is not available and simply display once the computed property becomes available. You would still have a delay before it becomes visible, as the root of the problem is that the style is getting applied on the next DOM update.
<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>
Alternatively, as the css is applied on the next DOM update you could use a data property with Vue.$nextTick() (but it is essentially the same thing):
<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>
However, from your question, it appears that you want to apply a responsive layout. The best approach would be to scope
this into your style
tags and use css breakpoints. This would solve the delay problem and decouple your style and logic.
<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>
Btw, just as a side note, use the proper if/else statement in full for computed properties. Using things like if (!process.client) return { /* empty style object */}
sometimes produces some unexpected behaviour in Vue computed properties.
Yung Silva
Updated on June 04, 2022Comments
-
Yung Silva almost 2 years
I'm working on a nuxt.js project, where I need to determine styles within the
computed
propety and apply on adiv
based onscreen size
, as in the example below:basic example
<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>
real example
<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>
making use of the
GridLayout
component on pages.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>
does not work, it generates a error
windows is note defined
inif (this.mq() === 'small')
This works perfectly in
pure Vue.js
but I understand that it does not work on Nuxt.js because it is server side rendering, it makes perfect sense, but how could I make it work?the closest I got was moving the style code into the
mounted
method or wrapping the style code inif (process.client) {...}
, but any of the alternatives would generate a certaindelay
,jump
in content, example:process.client vs without the process.client
jump / delay on the layout when uses process.client condition
how could I make it work without delay? how could I have the screen width before the mounted, default behavior of Vue.js?
-
Yung Silva almost 5 yearsI tested it and it works, but it applies the style with a delay because of
if (process.client)
, same when the style logic is inside themountd
.. I need to have the styles ready before reaching themounted
. I tried this option but the code seems incomplete, I could not get it to work, can you help me? I'm not using store, I'd like to store in a global function, for examplethis.$mq
-
GoldenLab88 almost 5 yearsFrom your question, it appears you just want to have a responsive layout. Your best option is to write the breakpoints into your css directly and not have the style dependent on a computed property... is this not an option for you? There are other/better options but I'm not sure how your project is set up or what your aim is.
-
Yung Silva almost 5 yearsin this case no, because it is a reusable component, soon the stylization will be dynamic and will have a certain level of complexity to the point that only assigning a CSS class would not solve the problem, need to be done with scripting.
-
GoldenLab88 almost 5 yearsMy gut says that this is not the right approach, but I don't know what you're building so I trust you! :) Regarding the delay, I suppose you could just display once the computed property is set on the next tick... it's a little bit "hacky" but it might work. I'll edit my answer
-
Yung Silva almost 5 yearsI'm really happy to be helping me :) I tested, it works again, but still with delay, only different. If you only use
if (process.client)
it displays unstressed content, and applies the style with delay. If useif (process.client)
along withv-show =" css "
it will show the div only when the style is ready, but still with delay compared to other div/DOM. in short, the delay problem continues -
Yung Silva almost 5 yearsI believe the best option is to check
innerWidth
inside thecomputed
with the help of a plugin injected into and this, the context, then simply doif (this.$ mq < 600) return 'mobile'
-
GoldenLab88 almost 5 yearsAh I see, I thought that the div was a container of some sort. I believe the delay is because the css doesn't get applied until the next DOM update cycle, so I don't think injecting a global function will solve the delay problem. Ultimately, I think you'll have to do this via css (I'll edit my answer to include this)... maybe if you can show us your mobile/desktop css objects we can help you with that?
-
Yung Silva almost 5 yearsI updated my question, I'd love for you to take a look, can you imagine some way to solve it without having to delete the
GridLayout
component? -
GoldenLab88 almost 5 yearsYes, that is a little more complex! haha. Well, I quickly copied and pasted that into a new nuxt project and there is no issue with delay. Everything renders on time. I assume your real code is lot more complex and it doesn't get computed until the next tick. As a quick fix, I suppose you could; 1) Add an inline placeholder until v-show is ready; or 2) Don't show any components on the page until the computed innerWidth is ready... maybe pass innerWidth as a prop to GridLayout? Sorry it's a bit hacky and I can't be of more help here... but that's my 2 cents given I can't reproduce the delay.
-
Yung Silva almost 5 yearsWhen i updated my question i put
if (this.mq === 'small')
but the correct one isif (this.mq() === 'small')
, I updated my question again! if you copy and run in a nuxt project, you will see that thewindow is not defined
error. if you wrap everything inside anif (process.client)
you will see the delay regardless of the complexity or size of the page. -
GoldenLab88 almost 5 yearsOk, I wrapped everything in process.client. I'm resizing my window width small, medium etc. but I still don't get a delay.
-
Yung Silva almost 5 yearsthe delay is only on the first load of the component, do a simple test with process.client and another test without process.client, will observe that when it has the condition (process.cliente) it takes a while to apply the styles
-
Yung Silva almost 5 yearsto observe more clearly, keep pressing
F5
orcommand + R
if it is on the mac -
GoldenLab88 almost 5 yearsI'm doing exactly that... honestly I have no delay. I'm using Nuxt v2.8.1. I've disabled cache.
-
Yung Silva almost 5 yearslook this, can you upload a video too? I'd like to see the behavior you're getting, I know it sounds like little, but this little delay in big things can make the site very ugly.
-
Yung Silva almost 5 years
-
GoldenLab88 almost 5 yearsAh yes, I can reproduce it now! The problem was I wasn't doing it in 'infinty', apologies. Yes, this is definitely because it doesn't update until the next DOM update cycle. So with this implementation a quick fix would be to apply v-show on Vue.$nextTick or by waiting until non-falsy value for css like in my answer. Both will produce a tiny delay but at least the user won't see the layout jump.
-
Yung Silva almost 5 yearscould you show me the nextTick sweat in this situation?
-
GoldenLab88 almost 5 yearssure, I've updated my answer. But it essentially the same as css != null solution before. I'm just writing it directly on my phone so there might be mistakes.