5

前端單兵基本教練 - 徒手製作 AJAX 載入中遮罩

 2 years ago
source link: https://blog.darkthread.net/blog/ajax-loading-mask-from-scratch/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
徒手製作 AJAX 載入中遮罩-黑暗執行緒

今天這篇屬於前端開發的單兵基本教練,練習為耗時的 AJAX 呼叫製作載入中狀態顯示,如以下效果:

針對較耗時的 AJAX 呼叫,在等待結果回傳期間顯示轉圈圈之類的載入動畫,安撫使用者情緒提供明確的執行狀態回饋,同時封鎖網頁介面並防止使用者在等待期間繼續操作造成動線混亂。這種效果相信大家一定都看過,類似套件程式庫很多(例如:blockUIKendo UI progressBootstrap Spinner,但這次打算用最基本的 HTML、CSS、JavaScript 自己動手做一個,目標是搞懂原理,從只會用套件,提升到有修改甚至開發套件的能力。

剖析這種載入中效果,大致分為兩部分:遮罩 + 載入中動畫或文字。遮罩是一層蓋在現有網頁正上方的元素,利用 CSS width: 100%; height: 100%; 讓面積與整張網頁相同,再透過 position: absolute; z-index: 9999; 蓋在網頁上方,最後 background-color: #444; opacity: 0.5; 做出半透明灰色效果;至於載入中動畫,可以用 GIF、SVG 甚至用純 CSS 動畫製作,網路上有不少免費素材可資利用。整個遮罩跟動畫的 HTML 跟 CSS 類似這樣:

<!-- CSS -->
<style>
	html,body { height: 100%; margin: 0; }
	.loading { 
		position: absolute; z-index: 9999;
		top: 0; left: 0;
		width: 100%; height: 100%;
		display: none;
	}
	.loading .mask {
		position:  absolute;
		width: 100%; height: 100%;
		background-color: #444; opacity: 0.5;
	}
	.loading .animation {
		width: 64px; height: 64px;
		margin: auto; margin-top: 40px;
		background: url('https://i.imgur.com/6pCtQAW.gif');
	}
</style>

<!-- HTML -->
<div class="loading">
	<div class="mask"></div>
	<div class="animation">
	</div>
</div>

網頁載入時 .loading DIV 預設 display: none 不顯示,呼叫 AJAX 時將 display 改為 block,載入中 DIV 便會蓋在現有網頁上並顯示轉圈 GIF,阻擋使用者繼續操作網頁(注意:此時仍可用鍵盤控制,但正常使用者不致誤用,若要防範惡意破解,則需加入其他安全鎖),待收到結果後再改回 none 恢復正常。

接著來寫一個後端範例配合測試,用一支 getGuid.aspx 模擬耗時數秒的 Web API,並加入模擬執行出錯功能:

<%@ Page Language="C#" %>
<script runat="server">
    void Page_Load(object sender, EventArgs e)
    {
        int delaySec;
        var d = Request["d"];
        if (d == "E") throw new ApplicationException("ERROR");
        if (int.TryParse(Request["d"], out delaySec))
        {
            System.Threading.Thread.Sleep(delaySec * 1000);
        }
        Response.Write(Guid.NewGuid().ToString());
    }
</script>

前端部分,我們先不要用 jQuery 或 axios,試寫香草口味 JavaScript,確認我們知道 XHR 原理,具備用 JavaScript 實作的能力,用 jQuery/axios 是為了省時省力,而非沒有工具就什麼都不會的廢人。關於 XHR (XMLHttpRequest),MDN Web Docs 有權威且完整的說明。以下是用純 JavaScript 實作的完整範例:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width,initial-scale=1">
		<title>Loading animation demo</title>
		<style>
			html,body { height: 100%; margin: 0; }
			.loading { 
				position: absolute; z-index: 9999;
				top: 0; left: 0;
				width: 100%; height: 100%;
				display: none;
			}
			.loading .mask {
				position:  absolute;
				width: 100%; height: 100%;
				background-color: #444; opacity: 0.5;
			}
                .loading .animation {
                    width: 64px; height: 64px;
                    margin: auto; margin-top: 40px;
                    background: url(https://i.imgur.com/6pCtQAW.gif);
                }
		</style>
	</head>
	<body>
		<select id="selAction">
			<option value="1">Delay 1s</option>
			<option value="3">Delay 3s</option>
			<option value="D">No Host</option>
			<option value="N">HTTP 404</option>
			<option value="E">HTTP 500</option>
		</select>
		<button id='btnTest'>Run Test</button><br />
		Result = <span id="txtResult"></span>
		<div class="loading">
			<div class="mask"></div>
			<div class="animation">
			</div>
		</div>
		<script>
			function toggleLoading(show) {
				document.querySelector('.loading').style.display =
					show ? 'block' : 'none';
            }
			function callAjax() {
				var req = new XMLHttpRequest();
				req.addEventListener("load", function () {
					toggleLoading(false);
					if (req.status == 200)
						document.getElementById('txtResult').innerText = req.responseText;
					else {
						alert("XHR Status=" + req.status);
                    }

				});
				req.addEventListener("error", function () {
					//failed to get response from remote server
					alert("ERROR");
					toggleLoading(false);
				});
				var act = document.getElementById('selAction').value;
				if (act == "D") {
					req.open("POST", "http://no-such-host.net/null.html");
				}
                else if (act == "N") {
                    req.open("POST", "no-such-page.aspx");
                }
				else {
					req.open("POST", "getGuid.aspx?d=" + act);
				}
				toggleLoading(true);
				document.getElementById('txtResult').innerText = "";
				req.send();
			}
			document.getElementById('btnTest')
				.addEventListener('click', callAjax);
		</script>
	</body>
</html>

XHR 的使用方式是先 new XMLHttpRequest() 建立物件,在 load 事件檢查傳回結果,由 status 屬性判斷結果,responseText、responseXML 讀取結果,若連不上伺服器或未得到 HTTP 回應(例如:DNS 有錯、TLS 憑證無效、網站無回應... 等等),則會觸發 error 事件。.open() 指定 GET/POST 及網址,.send() 送出 HTTP 請求,在送出請求前將 .loading 的 style.display 設為 block,load/error 事件執行時代表呼叫已結束,此時再將 .loading style.display 設成 none,就這麼簡單。

品嚐完香草口味,順便溫習一下 jQuery 練練手感:

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
    $('#btnTest').click(function() {
        var act = $('#selAction').val();
        var url = "getGuid.aspx?d=" + act;
        if (act == "D") url = "http://no-such-host.net/null.html";
        else if (act == "N") url = "no-such-page.aspx";
        $('.loading').show();
        $.post(url)
            .done(function (guid) { $('#txtResult').text(guid); })
            .fail(function (xhr) { alert("ERROR-" + xhr.status); })
            .always(function () { $('.loading').hide(); });
    });
    </script>

打完收工。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK