前言

在公司的 Angular project 使用 onResize() 監聽 screen viewport changed 時,發生了一些預期外的問題

經過了一個禮拜的搗鼓,以及 Sr. engineer 的指導,發現可以使用這個 ResizeObserver API,雖然專案最後沒有採用,我們用了另一個算是微繞路的方法,不算是 Work around,可也不太能百分百說是正解,欲知詳情如何就請看下去吧 👀

問題

Web APP 在手機上轉動方向後,透過 onResize() window event 的 call back 來做後續的動作時,沒有抓到元素變動後的正確位置

原因是 onResize() event 是針對 window viewport 發生變動,就會被 fired,並不是針對元素有變動才 fired,所以有機率會發生想要計算的目標元素還沒更新完畢時就收到 call back,這時候抓到的值可能會是還沒改變時的樣子

相關討論可以到永遠的好朋友 StackOverflow 上看

onOrientationChange() window event 也是一樣的,它也是針對 window viewport 的改變,只是多了判斷角度,無法讓我們接收 DOM 真的改變後的 call back

Whenever the viewport is drawn at a different angle compared to the device’s natural orientation (refer to this spec)

這個問題發生的機會在小專案可能不高,因為從 window 一變動到 DOM updated 的時差應該是非常短的,我們後來認為應該跟目前的專案太肥了導致在 rotating 的 perf 很差有關

解法

使用 JS 剛推出不久的 API - ResizeObserver 直接監聽 DOM 變動,就能完美解決這個問題😇

目前幾乎所有瀏覽器都支援了 (ResizeObserver 背後其實就是基於 RxJS 的 Observable 概念)

使用它除了能解決我上述提到的問題,還能夠改進不必要的 call back check 次數

在大部分的使用情境,我們用 window event 去監聽大小變動都是想要 update layout (如果有需要),但有時候元素大小沒有被變動因為視窗變動了,window resize event 還是會被 fired

延伸閱讀

library

剩下的小問題就是在 Angular 10 + Typescript 中並沒有找到 ResizeObserver type,Compiler 會報錯 (不過是可以使用的)

大部分推薦的是引入這個已經存在一陣子的 resize-observer-polyfill open source

TS code snippet

如果不想引入 3rd party library,在 Angular + ts code 內要使用 any type 宣告,並且在 initiate 的地方的前一行用 // @ts-ignore 去避免 compiler error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private resizeObserver: any;
@ViewChild('targetDiv') targetElement: ElementRef;

constructor(...) {}

ngOnInit() {
if ('ResizeObserver' in window) {
// @ts-ignore
this.resizeObserver = new ResizeObserver((entries: any) => {
this.targetBottom = entries[0].target.getBoundingClientRect().bottom;
});
} else {
console.warn('ResizeObserver is unsupported in this browser');
}
}

ngAfterViewInit() {
if (this.resizeObserver) {
this.resizeObserver.observe(this.targetElement.nativeElement);
}
}

Trick

senior 同事提供的另外一個方法是加 setTimeout()但不要加任何秒數 (通常不建議去設置任何秒數去預期某些行為會在這個秒數內結束),只需等這個 tick 結束,看看能不能抓到目標元素正確的位置

1
2
3
4
5
6
@HostListener('window:orientationchange')
onOrientationChange() {
setTimeout(() => {
// actions after orientation changed
});
}

會稱作 Trick 的原因是可能不是萬用解,我試過放在 onResize() 內,還是沒有辦法抓到正確的數值,後來發現加在 onOrientationChange() 內有辦法得到正確的值

正好我們要解的 issue 只有 在 orientation change 後才需要✌️✌️✌️

如果加入 ResizeObserver,需要考慮 backward compatibility,萬一這個瀏覽器版本不支援的話,要有對應的 fall back action,後來就決定比起加入 ResizeObserver,使用這個方法才最適合我們目前的需求

後記

這次經驗又讓我學到新東西,對於前端開發還不能說是特別熟稔,目前接觸了一年多下來發現真的要很認真與時俱進,既有的基礎核心都還沒補完,就要更新知識,在不同語言間跳轉的話我又容易忘記前端用過的東西,然後瀏覽器種類、版本、裝置又這麼多組合要考慮= =…以後誰在跟我說前端很簡單的都給我滾