Analyse de certains principes techniques clés du SDK de surveillance frontale

Php Development Engineer 2021-10-13 06:21:22
analyse certains principes techniques cl


Une plate - forme complète de surveillance frontale comprend trois parties:Collecte et communication des données、Organisation et stockage des données、Présentation des données.

C'est la première partie de cet article——Collecte et communication des données.Voici un aperçu du contenu de cet article,Vous pouvez commencer par un aperçu de:

Il est difficile de comprendre les connaissances théoriques,Pour ce faire, j'a i écrit un simpleSurveillance SDK,Vous pouvez l'utiliser pour écrire des DEMO,Aide à approfondir la compréhension.Lisez - le avec cet article,Ça marche mieux.

Acquisition de données sur le rendement

chrome L'équipe de développement a proposé une série d'indicateurs pour mesurer le rendement des pages Web:

  • FP(first-paint),Temps écoulé entre le début du chargement de la page et le premier pixel dessiné à l'écran
  • FCP(first-contentful-paint),Temps écoulé entre le début du chargement de la page et la fin du rendu à l'écran de n'importe quelle partie du contenu de la page
  • LCP(largest-contentful-paint),Temps écoulé entre le début du chargement de la page et la fin du rendu à l'écran du plus grand bloc de texte ou élément d'image
  • CLS(layout-shift),Depuis le chargement de la page etÉtat du cycle de vieDevient le score cumulatif de tous les décalages inattendus de mise en page qui se produisent pendant la dissimulation

Ces quatre mesures de rendement doivent être adoptées. PerformanceObserver Pour obtenir(On peut aussi performance.getEntriesByName() Accès,Mais il n'a pas été informé lorsque l'événement a été déclenché).PerformanceObserver Est un objet de surveillance du rendement,Utilisé pour surveiller les événements de mesure du rendement.

FP

FP(first-paint),Temps écoulé entre le début du chargement de la page et le premier pixel dessiné à l'écran.En fait, FP Il n'y a aucun problème à comprendre l'écran blanc..

Les codes de mesure sont les suivants::

const entryHandler = (list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-paint') {
observer.disconnect()
}
console.log(entry)
}
}
const observer = new PerformanceObserver(entryHandler)
// buffered Propriété indiquant si les données mises en cache sont observées,C'est - à - dire qu'il n'est pas important d'observer que le Code est ajouté plus tard que l'événement déclenche.
observer.observe({ type: 'paint', buffered: true })
Copier le Code

Le code ci - dessus permet d'obtenir FP Le contenu de:

{
duration0,
entryType"paint",
name"first-paint",
startTime359, // fp Temps
}
Copier le Code

Parmi eux startTime C'est le temps qu'il nous faut pour dessiner..

FCP

FCP(first-contentful-paint),Temps écoulé entre le début du chargement de la page et la fin du rendu à l'écran de n'importe quelle partie du contenu de la page.Pour cet indicateur,"Contenu"Renvoie au texte、Images(Inclure une image de fond)、<svg>Element or not White<canvas>Élément.

Pour offrir une bonne expérience utilisateur,FCP Le score de 1.8 En quelques secondes.

Code de mesure:

const entryHandler = (list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
observer.disconnect()
}
console.log(entry)
}
}
const observer = new PerformanceObserver(entryHandler)
observer.observe({ type: 'paint', buffered: true })
Copier le Code

Le code ci - dessus permet d'obtenir FCP Le contenu de:

{
duration0,
entryType"paint",
name"first-contentful-paint",
startTime459, // fcp Temps
}
Copier le Code

Parmi eux startTime C'est le temps qu'il nous faut pour dessiner..

LCP

LCP(largest-contentful-paint),Temps écoulé entre le début du chargement de la page et la fin du rendu à l'écran du plus grand bloc de texte ou élément d'image.LCP L'indicateur sera basé sur la pageChargement initialPoint dans le temps pour signaler le maximum visible dans la zone visibleBloc d'image ou de texteTemps relatif pour terminer le rendu.

Un bon LCP Les scores devraient être contrôlés 2.5 En quelques secondes.

Code de mesure:

const entryHandler = (list) => {
if (observer) {
observer.disconnect()
}
for (const entry of list.getEntries()) {
console.log(entry)
}
}
const observer = new PerformanceObserver(entryHandler)
observer.observe({ type: 'largest-contentful-paint', buffered: true })
Copier le Code

Le code ci - dessus permet d'obtenir LCP Le contenu de:

{
duration: 0,
element: p,
entryType: "largest-contentful-paint",
id: "",
loadTime: 0,
name: "",
renderTime: 1021.299,
size: 37932,
startTime: 1021.299,
url: "",
}
Copier le Code

Parmi eux startTime C'est le temps qu'il nous faut pour dessiner..element Oui. LCP Dessiné DOM Élément.

FCP Et LCP La différence entre:FCP Déclenché chaque fois que n'importe quel contenu est dessiné,LCP Est déclenché lorsque le rendu de contenu maximum est terminé.

