iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0
Modern Web

前端蛇行撞牆記系列 第 26

Day26 前端蛇行撞牆記 - 在 Vue 3 偵測螢幕尺寸變化 / VueUse及原生JS

  • 分享至 

  • xImage
  •  

前言

在原生裏面,要偵測螢幕尺寸可以使用 window,在 Vue 的 <script> 裡面也可以使用 window 的屬性 innerWidth, innerHeight

但如果在 Vue檔的 <template> 寫上 {{ window }} 會顯示: "[object Window]"<template> 是拿不到 window 的,除非在 main.js 裏面新增:

app.config.globalProperties.window = window;

就可以在 <template> 使用 window 的屬性了。

然而這個只能拿到當下的螢幕尺寸大小,如果有人在那邊拉來拉去(就是你們這些前端仔!),讓網頁的寬度改變的話,window 是無法偵測到的。

如果今天要在螢幕在 576px 以上讓 element 的 class 新增一個改變容器置中的功能,該怎麼做到 即時偵測螢幕寬度 呢?

以下會介紹使用 VueUse 跟 使用原生的用法:

VueUse

  1. 主要是以 composition API 做成的 Vue函式工具庫,在 Vue2, Vue3 都可以使用。
  2. 而且只會打包有使用到的程式碼。
  3. 大多數的函式都會返回一個ref()

直接用 npm install 安裝到專案中:

npm i @vueuse/core

裏面有非常多的函式可以被 use,找到一個最符合要偵測螢幕尺寸大小的。

useWindowSize

安裝之後,就可以直接使用 width, height 了。

import { useWindowSize } from '@vueuse/core'

const { width, height } = useWindowSize()

接著再使用 Vue 的 watch 就可以及時偵測目前螢幕變化的大小~

const isCentered = ref(null);

watch(width, (newWidth, oldWidth) => {
  if (newWidth >= 576) {
    isCentered.value = true;
  } else {
    isCentered.value = false;
  }
});

於是在 <template> 也可以使用這個響應式變化了!
在 576px 以上會自動新增 modal-dialog-centered class名稱上去,其他 size 則自動沒有。

<div
  class="modal-dialog modal-fullscreen-sm-down modal-lg"
  :class="{ 'modal-dialog-centered': isCentered }"
>

好奇去看了一下 useWindowSize的原始碼,發現 useWindowSize 吃的是 resize 事件的呢

useEventListener('resize', update, { passive: true })

他只會在有改變螢幕大小時觸發,如果在一進去頁面就不會有任何改變,如果還要為一開始進來偵測螢幕大小給不同狀態的話,還是先得用 window 去抓當下的螢幕尺寸,然後剩下就給調整大小時,才有 watch 的改變!

Vue 使用原生偵測

在寫鐵人賽的當下我只會使用 computed, watch,不過後來學習到 watchEffect 發現更適合。以下會介紹兩種不同的寫法。

computed

雖然我使用了 VueUse 做到這件事,但因為當初沒有想到可以使用 resize 事件來偵測,所以又用原生試試看,發現也可以啊 XD

  1. onMounted() 裡面放一個 window.addEventListener('resize')
  2. 在外面放一個響應式變數 let windowWidth = ref(window.innerWidth);
  3. 在 listener 一直對 windowWidth 做重新賦值的動作
  4. 外面在放一個 computed 的變數去 return windowWidth,這樣只要 windowWidth 改變,這個 computed 就會去計算
  5. 最後在 <template> 放上 computed 的變數就好了!
const resizeFont = ref("");
const isLess500 = ref(false);
const isBigger500 = ref(false);

let windowWidth = ref(window.innerWidth);

const resize = computed(() => {
  return windowWidth;
});

onMounted(() => {
  window.addEventListener("resize", function () {
    windowWidth.value = window.innerWidth;
    if (windowWidth.value < 500) {
      resizeFont.value = "less than 500px";
      isLess500.value = true;
      isBigger500.value = false;
    } else {
      resizeFont.value = "greater than 500px";
      isBigger500.value = true;
      isLess500.value = false;
    }
  });
});

再試著在resize的時候去改文字顏色也有成功

<span>window.innerWidth : </span>
<span>{{ resize }}</span>
<p :class="{ changeResizeColor: isLess500, blue: isBigger500 }">
    {{ resizeFont }}
</p>

成果:

watchEffect

因為其實我們需要第一次的值,這樣使用 watchEffect 的話,第一次就算沒有 resize 但還是會有初始值

watchEffect 沒有 oldValue, newValue 直接給一個 callback , watch 會自動建立 callback 裡面的變數來產生依賴。

  1. 我們在組件被創建起來的時候去監聽,在onMounted裡面監聽 window 的 resize 事件。
  2. 在裡面把目前的 window.innerWidth 賦值給響應式變數 windowWidth
  3. 為了要讓他去改變其他變數,再用 watchEffect 去改變 result1 的值。
const result1 = ref("");

onMounted(() => {
  window.addEventListener("resize", function () {
    windowWidth.value = window.innerWidth;
  });
});

// window width
const windowWidth = ref(window.innerWidth);


watchEffect(() => {
  if (windowWidth.value < 300) {
    result1.value = "its less than 300px";
  } else {
    result1.value = "its greatter than 300px";
  }
});

程式碼變很少吧!

成果:

結論

  • resize 是屬於 window 的事件,所以一定只能綁在window上
  • 在 Vue 裡面要監聽 window 要放在 onMounted() 裏面
  • VueUse + watch 可以去偵測螢幕寬度

用 VueUse + watch 比較快,但是對於新手來說有點困難(我),感覺就是誤打誤撞XD

如果還有更好的方法請推薦給我吧~歐內該~

怎麼會突然就進到Vue了呢XD

剩下一個禮拜了~~~~


參考資料:
VueUse
Vue js 3 - watch window.innerWidth does not working
Day_24: 讓 Vite 來開啟你的Vue 之 VueUse
Vanilla JS 與 Vue 的生命週期 Day 28
Add support for a v-on:resize event #1915


上一篇
Day25 前端蛇行撞牆記 - addEventListener() 的參數(下)
下一篇
Day27 前端蛇行撞牆記 - 表單輸入 input 原生及 v-model 比較 / Vue 3 (上)
系列文
前端蛇行撞牆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言