im trying to use v-if to render 2 buttons in different containers on page, depending on screen size is there way how to change parent of items in @media or way how to use screen size in v-if condition?
i tried
<div v-if="window.innerWidth < 860">
<!-- Some code -->
</div>
but it throws me "TypeError: Cannot read properties of undefined (reading 'innerWidth')"
(when i use just media query, there appears bug with radio btns, when they needs 2 clicks to get :checked styles) when i tried @media, in fact there was 4 buttons, that binded to 2 variables
<!-- button code -->
<label>
<input type="radio" name="month-season" id="month" value="month" v-model="monthSeason">
<null-button>
<img src="../assets/green-smile.png" alt="#" id="month-icon">
text
</null-button>
</label>
<!-- null-button -->
<div class="parent">
<div class="container">
<slot></slot>
</div>
<div>
<!-- styles for button -->
<style>
.month-season {
text-align: right;
label {
.container {
color: rgba(45, 206, 137, 1);
background: white;
img {
margin: 0 4px 0 0;
}
}
input:checked + .parent .container {
background: rgba(45, 206, 137, 1);
color: white;
img {
filter: brightness(1000%);
}
}
input {
appearance: none;
}
}
}
</style>
Error message when I use window.innerWidth
You can use useWindowSize (or useElementSize).
<script>
import { useWindowSize } from '@vueuse/core'
const { width } = useWindowSize()
</script>
<template>
<div v-if="width < 860">
<!-- Some code -->
</div>
<div v-if="width >= 860">
<!-- Some other code -->
</div>
</template>
Vue3 SFC Playground
All JS expression that you use in a template are executed in a so called template context. It contains all stuff declared in a component and does NOT contains JS globals like window
and its props like alert
. So you should use window
outside a template or to add to app.config.globalProperties
.
But adding window
to the context doesn't solve you problem because JS expressions using it aren't reactive and your code would work only on initial component rendering.
So you should go reactive here. A common Vue3 approach would be using composables here.
We could use 2 different approaches here. Either using matchMedia
or window.innerWidth
as you tried.
With matchMedia
you can go with any CSS media query you like.
So in the both cases you listen for appropriate events and update ref
that is used further in a template.
If you listen on window
don't forget to remove the listener when the component is unmounted otherwise you will have problems of ghost event handlers.
So your matchMedia
composable:
import {ref} from 'vue';
export function useMatchMedia(query){
const match = window.matchMedia(query);
const isMatching = ref(match.matches);
match.addEventListener('change', e => isMatching.value = e.matches);
return isMatching;
}
Your window.innerWidth
composable:
import {ref, onUnmounted} from 'vue';
export function useInnerWidth(){
const width = ref(window.innerWidth);
const syncWidth = () => width.value = window.innerWidth;
window.addEventListener('resize', syncWidth);
onUnmounted(() => window.removeEventListener('resize', syncWidth));
return width;
}
And the usage:
<script setup>
import { useMatchMedia } from './useMatchMedia';
const smallScreen = useMatchMedia('(max-width: 859px)');
import { useInnerWidth } from './useInnerWidth';
const innerWidth = useInnerWidth();
</script>
<template>
<h1 v-if="smallScreen">I'm a smaller screen</h1>
<h1 v-else>I'm a bigger screen</h1>
<p v-if="innerWidth < 860">Smaller screen width: {{innerWidth}}</p>
<p v-else>Bigger screen width: {{innerWidth}}</p>
</template>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With