如何在瀏覽器關閉發送請求


如何在瀏覽器關閉發送請求

有的,我們需要在頁面關閉時,統計用戶在該網站瀏覽時長;有時需要告知后台,該用戶已離開...
遇到這樣的情況並不少見。

只是在此之前,有兩件很重要的事情需要區分開來:

  • 如何知道瀏覽器是 關閉 還是 刷新
  • 關閉時發送請求,使用哪種 請求方式 才好

頁面的生命周期函數

當前有兩種表示方式

  • 頁面生命周期函數

    • DOMContentLoaded —— 瀏覽器加載 HTML,並構建 DOM 樹,但像 和樣式這樣的資源可能還沒有加載。
    • load —— 瀏覽器加載所有資源(圖像,樣式等)。
    • beforeunload/unload —— 當用戶離開頁面時。
  • Page Visibility API 教程,Page Lifecycle API 教程

    • Active 階段 網頁處於可見狀態,且擁有輸入焦點
    • Passive 階段 只可能發生在桌面同時有多個窗口的情況。
    • Hidden 階段 用戶的桌面被其他窗口占據,網頁不可見
    • Terminated 階段 由於用戶主動關閉窗口,或者在同一個窗口前往其他頁面,導致當前頁面開始被瀏覽器卸載並從內存中清除
    • Frozen 階段 網頁處於 Hidden 階段的時間過久,用戶又不關閉網頁,瀏覽器就有可能凍結網頁,使其進入 Frozen 階段。不過,也有可能,處於可見狀態的頁面長時間沒有操作,也會進入 Frozen 階段
    • Discarded 階段 處於 Frozen 階段,用戶又不喚醒頁面,那么就會進入 Discarded 階段,即瀏覽器自動卸載網頁,清除該網頁的內存占用。不過,Passive 階段的網頁如果長時間沒有互動,也可能直接進入 Discarded 階段

判斷瀏覽器是 關閉 還是 刷新

用來判斷用戶是否離開

通過三種方式來進行判斷

  • beforeunload/unload
  • Terminated 階段
  • 兩者結合判斷

理論知識夠了,開始進行嘗試
在此之前,由於頁面關閉時,很多方法執行不了,只能通過連接后台進行判斷,而且也很准確

var url = "http://127.0.0.1:3000/logout";
function getAajx(url) {
    var client = new XMLHttpRequest();
    client.open("get", url, false); // 第三個參數表明是同步的 xhr
    client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
    client.send();
}
function postAajx(url, data) {
    var client = new XMLHttpRequest();
    client.open("post", url, false); // 第三個參數表明是同步的 xhr
    client.setRequestHeader(
        "Content-Type",
        "application/x-www-form-urlencoded"
    );
    client.send(data);
}

beforeunload/unload

當瀏覽器窗口關閉或者刷新時,會觸發beforeunload事件。當前頁面不會直接關閉,可以點擊確定按鈕關閉或刷新,也可以取消關閉或刷新

由於刷新和關閉都會執行這個方法,因而在beforeunload/unload 中無法進行區分,下面是網友們的各種思路

  • 鼠標的位置,由於需要點擊關閉按鈕,因而鼠標不會在窗口內
window.addEventListener('beforeunload', function () {
    var n = window.event.screenX - window.screenLeft;
    var b = n > document.documentElement.scrollWidth - 20;
    if ((b && window.event.clientY < 0) || window.event.altKey) {
        alert("是關閉而非刷新");
    window.event.returnValue = ""; //這里可以放置你想做的操作代碼
    } else {
        alert("是刷新而非關閉");
    }
}, false)

可惜執行不了,主要利用關閉刷新都會執行 onbeforeunload方法

  • 利用刷新跟關閉在不同瀏覽器中執行的方式不同1,2
