Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

v-if - condition based on screen size

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 when using window.innerWidth

like image 844
Dart Avatar asked Sep 14 '25 21:09

Dart


2 Answers

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>
like image 108
MrSpt Avatar answered Sep 16 '25 11:09

MrSpt


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>
like image 45
Alexander Nenashev Avatar answered Sep 16 '25 11:09

Alexander Nenashev