LCP Les types d'éléments examinés sont::

  • <img>Élément
  • Intégré dans<svg>Dans l'élément<image>Élément
  • <video>Élément(Utiliser l'image de couverture)
  • Adoptionurl()Fonctions(Plutôt que d'utiliserCSS Gradient)Éléments chargés avec image de fond
  • Contenant des noeuds de texte ou d'autres sous - éléments de texte au niveau de la ligneÉléments au niveau du bloc.

CLS

CLS(layout-shift),Depuis le chargement de la page etÉtat du cycle de vieDevient le score cumulatif de tous les décalages inattendus de mise en page qui se produisent pendant la dissimulation.

La fraction de décalage de disposition est calculée comme suit::

Score offset de mise en page = Score d'impact * Fraction de distance
Copier le Code

Score d'impactMesureÉlément instable.Effet sur la zone de visualisation entre deux images.

Fraction de distanceSe réfère à n'importe quelÉlément instableDistance maximale de déplacement en un seul cadre(Horizontal ou vertical)Diviser par la dimension maximale de la zone visible(Largeur ou hauteur,Le plus élevé des montants suivants est retenu:).

CLS C'est la somme de tous les décalages de mise en page.

Quand un DOM Déplacement entre les deux cadres rendus,Ça va déclencher CLS(Comme le montre la figure).

Le rectangle de l'image ci - dessus se déplace du coin supérieur gauche vers la droite,C'est un décalage de mise en page..En même temps,In CLS Moyenne,Il y en a un qui s'appelleFenêtre de sessionTerminologie:.Un ou plusieurs décalages de mise en page uniques qui se produisent rapidement et consécutivement,Chaque décalage est inférieur à 1 Secondes,Et la durée maximale de toute la fenêtre est 5 Secondes.

Par exemple, la deuxième fenêtre de session de l'image ci - dessus,Il y a quatre décalages de disposition à l'intérieur.,L'intervalle entre chaque décalage doit être inférieur à 1 Secondes,Et le temps entre le premier et le dernier décalage ne doit pas dépasser 5 Secondes,C'est une fenêtre de session..Si cette condition n'est pas remplie,Même une nouvelle fenêtre de session.Quelqu'un pourrait demander,Pourquoi cette règle??En fait, c'est chrome Résultats de l'analyse de l'équipe à partir d'un grand nombre d'expériences et d'études Evolving the CLS metric.

CLS Il y a trois façons de calculer:

  1. Cumul
  2. Moyenne de toutes les fenêtres de session
  3. Prend la valeur maximale dans toutes les fenêtres de session

Cumul

C'est - à - dire additionner tous les points de décalage de mise en page à partir du chargement de la page.Mais ce calcul n'est pas convivial pour les pages à longue durée de vie,Plus la page reste longtemps,CLS Plus le score est élevé.

Moyenne de toutes les fenêtres de session

Ce calcul n'est pas effectué en unités décalées par une seule disposition,Au lieu de cela, la fenêtre de session.Additionnez les valeurs de toutes les fenêtres de session et faites la moyenne.Mais cette méthode de calcul présente également des inconvénients..

Comme vous pouvez le voir sur la photo ci - dessus,La première fenêtre de session a produit CLS Points,La deuxième fenêtre de session a produit CLS Points.Si vous prenez leur moyenne comme CLS Points,Vous ne pouvez pas voir la santé de la page.Il s'avère que la page a été décalée plus tôt,Moins de décalage à un stade ultérieur,La moyenne actuelle ne reflète pas cette situation.

Prend la valeur maximale dans toutes les fenêtres de session

C'est la meilleure façon de calculer.,Prend le maximum de toutes les fenêtres de session à la fois,Utilisé pour refléter le pire cas de décalage de mise en page.Pour plus de détails, voir Evolving the CLS metric.

Voici les codes de mesure pour la troisième méthode de calcul:

let sessionValue = 0
let sessionEntries = []
const cls = {
subType: 'layout-shift',
name: 'layout-shift',
type: 'performance',
pageURL: getPageURL(),
value: 0,
}
const entryHandler = (list) => {
for (const entry of list.getEntries()) {
// Only count layout shifts without recent user input.
if (!entry.hadRecentInput) {
const firstSessionEntry = sessionEntries[0]
const lastSessionEntry = sessionEntries[sessionEntries.length - 1]
// If the entry occurred less than 1 second after the previous entry and
// less than 5 seconds after the first entry in the session, include the
// entry in the current session. Otherwise, start a new session.
if (
sessionValue
&& entry.startTime - lastSessionEntry.startTime < 1000
&& entry.startTime - firstSessionEntry.startTime < 5000
) {
sessionValue += entry.value
sessionEntries.push(formatCLSEntry(entry))
} else {
sessionValue = entry.value
sessionEntries = [formatCLSEntry(entry)]
}
// If the current session value is larger than the current CLS value,
// update CLS and the entries contributing to it.
if (sessionValue > cls.value) {
cls.value = sessionValue
cls.entries = sessionEntries
cls.startTime = performance.now()
lazyReportCache(deepCopy(cls))
}
}
}
}
const observer = new PerformanceObserver(entryHandler)
observer.observe({ type: 'layout-shift', buffered: true })
Copier le Code

Après avoir lu la description textuelle ci - dessus,Regardez le Code et vous comprendrez..La mesure du décalage de disposition primaire est la suivante::

{
duration: 0,
entryType: "layout-shift",
hadRecentInput: false,
lastInputTime: 0,
name: "",
sources: (2) [LayoutShiftAttribution, LayoutShiftAttribution],
startTime: 1176.199999999255,
value: 0.000005752046026677329,
}
Copier le Code

Dans le Code value Le champ est le score offset de mise en page.

DOMContentLoaded、load Événements

Quand pur HTML Lorsqu'il est entièrement chargé et analysé,DOMContentLoaded L'événement sera déclenché,Pas besoin d'attendre css、img、iframe Chargement terminé.

Lorsque la page entière et toutes les ressources dépendantes telles que les feuilles de style et les images sont chargées,Déclenchera load Événements.

Bien que ces deux indicateurs de rendement soient plus anciens,Mais ils peuvent encore refléter certaines situations sur la page.Il est toujours nécessaire de les surveiller..

import { lazyReportCache } from '../utils/report'
['load', 'DOMContentLoaded'].forEach(type => onEvent(type))
function onEvent(type) {
function callback() {
lazyReportCache({
type: 'performance',
subType: type.toLocaleLowerCase(),
startTime: performance.now(),
})
window.removeEventListener(type, callback, true)
}
window.addEventListener(type, callback, true)
}
Copier le Code

Temps de rendu du premier écran

Dans la plupart des cas,Le temps de rendu du premier écran peut être atteint en load Acquisition d'événements.Sauf dans des circonstances exceptionnelles.,Par exemple, les images chargées asynchrones et DOM.

<script> setTimeout(() => { document.body.innerHTML = ` <div> <!-- Omettre un tas de code... --> </div> ` }, 3000) </script>
Copier le Code

Ça ne passera pas. load L'événement a le temps de rendu du premier écran.Nous devons passer MutationObserver Pour obtenir le temps de rendu du premier écran.MutationObserver On écoute. DOM Événements déclenchés lorsque les attributs de l'élément changent.

Processus de calcul du temps de rendu du premier écran:

  1. Utilisation MutationObserver Écouter document Objet,Chaque fois que DOM Lorsque les attributs de l'élément changent,Déclencheur d'événements.
  2. Juge ça. DOM Si l'élément est dans le premier écran,Si dans,Dans requestAnimationFrame() Appelé dans une fonction de rappel performance.now() Obtenir l'heure actuelle,Comme son temps de dessin.
  3. Le dernier DOM Le temps de dessin de l'élément est comparé au temps de toutes les images chargées dans le premier écran,Utiliser le maximum comme temps de rendu du premier écran.

Écouter DOM

const next = window.requestAnimationFrame ? requestAnimationFrame : setTimeout
const ignoreDOMList = ['STYLE', 'SCRIPT', 'LINK']
observer = new MutationObserver(mutationList => {
const entry = {
children: [],
}
for (const mutation of mutationList) {
if (mutation.addedNodes.length && isInScreen(mutation.target)) {
// ...
}
}
if (entry.children.length) {
entries.push(entry)
next(() => {
entry.startTime = performance.now()
})
}
})
observer.observe(document, {
childList: true,
subtree: true,
})
Copier le Code

Le code ci - dessus est Listener DOM Codes modifiés,Doit être filtré en même temps stylescriptlink Étiquette isométrique.

Déterminer s'il est sur le premier écran

Une page peut avoir beaucoup de contenu,Mais l'utilisateur ne peut voir que le contenu d'un seul écran.Donc en comptant le temps de rendu du premier écran,,Champ d'application requis,Limiter le contenu rendu à l'écran courant.

const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
// dom Si l'objet est à l'écran
function isInScreen(dom) {
const rectInfo = dom.getBoundingClientRect()
if (rectInfo.left < viewportWidth && rectInfo.top < viewportHeight) {
return true
}
return false
}
Copier le Code

Utiliser requestAnimationFrame() Accès DOM Temps de tirage

Quand DOM Changement déclenché MutationObserver Au moment de l'événement,Juste un représentant. DOM Le contenu peut être lu à,Ça ne veut pas dire que DOM Est dessiné à l'écran.

Comme le montre la figure ci - dessus,Quand il est déclenché MutationObserver Au moment de l'événement,Peut être lu à document.body Il y a déjà quelque chose dessus.,Mais en fait, l'écran de gauche n'a rien dessiné.Alors appelez requestAnimationFrame() Obtenez l'heure actuelle comme DOM Temps de tirage.

Comparé au temps de chargement de toutes les images sur le premier écran

function getRenderTime() {
let startTime = 0
entries.forEach(entry => {
if (entry.startTime > startTime) {
startTime = entry.startTime
}
})
// Besoin de comparer avec le temps de chargement de toutes les images de la page courante,Prendre la valeur maximale
// Le temps de demande d'image doit être inférieur à startTime,Le temps de fin de réponse doit être supérieur à startTime
performance.getEntriesByType('resource').forEach(item => {
if (
item.initiatorType === 'img'
&& item.fetchStart < startTime
&& item.responseEnd > startTime
) {
startTime = item.responseEnd
}
})
return startTime
}
Copier le Code

Optimisation

Le Code n'est pas encore optimisé.,Deux considérations principales:

  1. Quand déclarer le temps de rendu?
  2. Si compatible avec l'ajout asynchrone DOM Situation?

Premier point,Doit être DOM Ne changez pas avant de déclarer le temps de rendu,En général load Après le déclenchement de l'événement,DOM Ça ne change plus..Pour qu'on puisse faire un rapport à ce moment - là.

Deuxième point,Ça pourrait être dans LCP Signaler après le déclenchement de l'événement.Chargement synchrone ou asynchrone DOM,Il faut le dessiner.,Pour pouvoir écouter LCP Événements,L'escalade n'est autorisée qu'après le déclenchement de l'événement.

Combiner les deux solutions ci - dessus,Avec le code suivant:

let isOnLoaded = false
executeAfterLoad(() => {
isOnLoaded = true
})
let timer
let observer
function checkDOMChange() {
clearTimeout(timer)
timer = setTimeout(() => {
// Attendez. load、lcp Après le déclenchement de l'événement et DOM Quand l'arbre ne change plus,Calculer le temps de rendu du premier écran
if (isOnLoaded && isLCPDone()) {
observer && observer.disconnect()
lazyReportCache({
type: 'performance',
subType: 'first-screen-paint',
startTime: getRenderTime(),
pageURL: getPageURL(),
})
entries = null
} else {
checkDOMChange()
}
}, 500)
}
Copier le Code

checkDOMChange() Le Code est déclenché à chaque fois MutationObserver Appelé à l'événement,Doit être traité avec une fonction anti - bavardage.

La demande d'interface prend du temps

La demande d'interface prend du temps XMLHttpRequest Et fetch Pour écouter.

Écouter XMLHttpRequest

originalProto.open = function newOpen(...args) {
this.url = args[1]
this.method = args[0]
originalOpen.apply(this, args)
}
originalProto.send = function newSend(...args) {
this.startTime = Date.now()
const onLoadend = () => {
this.endTime = Date.now()
this.duration = this.endTime - this.startTime
const { status, duration, startTime, endTime, url, method } = this
const reportData = {
status,
duration,
startTime,
endTime,
url,
method: (method || 'GET').toUpperCase(),
success: status >= 200 && status < 300,
subType: 'xhr',
type: 'performance',
}
lazyReportCache(reportData)
this.removeEventListener('loadend', onLoadend, true)
}
this.addEventListener('loadend', onLoadend, true)
originalSend.apply(this, args)
}
Copier le Code

Comment juger XML La demande a - t - elle été acceptée??Selon que son code d'état est 200~299 Entre.Si dans,C'est le succès,Sinon, l'échec.

Écouter fetch

const originalFetch = window.fetch
function overwriteFetch() {
window.fetch = function newFetch(url, config) {
const startTime = Date.now()
const reportData = {
startTime,
url,
method: (config?.method || 'GET').toUpperCase(),
subType: 'fetch',
type: 'performance',
}
return originalFetch(url, config)
.then(res => {
reportData.endTime = Date.now()
reportData.duration = reportData.endTime - reportData.startTime
const data = res.clone()
reportData.status = data.status
reportData.success = data.ok
lazyReportCache(reportData)
return res
})
.catch(err => {
reportData.endTime = Date.now()
reportData.duration = reportData.endTime - reportData.startTime
reportData.status = 0
reportData.success = false
lazyReportCache(reportData)
throw err
})
}
}
Copier le Code

Pour fetch,Peut être basé sur ok Champ pour déterminer si la demande a été acceptée,Si oui true Demande acceptée,Sinon, l'échec.

Attention!,Temps de demande d'interface écouté et chrome devtool Le temps détecté sur peut être différent.C'est parce que chrome devtool Ce qui a été détecté sur HTTP Temps d'envoi de la demande et d'interface tout au long du processus.Mais xhr Et fetch Est une requête asynchrone,La fonction de rappel doit être appelée après une demande d'interface réussie.L'événement déclenche la fonction de rappel dans la file d'attente des messages,Puis le navigateur traite,Il y a aussi un processus d'attente.

Temps de chargement des ressources、Taux de succès du cache

Adoption PerformanceObserver On peut écouter. resource Et navigation Événements,Si le navigateur ne supporte pas PerformanceObserver,Peut encore passer performance.getEntriesByType(entryType) Pour le déclassement.

Quand resource Lorsque l'événement est déclenché,Vous pouvez obtenir la liste des ressources correspondantes,Chaque objet de ressource contient les champs suivants::

Nous pouvons extraire des informations utiles de ces champs:

{
name: entry.name, // Nom de la ressource
subType: entryType,
type: 'performance',
sourceType: entry.initiatorType, // Type de ressource
duration: entry.duration, // Le chargement des ressources prend du temps
dns: entry.domainLookupEnd - entry.domainLookupStart, // DNS Ça prend du temps
tcp: entry.connectEnd - entry.connectStart, // Établissement tcp Connexion longue
redirect: entry.redirectEnd - entry.redirectStart, // La redirection prend du temps
ttfb: entry.responseStart, // Temps du premier octet
protocol: entry.nextHopProtocol, // Demande d'accord
responseBodySize: entry.encodedBodySize, // Taille du contenu de la réponse
responseHeaderSize: entry.transferSize - entry.encodedBodySize, // Taille de la tête de réponse
resourceSize: entry.decodedBodySize, // Taille de la ressource après décompression
isCache: isCache(entry), // Si le cache est touché
startTime: performance.now(),
}
Copier le Code

Déterminer si la ressource a touché le cache

L'un de ces objets de ressources transferSize Champ,Il représente la taille de la ressource acquise,Inclure la taille des champs d'en - tête de réponse et des données de réponse.Si cette valeur est 0,La description est lue directement à partir du cache(Mise en cache forcée).Si cette valeur n'est pas 0,Mais encodedBodySize Le champ est 0,Indique qu'il prend le cache de négociation(encodedBodySize Représente les données de réponse à la demande body Taille).

function isCache(entry) {
// Lire directement à partir du cache ou 304
return entry.transferSize === 0 || (entry.transferSize !== 0 && entry.encodedBodySize === 0)
}
Copier le Code

Non - respect des conditions ci - dessus,Description cache manquant.Et ensuite,Toutes les données qui ont touché le cache/Total des donnéesPour obtenir le taux de succès du cache.

Cache aller - retour du Navigateur BFC(back/forward cache)

bfcache Est un cache de mémoire,Il garde toute la page en mémoire.La page entière est immédiatement visible lorsque l'utilisateur revient,Au lieu de rafraîchir à nouveau.Selon cet article bfcache Introduction,firfox Et safari Toujours soutenu bfc,chrome Prise en charge uniquement sur les navigateurs mobiles haute version.Mais j'ai essayé.,Seulement safari Prise en charge du Navigateur,Peut - être le mien. firfox Mauvaise version.

Mais bfc Il y a aussi des défauts.,Lorsque l'utilisateur retourne à bfc Lors de la restauration d'une page,Le Code de la page originale ne sera pas exécuté à nouveau.À cette fin,,Le navigateur fournit un pageshow Événements,Vous pouvez y mettre le Code qui doit être exécuté à nouveau.

window.addEventListener('pageshow', function(event) {
// Si la propriété est true,Indique que c'est à partir de bfc Pages récupérées dans
if (event.persisted) {
console.log('This page was restored from the bfcache.');
} else {
console.log('This page was loaded normally.');
}
});
Copier le Code

De bfc Pages récupérées dans,Nous devons aussi collecter leurs FP、FCP、LCP Attendre toutes sortes de temps.

onBFCacheRestore(event => {
requestAnimationFrame(() => {
['first-paint', 'first-contentful-paint'].forEach(type => {
lazyReportCache({
startTime: performance.now() - event.timeStamp,
name: type,
subType: type,
type: 'performance',
pageURL: getPageURL(),
bfc: true,
})
})
})
})
Copier le Code

Le code ci - dessus est bien compris,In pageshow Après le déclenchement de l'événement,Soustraire le temps de déclenchement de l'événement de l'heure actuelle,Cette différence de temps est le temps de dessin de l'indice de performance.Attention!,De bfc Ces mesures de performance pour les pages récupérées dans,Les valeurs sont généralement faibles,En général 10 ms Gauche et droite.Donc vous leur donnez un champ d'identification bfc: true.Cela permet de les ignorer dans les statistiques de performance.

FPS

Utilisation requestAnimationFrame() Nous pouvons calculer FPS.

const next = window.requestAnimationFrame
? requestAnimationFrame : (callback) => { setTimeout(callback, 1000 / 60) }
const frames = []
export default function fps() {
let frame = 0
let lastSecond = Date.now()
function calculateFPS() {
frame++
const now = Date.now()
if (lastSecond + 1000 <= now) {
// Parce que now - lastSecond En millisecondes,Alors... frame Oui. * 1000
const fps = Math.round((frame * 1000) / (now - lastSecond))
frames.push(fps)
frame = 0
lastSecond = now
}
// Évitez de signaler trop rapidement,Mettre en cache un certain nombre de rapports
if (frames.length >= 60) {
report(deepCopy({
frames,
type: 'performace',
subType: 'fps',
}))
frames.length = 0
}
next(calculateFPS)
}
calculateFPS()
}
Copier le Code

La logique du Code est la suivante::

  1. Enregistrer une heure initiale,Et chaque fois que ça se déclenche, requestAnimationFrame() Heure,Ajoutez juste le nombre de cadres 1.Dans une seconde.Nombre de cadres/Temps écouléPour obtenir le taux de trame actuel.

Lorsque trois consécutifs sont inférieurs à 20 De FPS Au moment de l'apparition,On peut conclure que la page est carton.,Pour plus de détails, voir Comment surveiller les captures de page.

export function isBlocking(fpsList, below = 20, last = 3) {
let count = 0
for (let i = 0; i < fpsList.length; i++) {
if (fpsList[i] && fpsList[i] < below) {
count++
} else {
count = 0
}
if (count >= last) {
return true
}
}
return false
}
Copier le Code

Vue Temps de rendu du changement de routage

Nous savons déjà comment calculer le temps de rendu du premier écran,Mais comment calculer SPA Qu'en est - il du temps de rendu de page causé par le routage de page appliqué?Pour cet article Vue Par exemple,Dis - moi ce que je pense..

export default function onVueRouter(Vue, router) {
let isFirst = true
let startTime
router.beforeEach((to, from, next) => {
// D'autres statistiques de temps de rendu sont déjà disponibles pour la première fois sur la page d'entrée
if (isFirst) {
isFirst = false
return next()
}
// Voilà. router Ajouter un nouveau champ,Indique si le temps de rendu doit être calculé
// Seuls les sauts de routage doivent être calculés
router.needCalculateRenderTime = true
startTime = performance.now()
next()
})
let timer
Vue.mixin({
mounted() {
if (!router.needCalculateRenderTime) return
this.$nextTick(() => {
// .Code qui ne s'exécute qu'après que la vue entière a été rendue
const now = performance.now()
clearTimeout(timer)
timer = setTimeout(() => {
router.needCalculateRenderTime = false
lazyReportCache({
type: 'performance',
subType: 'vue-router-change-paint',
duration: now - startTime,
startTime: now,
pageURL: getPageURL(),
})
}, 1000)
})
},
})
}
Copier le Code

La logique du Code est la suivante::

  1. Écouter les crochets de routage,Déclenché lors du changement de route router.beforeEach() Crochet,Dans la fonction de rappel de ce crochet, Notez l'heure actuelle comme l'heure de début du rendu.
  2. Utilisation Vue.mixin() Pour tous les composants mounted() Injecter une fonction.Chaque fonction exécute une fonction tampon.
  3. Quand le dernier composant mounted() Quand ça se déclenche,Cela signifie que tous les composants sous cette route ont été montés.Ça pourrait être dans this.$nextTick() Obtenir le temps de rendu dans la fonction de rappel.

En même temps,Il faut aussi tenir compte d'une situation.Lorsque le routage n'est pas commuté,Il peut également y avoir des changements dans les composants,Ne devrait pas être dans ces composants pour le moment mounted() Calculer le temps de rendu.Donc vous devez ajouter un needCalculateRenderTime Champ,Définir le routage comme true,Le représentant peut calculer le temps de rendu.

Collecte de données d'erreur

Erreur de chargement de la ressource

Utiliser addEventListener() Écouter error Événements,Les erreurs de chargement des ressources peuvent être saisies.

// Erreur de chargement de la ressource de capture js css img...
window.addEventListener('error', e => {
const target = e.target
if (!target) return
if (target.src || target.href) {
const url = target.src || target.href
lazyReportCache({
url,
type: 'error',
subType: 'resource',
startTime: e.timeStamp,
html: target.outerHTML,
resourceType: target.tagName,
paths: e.path.map(item => item.tagName).filter(Boolean),
pageURL: getPageURL(),
})
}
}, true)
Copier le Code

js Erreur

Utiliser window.onerror On peut écouter. js Erreur.

// Écouter js Erreur
window.onerror = (msg, url, line, column, error) => {
lazyReportCache({
msg,
line,
column,
error: error.stack,
subType: 'js',
pageURL: url,
type: 'error',
startTime: performance.now(),
})
}
Copier le Code

promise Erreur

Utiliser addEventListener() Écouter unhandledrejection Événements,Peut capturer non géré promise Erreur.

// Écouter promise Erreur L'inconvénient est que les données de colonne ne sont pas disponibles
window.addEventListener('unhandledrejection', e => {
lazyReportCache({
reason: e.reason?.stack,
subType: 'promise',
type: 'error',
startTime: e.timeStamp,
pageURL: getPageURL(),
})
})
Copier le Code

sourcemap

En général, le Code de l'environnement de production est compressé,Et l'environnement de production ne met pas sourcemap Téléchargement de fichiers.Il est donc difficile de lire les informations d'erreur de code dans l'environnement de production.Donc,,Nous pouvons utiliser source-map Pour restaurer ces messages d'erreur de code compressés.

Lorsque le Code signale une erreur,Nous pouvons obtenir le nom de fichier correspondant、Nombre de lignes、Nombre de colonnes:

{
line: 1,
column: 17,
file: 'https:/www.xxx.com/bundlejs',
}
Copier le Code

Puis appelez le code suivant pour restaurer:

async function parse(error) {
const mapObj = JSON.parse(getMapFileContent(error.url))
const consumer = await new sourceMap.SourceMapConsumer(mapObj)
// Oui. webpack://source-map-demo/./src/index.js Dans le fichier ./ Enlevez
const sources = mapObj.sources.map(item => format(item))
// Le nombre de lignes d'erreur et de fichiers source non compressés est obtenu à partir des informations d'erreur compressées
const originalInfo = consumer.originalPositionFor({ line: error.line, column: error.column })
// sourcesContent Contient le code source non compressé de chaque fichier,Trouver le code source correspondant en fonction du nom du fichier
const originalFileContent = mapObj.sourcesContent[sources.indexOf(originalInfo.source)]
return {
file: originalInfo.source,
content: originalFileContent,
line: originalInfo.line,
column: originalInfo.column,
msg: error.msg,
error: error.error
}
}
function format(item) {
return item.replace(/(\.\/)*/g, '')
}
function getMapFileContent(url) {
return fs.readFileSync(path.resolve(__dirname, `./maps/${url.split('/').pop()}.map`), 'utf-8')
}
Copier le Code

Chaque fois que le projet est emballé,Si elle est allumée sourcemap,Donc chaque js Tous les fichiers auront une correspondance map Documentation.

bundle.js
bundle.js.map
Copier le Code

À ce moment - là, js Les fichiers sont placés sur un serveur statique pour l'accès de l'utilisateur,map Les fichiers sont stockés sur le serveur,Utilisé pour restaurer les messages d'erreur.source-map .La bibliothèque peut restaurer les messages d'erreur de code non compressés basés sur les messages d'erreur de code compressés.Par exemple, la position d'erreur après compression est 1 D'accord 47 Colonnes,L'emplacement réel après restauration peut être 4 D'accord 10 Colonnes.En plus des informations de localisation,Le texte original du code source est également disponible.

La figure ci - dessus est un exemple de code qui a été restauré par erreur.Considérant que cette partie ne relève pas SDK Champ d'application,Alors j'en ai un autre. Entrepôt Pour faire ça.,Si vous êtes intéressé, vous pouvez voir.

Vue Erreur

Utilisation window.onerror C'est impossible à capturer. Vue Faux.,Il faut l'utiliser Vue Fourni API Pour écouter.

Vue.config.errorHandler = (err, vm, info) => {
// Imprimer les messages d'erreur sur la console
console.error(err)
lazyReportCache({
info,
error: err.stack,
subType: 'vue',
type: 'error',
startTime: performance.now(),
pageURL: getPageURL(),
})
}
Copier le Code

Acquisition de données comportementales

PV、UV

PV(page view) Est le nombre de pages vues,UV(Unique visitor)Accès des utilisateurs.PV Il suffit de visiter la page une fois.,UV Plusieurs visites au cours de la même journée ne sont comptées qu'une seule fois.

Pour le Front End,Chaque fois que vous accédez à la page PV C'est tout.,UV Les statistiques sont mises sur le serveur,Il s'agit principalement d'analyser les données déclarées pour les statistiques UV.

export default function pv() {
lazyReportCache({
type: 'behavior',
subType: 'pv',
startTime: performance.now(),
pageURL: getPageURL(),
referrer: document.referrer,
uuid: getUUID(),
})
}
Copier le Code

Durée du séjour de la page

L'utilisateur entre dans la page pour enregistrer une heure initiale,Soustraire l'heure initiale de l'heure courante lorsque l'utilisateur quitte la page,Est la durée du séjour de l'utilisateur.Cette logique de calcul peut être placée dans beforeunload Dans l'incident.

export default function pageAccessDuration() {
onBeforeunload(() => {
report({
type: 'behavior',
subType: 'page-access-duration',
startTime: performance.now(),
pageURL: getPageURL(),
uuid: getUUID(),
}, true)
})
}
Copier le Code

Profondeur d'accès à la page

Il est utile d'enregistrer la profondeur d'accès à la page,Par exemple, différentes pages actives a Et b.a Profondeur moyenne d'accès seulement 50%,b La profondeur moyenne d'accès est de 80%,Description b Plus populaire auprès des utilisateurs,Des modifications ciblées peuvent être apportées en fonction de ce point. a Page active.

En outre, la profondeur d'accès et la durée de séjour peuvent être utilisées pour identifier les brosses de commerce électronique.Par exemple, quelqu'un entre dans la page et tire la page en bas et attend un certain temps avant d'acheter,Quelqu'un fait défiler la page lentement.,Achat final.Bien qu'ils restent sur la page pendant la même période,Mais apparemment, la première personne est plus comme une brosse à linge..

Le processus de calcul de la profondeur d'accès à la page est un peu plus compliqué:

  1. Lorsque l'utilisateur entre dans la page,Enregistrer l'heure actuelle、scrollTop Valeur、Hauteur visuelle de la page、Hauteur totale de la page.
  2. Le moment où l'utilisateur fait défiler la page,Ça va déclencher scroll Événements,.Calculer la profondeur d'accès à la page et la durée du séjour en utilisant les données obtenues au premier point dans la fonction de rappel.
  3. Lorsque l'utilisateur fait défiler la page vers un point,Arrêtez - vous et continuez à regarder la page.L'heure actuelle est enregistrée、scrollTop Valeur、Hauteur visuelle de la page、Hauteur totale de la page.
  4. Répétez le deuxième point....

Voir le code spécifique:

let timer
let startTime = 0
let hasReport = false
let pageHeight = 0
let scrollTop = 0
let viewportHeight = 0
export default function pageAccessHeight() {
window.addEventListener('scroll', onScroll)
onBeforeunload(() => {
const now = performance.now()
report({
startTime: now,
duration: now - startTime,
type: 'behavior',
subType: 'page-access-height',
pageURL: getPageURL(),
value: toPercent((scrollTop + viewportHeight) / pageHeight),
uuid: getUUID(),
}, true)
})
// Initialiser l'enregistrement après le chargement de la page hauteur d'accès actuelle、Temps
executeAfterLoad(() => {
startTime = performance.now()
pageHeight = document.documentElement.scrollHeight || document.body.scrollHeight
scrollTop = document.documentElement.scrollTop || document.body.scrollTop
viewportHeight = window.innerHeight
})
}
function onScroll() {
clearTimeout(timer)
const now = performance.now()
if (!hasReport) {
hasReport = true
lazyReportCache({
startTime: now,
duration: now - startTime,
type: 'behavior',
subType: 'page-access-height',
pageURL: getPageURL(),
value: toPercent((scrollTop + viewportHeight) / pageHeight),
uuid: getUUID(),
})
}
timer = setTimeout(() => {
hasReport = false
startTime = now
pageHeight = document.documentElement.scrollHeight || document.body.scrollHeight
scrollTop = document.documentElement.scrollTop || document.body.scrollTop
viewportHeight = window.innerHeight
}, 500)
}
function toPercent(val) {
if (val >= 1) return '100%'
return (val * 100).toFixed(2) + '%'
}
Copier le Code

Cliquez sur

Utilisation addEventListener() Écouter mousedowntouchstart Événements,Nous pouvons recueillir la taille de chaque zone cliquée par l'utilisateur,.Cliquez sur l'emplacement exact des coordonnées sur toute la page,Cliquez sur le contenu de l'élément et d'autres informations.

export default function onClick() {
['mousedown', 'touchstart'].forEach(eventType => {
let timer
window.addEventListener(eventType, event => {
clearTimeout(timer)
timer = setTimeout(() => {
const target = event.target
const { top, left } = target.getBoundingClientRect()
lazyReportCache({
top,
left,
eventType,
pageHeight: document.documentElement.scrollHeight || document.body.scrollHeight,
scrollTop: document.documentElement.scrollTop || document.body.scrollTop,
type: 'behavior',
subType: 'click',
target: target.tagName,
paths: event.path?.map(item => item.tagName).filter(Boolean),
startTime: event.timeStamp,
pageURL: getPageURL(),
outerHTML: target.outerHTML,
innerHTML: target.innerHTML,
width: target.offsetWidth,
height: target.offsetHeight,
viewport: {
width: window.innerWidth,
height: window.innerHeight,
},
uuid: getUUID(),
})
}, 500)
})
})
}
Copier le Code

Saut de page

Utilisation addEventListener() Écouter popstatehashchange Événement de saut de page.Notez que l'appelhistory.pushState()Ouhistory.replaceState()Ne pas déclencherpopstateÉvénements.Seulement si vous faites une action de navigateur,L'événement ne sera déclenché que,Si l'utilisateur clique sur le bouton arrière du Navigateur(OuJavascriptAppelé en Codehistory.back()Ouhistory.forward()Méthodes).Même chose.,hashchange C'est pareil..

export default function pageChange() {
let from = ''
window.addEventListener('popstate', () => {
const to = getPageURL()
lazyReportCache({
from,
to,
type: 'behavior',
subType: 'popstate',
startTime: performance.now(),
uuid: getUUID(),
})
from = to
}, true)
let oldURL = ''
window.addEventListener('hashchange', event => {
const newURL = event.newURL
lazyReportCache({
from: oldURL,
to: newURL,
type: 'behavior',
subType: 'hashchange',
startTime: performance.now(),
uuid: getUUID(),
})
oldURL = newURL
}, true)
}
Copier le Code

Vue Changement de route

Vue Peut être utilisé router.beforeEach Crochet pour écouter les changements de routage.

export default function onVueRouter(router) {
router.beforeEach((to, from, next) => {
// La première page de chargement n'a pas besoin de statistiques
if (!from.name) {
return next()
}
const data = {
params: to.params,
query: to.query,
}
lazyReportCache({
data,
name: to.name || to.path,
type: 'behavior',
subType: ['vue-router-change', 'pv'],
startTime: performance.now(),
from: from.fullPath,
to: to.fullPath,
uuid: getUUID(),
})
next()
})
}
Copier le Code

Communication des données

Méthode d & apos; établissement des rapports

La Déclaration des données peut être effectuée de plusieurs façons::

La simplicité de mon écriture SDK C'est le premier.、La deuxième méthode combine la méthode de déclaration.Utilisation sendBeacon Les avantages de l'escalade sont évidents.

Utiliser sendBeacon() La méthode permet à l'agent utilisateur d'envoyer des données asynchrones au serveur lorsqu'il en a l'occasion,Sans retarder le déchargement de la page ou affecter les performances de chargement de la prochaine navigation.Cela résout tous les problèmes liés à la présentation des données analytiques:Fiabilité des données,Le transfert est asynchrone et n'affecte pas le chargement de la page suivante.

Non pris en charge sendBeacon Sous le navigateur, nous pouvons utiliser XMLHttpRequest Pour l'escalade.Un HTTP La demande comprend deux étapes d'envoi et de réception.En fait, pour l'escalade,,On doit juste s'assurer que ça sort..C'est - à - dire que l'envoi est réussi.,Peu importe si la réponse est reçue ou non..À cette fin,,J'ai fait une expérience.,In beforeunload Avec XMLHttpRequest C'est transmis. 30kb Données(Les données générales à déclarer sont rarement aussi volumineuses),Changement de navigateur,Peut être envoyé avec succès.Bien sûr.,Ceci et les performances matérielles、L'état du réseau est également pertinent.

Calendrier des rapports

Il y a trois possibilités de déclaration:

  1. Adoption requestIdleCallback/setTimeout Report du délai.
  2. In beforeunload Rapport dans la fonction de rappel.
  3. Mise en cache des données d'escalade,Une fois qu'une certaine quantité est atteinte, elle doit être signalée..

Il est recommandé de combiner les trois méthodes d'escalade.:

  1. Mettre en cache d'abord les données d'escalade,Après avoir mis en cache un certain nombre,Utilisation requestIdleCallback/setTimeout Report du délai.
  2. Soumettre uniformément les données non déclarées au départ de la page.

Résumé

Il est difficile de comprendre les connaissances théoriques,Pour ce faire, j'ai écrit un simpleSurveillance SDK,Vous pouvez l'utiliser pour écrire des DEMO,Aide à approfondir la compréhension.Lisez - le avec cet article,Ça marche mieux.

Si vous pensez que cet article vous aidera un peu,Fais - moi plaisir..Ou peut - être rejoindre mon groupe de développement:1025263163Apprendre les uns des autres,Nous aurons des réponses techniques professionnelles

Si vous trouvez cet article utile,S'il vous plaît, donnez - nous un peu de notre projet Open Sourcestar: http://github.crmeb.net/u/defu Merci beaucoup. !

版权声明
本文为[Php Development Engineer]所创,转载请带上原文链接,感谢
https://qdmana.com/2021/10/20211012210537508E.html

  1. Javascript——面向对象
  2. Liu Yifei is really not afraid to walk away? Honey peach's figure appears in a large backless dress. It's really popular
  3. 学透CSS-如何组织你的CSS代码
  4. Le chapitre de Shen jilan sur le service méritoire a pris fin, la réputation publique a diminué, mais les trois points de vue ne peuvent toujours pas être ignorés
  5. He was abandoned by his parents when he was three years old. When he grew up, he became popular in art. His biological parents came to recognize him. The outcome was very helpless
  6. Nouvelle grande expérience de prise de vue en direct 2022 Geely Howe
  7. Javascript——判断数据类型的四种方法
  8. Jouer est un jeu, perdre est la vie: le jeu du calmar du point de vue juridique
  9. "Harry Potter" is crooked and less than 166 tall! A sequel to Harry Potter?
  10. 前端面试每日 3+1 —— 第910天
  11. 前端面试每日 3+1 —— 第909天
  12. React Foundation
  13. Webpack packaging style resources and HTML resources
  14. 2021 Internet manufacturers' Java face-to-face collection, Java front-end interview questions
  15. Xiao Zhan zero sent a document to celebrate the birthday of the motherland. Millions of fans praised it one after another. The blessing copy was moving
  16. La nouvelle Volkswagen Santana rendered blue body Painted hard look est prévue pour l'année prochaine
  17. His wife has cancer and his husband is affectionate. These five male stars can be called "peerless good men"
  18. Tianjin Auto Show: Toyota's fourth generation ths architecture blessing! GAC motor's second generation gs8 pre-sale is open!
  19. Nodejs + Serverless实现LaTeX公式渲染服务
  20. vue源码分析之数据流向
  21. Algorithmes quotidiens & questions d'entrevue️⃣(1)️⃣(interviewer les commandes Linux)
  22. Analyse des questions d'entrevue: déstockage des tableaux et complexité temporelle
  23. Flux de données pour l'analyse des sources de vue
  24. Take group photos, sing songs and write blessings... Everyone on the Fuxing train celebrates the national day
  25. React核心 -- React-Hooks
  26. Mise en œuvre du Service de rendu de formule latex par nodejs + serverless
  27. React Core - React Hooks
  28. After autumn, wear less black, white and gray, and choose more gentle "light colors"
  29. Wu Qili sent blessings on the national day. She looked haggard and thin on her own. It was worrying
  30. Grand sens de la vue à la fois, la force dure est excellente, toute la série 2.0t, le film réel 2021 haver h9!
  31. With the blessing of Toyota power, GAC motor's new gs8 is open for pre-sale, starting from 188800 yuan
  32. Test drive Xingyue L: the price is less than 200000, but there are 400000 cards. Is it inevitable to sell more than 10000 a month?
  33. Entrevue de première ligne 3 + 1 tous les jours - jour 909
  34. Entrevue de première ligne 3 + 1 par jour - jour 910
  35. Xiao Zhan's 18 word blessing copy triggered a heated discussion, and fans expressed their attitude and wished the motherland together
  36. Engineering Knowledge Card 005: comment webpack injecte - t - il les ressources JS emballées dans html?
  37. ant design vue 设置表格选择框,全选按钮选不全
  38. 五分钟掌握用Vue脚手架搭建一个完整项目!
  39. 100 questions d'entrevue Python de base partie 2 (41 - 60)
  40. 五分鐘掌握用Vue脚手架搭建一個完整項目!
  41. ant design vue 設置錶格選擇框,全選按鈕選不全
  42. Cinq minutes pour construire un projet complet avec l'échafaudage vue!
  43. La vue de conception ant définit la zone de sélection de la table, le bouton sélectionner tout n'est pas sélectionné
  44. vuex中助手函数的几种使用技巧总结
  45. La nouvelle voiture roule à grande vitesse, le tableau de bord apparaît "tasse de café" prompt, directement au magasin 4S!
  46. Résumé de plusieurs techniques d'utilisation de la fonction Helper dans vuex
  47. Un disciple féminin est venu à guozijian: Zhuo Wenyuan a été complètement noirci, et le frère aîné de sang qi a été blessé par lui!
  48. JavaScript - - quatre façons de juger les types de données
  49. HTTPS|SSL笔记-SSL分手过程(Encrypted Alert)
  50. The film arrangement rate exceeds 40%, and the box office exceeds 390 million in less than two days! Why is Changjin lake?
  51. Nouvelle grande expérience de prise de vue en direct 2022 Geely Howe
  52. Apprenez à connaître CSS - Comment organiser votre code CSS
  53. Can the Zero run C11 with less than 200000 become the leader of medium-sized pure electric SUV?
  54. Https | SSL Notes - SSL break Process (encrypted Alert)
  55. HTTPS|SSL筆記-SSL分手過程(Encrypted Alert)
  56. Récemment, j'a i été interrogé par des fans sur les génériques Java, donc j'ai regardé en arrière et j'espère que l'entrevue de mon frère cadet se passera bien.
  57. Comment utiliser la nouvelle spécification es dans votre projet
  58. JavaScript - - orienté objet
  59. South Korean actress Liu Renna, wearing a white strapless skirt and walking on the red carpet, has a hot body and attracts eyes. She smiles so sweet
  60. Nginx (3): process model