var _beforeUnload_time = 0;
var _gap_time = 0;
var is_fireFox = navigator.userAgent.indexOf("Firefox") > -1; //是否是火狐瀏覽器
window.addEventListener(
    "unload",
    function() {
        _gap_time = new Date().getTime() - _beforeUnload_time;
        if (_gap_time <= 5) {
        postAajx(url, `msg=瀏覽器關閉&time=${_gap_time}`);
        } else {
        postAajx(url, `msg=瀏覽器刷新&time=${_gap_time}`);
        }
    },
    false
);
window.addEventListener(
    "beforeunload",
    function() {
        _beforeUnload_time = new Date().getTime();
        if (is_fireFox) {
        postAajx(url, `msg=火狐關閉&time=${_gap_time}`);
        }
    },
    false
);

瀏覽器刷新

可以看出,確實是可行,刷新時長都超過 5s,關閉在 5s內。基於Chrome瀏覽器

需要更多地去驗證每個瀏覽器的細微差別,若是隨着瀏覽器的不斷升級,將來標准統一,有可能就沒有這樣的細微區別,導致執行不了

Terminated 階段

借助Page Lifecycle API,Page Visibility API 教程,Page Lifecycle API 教程,網友

const getState = () => {
    if (document.visibilityState === "hidden") {
        return "hidden";
    }
    if (document.hasFocus()) {
        return "active";
    }
    return "passive";
};

// Stores the initial state using the getState() function (defined above).
let state = getState();

// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the state value defined above.
const logStateChange = nextState => {
    const prevState = state;
    if (nextState !== prevState) {
        console.log(`State change: ${prevState} >>> ${nextState}`);
        getAajx(url+ "?origin=" + nextState);

        state = nextState;
    }
};

// These lifecycle events can all use the same listener to observe state
// changes (they call the getState() function to determine the next state).
["pageshow", "focus", "blur", "visibilitychange", "resume"].forEach(
    type => {
        window.addEventListener(type, () => logStateChange(getState()), {
        capture: true
        });
    }
);

// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener(
    "freeze",
    () => {
        // In the freeze event, the next state is always frozen.
        logStateChange("frozen");
    },
    { capture: true }
);

window.addEventListener(
    "pagehide",
    event => {
        if (event.persisted) {
        // If the event's persisted property is true the page is about
        // to enter the page navigation cache, which is also in the frozen state.
        logStateChange("frozen");
        } else {
        // If the event's persisted property is not true the page is
        // about to be unloaded.
        logStateChange("terminated");
        }
    },
    { capture: true }
);

測試page-lifecycle

由於各個瀏覽器顯示不同,上述效果不是很准確,特意使用 PageLifecycle.js再重新梳理一遍

<script src="./dist/lifecycle.es5.js"></script>
<script>
    console.log(window)
    lifecycle.addEventListener('statechange', function(event) {
    console.log(event.oldState, event.newState);
    getAajx(url+'?origin='+event.oldState+'>>>'+event.newState)
    });
</script>

lifecycle

很遺憾,刷新跟關閉一樣,依舊無法判斷出來。

綜上: 目前真的只能靠 方案一:beforeunload/unload 時間來進行判斷了

使用什么方式進行請求

  • img 動態創建img 利用 src屬性進行通知,同理script的src應該也可以
  • XMLHttpRequest 同步發送ajax請求(上述例子便是如此操作)
  • navigator.sendBeacon() sendBeacon 這個方法主要用於滿足統計和診斷代碼的需要,這些代碼通常嘗試在卸載(unload)文檔之前向web服務器發送數據
// 兼容性
navigator.sendBeacon = navigator.sendBeacon || new Function('var r = new XMLHttpRequest();r.open("POST",arguments[0],true);r.send(arguments[1]);');

function postForm (url, data) {
  let P = new FormData()
  P.append('origin', data.origin)
  navigator.sendBeacon(url, P)
}
lifecycle.addEventListener('statechange', function(event) {
  // getAajx(url+'?origin='+event.oldState+'>>>'+event.newState)
  postForm(url, {origin: event.oldState+'>>>'+event.newState })
});

sendBeacon

也是可以發送請求的,由於異步,所以跟同步的ajax不同,但是這個確實更合理

總結

  • 瀏覽器刷新 關閉 目前只能通過時間大小來判斷了,不完美
  • 發送請求 navigator.sendBeacon這個會更好


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM