# Angular Fade In/Out Div ( 此為 Component 範本 ) ###### tags: `Angular` ## 先上測試結果圖 ![](https://i.imgur.com/ypgGyTH.gif) ## 步驟/簡介 1. 用一個變數存取區塊(div)狀態 ( Default(其實就是隱藏)、FadeIn、FadeOut ) 2. 提供一個 function 判斷 window 和 element 之間的位置關係去更改此區塊(div)狀態 3. 註冊 window 的 OnScroll、OnResize 去呼叫第 2 點的 function 4. ngAfterViewInit ( 讓 url 更換/首次進入頁面 trigger ) 中使用 requestAnimationFrame 方法 ( 為了等待元素正確渲染完畢計算位置 ),讓其呼叫第 2 點的 function 5. 依照狀態、方向 在 css 中訂好對應的 class name 6. 使用 [ngClass] 依照狀態去更改套用對應的 class ## 訂定 Fade In Or Out Component ### Component.html ``` <div #fadeDiv class="fadeDiv-main" [ngClass]="applyClass()"> <ng-content select="[div-main-content]"></ng-content> </div> ``` ### Component.ts ``` import { Component, HostListener, Input, OnInit, ViewChild, ViewContainerRef, AfterViewInit } from '@angular/core'; @Component({ selector: 'app-fade-in-or-out-div', templateUrl: './fade-in-or-out-div.component.html', styleUrls: ['./fade-in-or-out-div.component.css'] }) export class FadeInOrOutDivComponent implements OnInit, AfterViewInit { @Input() isFromLeft = true; @ViewChild('fadeDiv', { read: ViewContainerRef }) fadeDiv: ViewContainerRef; // 註冊 window scroll 事件 // 不需要特別刪除事件,參考: https://stackoverflow.com/a/50015632 @HostListener('window:scroll', ['$event']) onScroll(e: Event): void { this.fadeInOrOutInTheScreen(); } // 註冊 window resize 事件 @HostListener('window:resize', ['$event']) onResize(e: Event): void { this.fadeInOrOutInTheScreen(); } // ※ 棄用原因: angular route change 的時候不會 trigger window onload // 註冊 window onload 事件 // 如果 div 已經在畫面內直接顯示 // ※: 原本使用 ngAfterViewInit 一樣沒有渲染完畢,導致 element offsetTop 還是錯的位置 // 故使用 load 事件,等元素加載完畢後再檢測 // @HostListener('window:load', ['$event']) onLoad(e: Event): void { // if (this.isFirstLoaded == false) { // this.fadeInOrOutInTheScreen(); // this.isFirstLoaded = true; // console.log("window load") // } // } isFirstLoaded: boolean = false; fadeStatus: E_FadeStatus = E_FadeStatus.Default; constructor() { } ngOnInit(): void { } ngAfterViewInit(): void { // 嘗試等待下一個動畫幀等 html 元素渲染完畢 // 參考: // 1. JavaScript學習日記 : Day30 - JavaScript動畫: https://ithelp.ithome.com.tw/articles/10280810 // 2. 計時與動畫: https://pjchender.dev/webapis/webapis-timer-and-scheduling/ // console.log(this.isFirstLoaded) if (this.isFirstLoaded == false) { requestAnimationFrame(() => { this.fadeInOrOutInTheScreen(); }) this.isFirstLoaded = true; } } applyClass() { let baseClass = ""; if (this.fadeStatus == E_FadeStatus.Default) { // console.log("hide") return "hide"; } else if (this.fadeStatus == E_FadeStatus.FadeIn) { baseClass = "fadeIn"; } else if (this.fadeStatus == E_FadeStatus.FadeOut) { baseClass = "fadeOut"; } if (this.isFromLeft) { // console.log(baseClass + " left-to-right") return baseClass + " left-to-right"; } else { // console.log(baseClass + " right-to-left") return baseClass + " right-to-left"; } } fadeInOrOutInTheScreen() { const element = this.fadeDiv.element.nativeElement; // console.log("object offsetTop: " + element.offsetTop) // console.log("object clientHeight: " + element.clientHeight) // console.log("window scrollY: " + window.scrollY) // console.log("window outerHeight: " + window.outerHeight) // 棄用原因: 看到底部才跑出來,這樣區塊太長會看不到最上方的資訊,故改為看到區塊的最上方就開始 Fade In // var bottom_of_object = element.offsetTop + element.clientHeight; // var bottom_of_window = window.scrollY + window.outerHeight; // console.log("object bottom: " + bottom_of_object) // console.log("window bottom: " + bottom_of_window) var top_of_object = element.offsetTop + 200; // +200 讓其再下滑一點才 FadeIn,這樣能更清楚看到 FadeIn 效果 var windowHeight = window.scrollY + window.outerHeight; // console.log("object top: " + top_of_object) // console.log("window height: " + windowHeight) // console.log(this.fadeStatus) if (windowHeight > top_of_object) { this.fadeStatus = E_FadeStatus.FadeIn; } else { // 如果是剛剛 Fade In 進入畫面的才需要 Fade Out if (this.fadeStatus == E_FadeStatus.FadeIn) { this.fadeStatus = E_FadeStatus.FadeOut; } } // console.log(this.fadeStatus) } } enum E_FadeStatus { Default = "Default", FadeIn = "FadeIn", FadeOut = "FadeOut", } ``` ### 測試荒唐鏡板 Component.css ``` div.fadeDiv-main { margin: 100px; padding: 100px; background-color: lightgreen; } div.fadeDiv-main.hide { opacity: 0; } div.fadeIn.right-to-left { background-image: url("https://pic1.xuehuaimg.com/proxy/baijia/https://f12.baidu.com/it/u=3910127792,1794832412&fm=173&app=25&f=JPEG?w=640&h=367&s=E0C296472A4108550ED12CA20300D002&access=215967316"); background-position: center; background-repeat: no-repeat; background-size: contain; animation: fadeIn-right-to-left-animation 1.5s ease-out forwards; } @keyframes fadeIn-right-to-left-animation { 0% { opacity: 0; transform: translateX(100%); } 100% { opacity: 1; transform: translateX(0%); } } div.fadeIn.left-to-right { background-image: url("https://pic1.xuehuaimg.com/proxy/baijia/https://f12.baidu.com/it/u=3910127792,1794832412&fm=173&app=25&f=JPEG?w=640&h=367&s=E0C296472A4108550ED12CA20300D002&access=215967316"); background-position: center; background-repeat: no-repeat; background-size: contain; animation: fadeIn-left-to-right-animation 1.5s ease-out forwards; } @keyframes fadeIn-left-to-right-animation { 0% { opacity: 0; transform: translateX(-100%); } 100% { opacity: 1; transform: translateX(0%); } } div.fadeOut.right-to-left { background-image: url("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQV-1hvRnGwpIgG2RmB8eYtBv9nnVNtoNYuDHoOrKkQ5RmXaGDwMhZgrmsDC1qEr3BpT-c&usqp=CAU"); background-position: center; background-repeat: no-repeat; background-size: contain; animation: fadeOut-right-to-left-animation 1.5s ease-out forwards; } @keyframes fadeOut-right-to-left-animation { 0% { opacity: 1; transform: translateX(0%); } 100% { opacity: 0; transform: translateX(100%); } } div.fadeOut.left-to-right { background-image: url("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQV-1hvRnGwpIgG2RmB8eYtBv9nnVNtoNYuDHoOrKkQ5RmXaGDwMhZgrmsDC1qEr3BpT-c&usqp=CAU"); background-position: center; background-repeat: no-repeat; background-size: contain; animation: fadeOut-left-to-right-animation 1.5s ease-out forwards; } @keyframes fadeOut-left-to-right-animation { 0% { opacity: 1; transform: translateX(0%); } 100% { opacity: 0; transform: translateX(-100%); } } ``` ### 正常使用版 Component.css ``` div.fadeDiv-main { width: 100%; height: 100%; padding: 5px; } div.fadeDiv-main.hide { opacity: 0; } div.fadeIn.right-to-left { animation: fadeIn-right-to-left-animation 1.5s ease-out forwards; } @keyframes fadeIn-right-to-left-animation { 0% { opacity: 0; transform: translateX(100%); } 100% { opacity: 1; transform: translateX(0%); } } div.fadeIn.left-to-right { animation: fadeIn-left-to-right-animation 1.5s ease-out forwards; } @keyframes fadeIn-left-to-right-animation { 0% { opacity: 0; transform: translateX(-100%); } 100% { opacity: 1; transform: translateX(0%); } } div.fadeOut.right-to-left { animation: fadeOut-right-to-left-animation 1.5s ease-out forwards; } @keyframes fadeOut-right-to-left-animation { 0% { opacity: 1; transform: translateX(0%); } 100% { opacity: 0; transform: translateX(100%); } } div.fadeOut.left-to-right { animation: fadeOut-left-to-right-animation 1.5s ease-out forwards; } @keyframes fadeOut-left-to-right-animation { 0% { opacity: 1; transform: translateX(0%); } 100% { opacity: 0; transform: translateX(-100%); } } ``` ## 呼叫 Fade In Or Out Component ### 呼叫時特別注意 錯誤示範↓ ![](https://i.imgur.com/Ng6ZPx6.png) 今日頭條6以後,最下面兩個div不該出現的 img 沒有給 height/width attribute 的情況下 而是 style 設定 width/height 都 100% ( 自適應大小 ) 這樣會因為 img 剛剛還沒 loading 出來 ( img 沒設定固定高度 => 沒讀到圖片的情況下高度為 0 ) 導致下面的 div 位置在渲染階段的時候被認為該出現 ( 還沒被圖片推下去 ) 但其實不應該 所以使用時,<span style="color:red;font-weight: bold">區塊最好先設定好該有的高度</span> ### html ``` <!-- class 可有可無,若要覆蓋基本 style 再給 class --> <app-fade-in-or-out-div class="testDiv" [isFromLeft]="false"> <div div-main-content> <h2>今日頭條</h2> </div> </app-fade-in-or-out-div> ``` ### css 若要覆蓋 style 的寫法 ``` ::ng-deep .testDiv>div.fadeDiv-main { margin: 100px; padding: 100px; background-color: rgb(255, 255, 255) !important; } ``` ### 進階使用: 呼叫方使用 float 進行兩側分割畫面 ( RWD 適應 ) #### 結果 ![](https://i.imgur.com/u3SZMXH.gif) #### css ``` .news-wrap { /* 可以用來清除浮動,去除 margin 合併 */ display: flow-root; } .news-wrap>.left { width: 50%; line-height: 30px; padding: 5px; border: 2px blue solid; float: left; } .news-wrap>.right { width: 50%; line-height: 30px; padding: 5px; border: 2px green solid; float: left; } @media screen and (max-width: 600px) { .news-wrap>.left { width: 100%; } .news-wrap>.right { width: 100%; } } ``` #### html ``` <div class="news-wrap"> <div class="left"> <app-fade-in-or-out-div [isFromLeft]="true"> <div div-main-content class="rounded shadow bg-white"> <h2 class="m-0 py-2">今日頭條1</h2> <h4><a href="https://ynews.page.link/EjrC">俄烏戰停火有解? 美媒推測普丁在等「完全解放」頓巴斯</a></h4> <img class="w-75 rounded shadow" style="max-width: 500px;" onerror="this.src='assets/Images/404-error.png'" [src]="'https://images.pexels.com/photos/3526022/pexels-photo-3526022.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1'" /> <p class="mt-3 text-start px-3"> 俄烏戰爭開打迄今已經超過4個多月,而隨著全球經濟所受到的衝擊愈來愈深,何時才能畫下句點,成了各國最關注的焦點,而現在最新消息傳出,俄羅斯總統普丁有可能在完全佔領頓巴斯(Donbas)地區後,提出停火協議。 根據《CNBC》新聞網的報導,本周一普丁慶賀俄羅斯「解放」了盧甘斯克地區後,另一個宣布脫離烏克蘭的頓巴斯地區成了普丁下一個重點,據當地官員透露,俄羅斯的軍隊現正集中精力攻打頓巴斯,該地區週​​日遭到猛烈砲擊。 對此專為國防產業提供建議的奧斯托亞諮詢(Ostoya Consulting)負責人阿布拉莫維奇(Victor Abramowicz)表示,俄羅斯「幾乎可以肯定」能夠在頓巴斯實現其更有限的目標,而普丁提議停火的前提,只要給他自己一個機會,來鞏固他迄今為止所取得的成果。 阿布拉莫維奇表示,如果普丁在取得頓巴斯後提議停火,將會讓烏克蘭總統澤倫斯基陷入困境,如果他拒絕了停火協議,最嚴重的後果將會失去西方國家的支持。尤其美國今年11月將面臨期中選舉,在自身經濟持續低迷的情況下,拜登總統對於烏克蘭的堅定支持會不會出現鬆動,澤倫斯基必須慎重考量。 </p> </div> </app-fade-in-or-out-div> <app-fade-in-or-out-div [isFromLeft]="true"> <div div-main-content class="rounded shadow bg-white w-100"> <h2 class="m-0 py-2">今日頭條3</h2> <h4><a href="https://ynews.page.link/BB3y">金曲33/小S當眾「襲胸」具俊曄 網虧:過氣主持風格</a></h4> <img class="w-75 rounded shadow" style="max-width: 500px;" onerror="this.src='assets/Images/404-error.png'" [src]="'https://images.pexels.com/photos/713149/pexels-photo-713149.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1'" /> <p class="mt-3 text-start px-3"> 「第33屆金曲獎」於昨(2)日晚間7時在高雄巨蛋隆重登場,由資深藝人羅時豐獨挑大樑主持典禮,並請來藝人小S(徐熙娣)與陶晶瑩一同搭檔演出,更邀請到大S老公、人稱「國民姊夫」的具俊曄擔任神秘嘉賓,不僅具俊曄於台上熱情的展現自我,小S更當眾對姊夫「襲胸」,眾多搞笑互動把現場氣氛炒熱到最高點。 身為今年金曲獎神秘嘉賓具俊曄,在台上勇於表現自己,除了大秀一口流利的中文,更與小S大跳酷龍經典歌曲〈Bing Bing Bing〉,此外,小S還大膽伸手狂摸姊夫的胸部,具俊曄隨即撥開她的手並大喊「不可以這樣」、「安堆(韓文不行)」,有趣的互動笑翻台下眾人。 </p> </div> </app-fade-in-or-out-div> </div> <div class="right"> <app-fade-in-or-out-div [isFromLeft]="false"> <div div-main-content class="rounded shadow bg-white w-100"> <h2 class="m-0 py-2">今日頭條2</h2> <h4><a href="https://ynews.page.link/6gHv">本土34499例95死! 1歲男童發燒、嘔吐「一天就心跳突停」不治</a></h4> <img class="w-75 rounded shadow" style="max-width: 500px;" onerror="this.src='assets/Images/404-error.png'" [src]="'https://images.pexels.com/photos/3952231/pexels-photo-3952231.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1'" /> <p class="mt-3 text-start px-3"> 中央流行疫情指揮中心今(6)日公布國內新增34577例COVID-19確定病例,分別為34499例本土個案及78例境外移入;另確診個案中新增95例死亡,中重症新增275例。今日新增死亡個案中,最小僅1歲男童,6月30日發病,出現嘔吐、呼吸喘、發燒等症狀,隔天送到急診時即突發心跳停止,急救不治。 今日新增本土個案,依縣市區分,以新北市新增5324例最多,其次為台中市新增4669例、桃園市新增3687例、高雄市新增3608例、台北市新增3110例、台南市新增2902例。 死亡個案95例當中,最年幼的1歲男童,本身有先天性神經系統疾病,長期使用呼吸器,6月30日發病,出現嘔吐、呼吸喘、發燒、活力及餵食量下降,7月1日送至醫院急診,一到急診後就突發心跳停止,經急救仍不治。死亡原因為新冠病毒感染跟本身疾病,歸類為共病死亡案例。 另外,今日中重症新增275例,中症患者新增99例、重症患者新增68例。當中一口氣新增3例兒童重症MIS-C(兒童多系統發炎症候群),分別為0歲女童、2歲男童、12歲女童(曾接種一劑疫苗),均在6月初確診,6月底又開始出現發燒合併相關的腹瀉、眼睛紅、頸部腫等症狀,於6月29日至7月2日間就醫,經醫師診斷疑似MIS-C住院,目前都住院治療中。 指揮中心統計,國內兒童(12歲以下)重症累計有88例,已有22例死亡;分別為腦炎22例、肺炎16例、MIS-C 30例、敗血症3例、哮吼症8症、家中死亡6例以及共病2例。 </p> </div> </app-fade-in-or-out-div> <app-fade-in-or-out-div [isFromLeft]="false"> <div div-main-content class="rounded shadow bg-white w-100"> <h2 class="m-0 py-2">今日頭條4</h2> <h4><a href="https://udn.com/news/story/7266/6439509">高溫上看36度!午後大雷雨 吳德榮:周五起雨漸少但酷熱</a></h4> <img class="w-75 rounded shadow" style="max-width: 500px;" onerror="this.src='assets/Images/404-error.png'" [src]="'https://images.pexels.com/photos/1102915/pexels-photo-1102915.jpeg'" /> <p class="mt-3 text-start px-3"> 中央大學大氣科學系兼任副教授吳德榮今天在「三立準氣象.老大洩天機」專欄表示,各國模式模擬顯示,熱帶低壓在日本本州南方近海,向東移動;未來有再恢復成輕颱的趨勢,並轉向沿本州東側北上。最新歐洲系集模式及美國模式模擬圖皆顯示,下周菲律賓東方至南海海面仍是有利熱帶擾動發展的海域,但模擬結果仍分歧,需持續密切觀察。 他說,今天各地大多為多雲時晴的天氣;大氣不穩定,迎風面的南台灣不定時及中部以北的午後,皆有明顯降雨的機率,應注意大雷雨、伴隨劇烈天氣如雷擊、強風、瞬間強降雨,也要注意氣象局的特報。天氣偏熱,北部23至36度,中部22至35度,南部23至35度,東部22至35度。 明天太平洋高壓仍偏弱,天氣晴熱,持續不穩定,午後易有強對流發生,仍應注意劇烈天氣。 吳德榮表示,周五至下周二太平洋高壓逐日增強,晴朗炎熱,高溫升至36度左右,要防曬、防中暑;午後對流逐日減弱、範圍逐日縮小,大多在山區及少部分鄰近平地。 </p> </div> </app-fade-in-or-out-div> </div> </div> ```