I need to have a reactive global variable in Vue. The variable is simply a boolean that tells me whether the user is on a mobile device. I have tried so many things but this is the last thing I tried:
Vue.prototype.$testIsMobile = false
const mobileMediaMatch = window.matchMedia('(max-width: 768px)')
Vue.prototype.$testIsMobile = mobileMediaMatch.matches
window.addEventListener('resize', function () {
console.log("resizeeeee: " + mobileMediaMatch.matches)
Vue.prototype.$testIsMobile = mobileMediaMatch.matches
}, true)
Now this will get triggered when I reszie my screen because I can see the text resizeeeee
getting logged repeatedly to the console. The problem is that when I use the variable $testIsMobile
in other components, the variable is not reactive. It does not re-render the page accordingly until I refresh the page manually. How can I make this variable fully reactive so that any component can use it and it contains the correct value?
Here is an example of how I use it in a component:
<div>--{{$testIsMobile}}--</div>
Although there's quite a bit of overlap,
If you want to detect touch devices, I suggest isMobile (or similar). It's not reactive, because (normally) the device does not change. (e.g: if emulating - therefore changing device on the fly - you need to reload the page to reapply detection.
Vue 2 implementation:
Vue.use({
install(v) {
v.prototype.$isMobile = isMobile
}
})
new Vue({ el: '#app' })
<script src="https://unpkg.com/vue@2.7.10/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ismobilejs@1/dist/isMobile.min.js"></script>
<div id="app">
<pre v-text="JSON.stringify($isMobile, null, 2)"></pre>
</div>
To detect if a media matches in real time, you could roll your own, but it's already done: vue-component-media-queries (for Vue 2):
const { MatchMedia, MediaQueryProvider } = VueComponentMediaQueries;
const baseQueries = [
{ xs: { max: 539 } },
{ sm: { min: 540, max: 767 } },
{ md: { min: 768, max: 991 } },
{ lg: { min: 992, max: 1199 } },
{ xl: { min: 1200, max: 1499 } },
{ xxl: { min: 1500 } },
];
new Vue({
el: "#app",
components: {
MatchMedia,
MediaQueryProvider,
},
data: () => ({
queries: Object.assign(
{
portrait: "(orientation: portrait)",
landscape: "(orientation: landscape)",
},
...Object.entries(Object.assign({}, ...baseQueries)).map(
([key, val]) => ({
[key]: [
val.min ? `(min-width: ${val.min}px)` : "",
val.max ? `(max-width: ${val.max}.99px)` : "",
]
.filter((o) => o)
.join(" and "),
...(val.min && val.max
? {
["min-" + key]: `(min-width: ${val.min}px)`,
["max-" + key]: `(max-width: ${val.max}.99px)`,
}
: {}),
})
)
),
}),
});
th {
text-align: left;
}
td:last-child {
text-align: center;
}
table {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
<script src="https://unpkg.com/vue@2.7.10/dist/vue.min.js"></script>
<script src="https://unpkg.com/vue-component-media-queries@1.0.0/dist/index.js"></script>
<div id="app">
<media-query-provider :queries="queries">
<match-media #default="matches">
<table>
<thead>
<tr>
<th>Key</th>
<th>Query</th>
<th>Matches?</th>
</tr>
</thead>
<tbody>
<tr
v-for="(value, key) in matches"
:key="key"
:style="{color: value ? '#275': '#930'}"
>
<td v-text="key"></td>
<td>
<code v-text="queries[key]"></code>
</td>
<td>{{ value ? '✅' : '❌' }}</td>
</tr>
</tbody>
</table>
</match-media>
<hr />
<!-- Simpler syntax (but the same thing, in essence): -->
<match-media v-slot="{ md }">
<div v-if="md">md</div>
<div v-else>not md</div>
</match-media>
</media-query-provider>
</div>
Look at the simpler syntax at the bottom.
Define as many queries as you want. They're all be available on <MediaQuery />
's v-slot
(e.g: #default="{ someQuery }"
)