白屏骨架屏監(jiān)控

調(diào)研過程

  1. 發(fā)現(xiàn)了幾個有意思的api
  • Document.elementsFromPoint():可以獲取到當(dāng)前視口內(nèi)指定坐標(biāo)處,由里到外排列的所有元素
  • HTMLElement.innerTextinnerText 很容易與Node.textContent混淆, 但這兩個屬性間實際上有很重要的區(qū)別. 大體來說, innerText 可操作已被渲染的內(nèi)容, 而 textContent 則不會
  • Node.nodeType只讀屬性Node.nodeType 表示的是該節(jié)點的類型
  • Document.getElementsByClassName():返回一個包含了所有指定類名的子元素的類數(shù)組對象
  • CanvasRenderingContext2D.measureText():返回一個關(guān)于被測量文本TextMetrics 對象包含的信息(例如它的寬度)
     let ctx = this.container.getContext('2d'); // canvas 上下文
     let width= ctx.measureText(name).width;
    
  1. 前端監(jiān)控幾個方法
  • 如果每次都去遍歷新增元素并計算是否可見是非常消耗性能的。實際上采用的是深度優(yōu)先算法,如果子元素可見,那父元素可見,不再計算。 同樣的,如果最后一個元素可見,那前面的兄弟元素也可見。通過深度優(yōu)先算法,性能有了大幅的提升

  • mutationObserver:通過這個API可以獲得頁面狀態(tài)變化

  • 加載白屏?xí)r間可以通過first meaningful paint來判定,(first meaning paint, 也就是主要內(nèi)容可見時間)

  • Hook宿主方APP里所有UIWebViewdelegate事件,獲取網(wǎng)頁加載結(jié)束的時機(jī)。截取網(wǎng)頁圖像,然后將完整的圖像壓縮成一定比例,一般取橫向7個像素,縱向根據(jù)原比例得出,比如原網(wǎng)頁視圖700*1000,壓縮之后的圖像大小:7*10。遍歷圖像上的像素,是否全為白色點,以此確定網(wǎng)頁是否白屏,埋點上報

我的思路:

以下是判斷白屏、骨架屏方法:

  • 首先調(diào)查下目前的骨架屏方案有哪些,如果都是dom節(jié)點,就遍歷所有的dom判斷里邊是否有文字,由于這種方案會同時判斷白屏,可以通過形狀來把白屏和骨架屏區(qū)分開,或者判斷不是白屏就是骨架屏

注意點:

  1. 有的頁面頂部有走馬燈,這樣可能會影響骨架屏的判斷
  2. 有的插件引入后會中帶有默認(rèn)文字,這樣會影響骨架屏的判斷,可以動態(tài)傳入頁面id來解決
let root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || window || {};
setTimeout(() => {
  skeletonscreen()
}, 5000);

//  遞歸函數(shù),獲取頁面dom數(shù)組
function countNodes (node, deepArr) {
  node = node || document.body
  //  計算自身
  let arr = deepArr ? deepArr : []
  //  判斷是否存在子節(jié)點
  if (node.hasChildNodes()) {
    //  獲取子節(jié)點
    var cnodes = node.childNodes;
    //  對子節(jié)點進(jìn)行遞歸統(tǒng)計
    for (var i = 0; i < cnodes.length; i++) {
      countNodes(cnodes.item(i), arr)
    }
  } else {
    arr.push(node)
  }
  return arr;
}

//  統(tǒng)計body的節(jié)點
let nodesArr = countNodes()
console.log('nodesArr', nodesArr)

let isNoTxt = nodesArr.some((item, index) => {
  return item.nodeValue && item.nodeValue.replace(/\s*/g, "")
})


let seletonseletonCoordinates = []

//計算每個色塊的面積
const linkSum = (i, j, num) => {
  //走過的路就置0
  coordinates[i][j] = 0;

  num++;
  //向上
  if ((i + 1 < h) && coordinates[i + 1][j] == 1) {
    num = linkSum(i + 1, j, num);
  }
  //向右
  if ((j + 1 < w) && coordinates[i][j + 1] == 1) {
    num = linkSum(i, j + 1, num);
  }
  //向下
  if ((i - 1 >= 0) && coordinates[i - 1][j] == 1) {
    num = linkSum(i - 1, j, num);
  }
  //向左
  if ((j - 1 >= 0) && coordinates[i][j - 1] == 1) {
    num = linkSum(i, j - 1, num);
  }
  return num;
}


//計算總塊數(shù)和灰色區(qū)域總面積
const getCountAndArea = () => {
  let sum = [];
  let count = 0;
  for (let i = 0; i < h; i++) {
    for (let j = 0; j < w; j++) {
      //連續(xù)1的個數(shù)
      if (seletonCoordinates[i][j] == 1) {
        let buf = 0;
        buf = linkSum(i, j, buf);
        count++;
        sum.push({
          index: count,
          area: buf
        });
      }
    }
  }
  return {
    count,
    sum
  };
}

//根據(jù)顏色判斷
function skeletonscreen () {
  if (root.html2canvas) {
    html2canvas(document.body, {
      backgroundColor: null,   //設(shè)置截圖的背景色
      useCORS: true, // 如果截圖的內(nèi)容里有圖片,可能會有跨域的情況,加上這個參數(shù),解決文件跨域問題
      allowTaint: false, //允許跨域(圖片跨域相關(guān))
      taintTest: true, //是否在渲染前測試圖片
    }).then((canvas) => {
      let ctx = canvas.getContext("2d");
      const ratio = root.devicePixelRatio
      const width = document.body.clientWidth * ratio
      const height = document.body.clientHeight * ratio / 3
      let img = ctx.getImageData(0, 0, width, height);
      let sumPx = 0
      const imgdata = img.data
      let r, g, b
      let x = 0, y = 0
      //設(shè)置二維數(shù)組
      for (let i = 0; i < height; i++) {
        seletonCoordinates[i] = []
      }
      for (var i = 0; i < imgdata.length; i += 4) {
        r = imgdata[i];
        g = imgdata[i + 1];
        b = imgdata[i + 2];
        if (r === 255 && g === 255 && b === 255) {
          sumPx += 1
          seletonCoordinates[y][x] = 0
        } else {
          seletonCoordinates[y][x] = 1
        }
        x++
        if (x > width) {
          x = 0
          y++
        }
      }
   
      let rst = getCountAndArea();
      if (rst.count > 3 && !isNoTxt) {
        console.log('頁面骨架屏')
      }
    })
  } 
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容