busuanzi 访问量统计与 live2d 插件同时使用导致 busuanzi 不显示的根本原因以及解决方法

昨天在一位网友的 Hexo 博客遇到了很诡异的 busuanzi 访问量统计不显示问题经过一番研究发现是 busuanzi 代码的一处问题和 live2d 插件的一处问题凑在一起导致的

今天在写博客之前先搜了一下 busuanzi live2d发现搜出来一堆结果我还以为我白研究了..结果点进去一看第一页结果里没有一个指出了问题的根本原因而且修复方法基本上都是删 feature 或者换组件但实际上只要知道问题的根本原因就可以在不妨碍正常功能运作的前提下进行修复所以昨天晚上没白忙活我这篇博客还是要写的

当然我只看了第一页搜索结果可能有更深入研究的文章被搜索引擎埋没了我这篇文章说不定也不会被搜到只不过既然第一页没有就让我也来成为被搜索引擎埋没的一员这样的话说不定被搜到的可能性就增加了

问题描述

在一个同时启用了 busuanzi 访问量统计和 live2d 插件的 Hexo 博客里访问或刷新博客时有大概率 busuanzi 会被隐藏

被隐藏的具体表现为刷新的瞬间 id 为 busuanzi_container_* 的容器是显示的然后容器的样式很快被设为 display: none

查看 F12 的 Networkbusuanzi 相关请求正常返回console 中没有报错

问题定位

这个过程中我也走了一些弯路就不写出来了

设置 display: none 的定位

查看 busuanzi.pure.mini.js发现 display: nonehides 函数中被设置

hides: function() {
  this.bszs.map(function(a) {
    var b = document.getElementById("busuanzi_container_" + a);
    b && (b.style.display = "none")
  })
},

而 hides 函数仅在一处被调用

try {
  a(b), scriptTag.parentElement.removeChild(scriptTag)
} catch (c) {
  bszTag.hides()
}

所以是在 a(b), scriptTag.parentElement.removeChild(scriptTag) 抛出异常时容器被隐藏

异常原因的定位

由于在其它地方的 busuanzi 不会出现这一问题而问题的原因还完全不清楚为了尽可能还原原始环境以复现问题我选择了使用 Firefox 的 Header Editor 插件 直接在原博客进行测试具体来说就是在本地复制一份 busuanzi 的代码然后跑一个 http server在 Header Editor 里把 busuanzi 代码的请求重定向到本地的 http server

首先catch 中添加 console.error(c)得到错误内容 TypeError: scriptTag.parentElement is null

查看代码中 scriptTag 相关的部分

scriptTag = document.createElement("SCRIPT"), scriptTag.type = "text/javascript", scriptTag.defer = !0, scriptTag.src = a, scriptTag.referrerPolicy = "no-referrer-when-downgrade", document.getElementsByTagName("HEAD")[0].appendChild(scriptTag)

所以再在 catch 中添加 s = document.querySelector("[src*=BusuanziCallback]"); console.log(s.parentElement);发现 s.parentElement 不是 null

再添加 console.log(s === scriptTag)发现结果是 sscriptTag 不同所以原因在于此时的 scriptTag 变量已经不指向 DOM 中的这个元素了

定位到 live2d 插件

此时我原本是没有任何头绪的但我发现并不是每次刷新页面都会触发这一问题所以没有触发问题时和触发问题时的差别就成了问题的突破口

经过多次刷新我发现有一串 console 输出在出现问题时总位于我在 catch 中添加的调试信息之前而在没出现问题时则位于调试信息之后

Live2D 2.1.00_1 live2d.core.js:5925:16
profile : Desktop live2d.core.js:5913:16
  [PROFILE_NAME] = Desktop live2d.core.js:5918:20
  [USE_ADJUST_TRANSLATION] = false live2d.core.js:5918:20
  [USE_CACHED_POLYGON_IMAGE] = false live2d.core.js:5918:20
  [EXPAND_W] = 2

多亏了 live2d 的这串输出我得以将问题定位到 live2d 插件上

实际上如果没有这些 console 输出也可以通过 <head>L2Dwidget.0.min.jsbusuanzi?jsonpCallback=BusuanziCallback 两个 <script> 的相对位置发现问题只不过这样的话就更隐蔽更难发现了

在 live2d 插件中定位问题

L2Dwidget.min.js 的第一行有源码地址以及时间/*! https://github.com/xiazeyu/live2d-widget.js built@2019-4-6 09:38:17 */

因为注释中给出的时间不是最新版本先查看 git log 并 checkout 到相应时间的版本

既然问题在于 <script> 元素被重新创建而导致原变量不指向 DOM 中元素就在代码中 grep head然后发现 问题代码document.head.innerHTML += ……

至此问题原因已发现就是 live2d 插件通过修改 document.head.innerHTML 来添加样式导致 busuanzi 的 scriptTag 变量指向的不再是 DOM 中的 <script> 元素

实际上live2d 插件的这一问题 已经修复但需要使用新版才行

解决方法

修改 busuanzi 的解决方法

因为 busuanzi 的代码较短而且本来就是用的外部的代码改起来比较容易

scriptTag.parentElement.removeChild(scriptTag) 修改为 s=document.querySelector('[src*=BusuanziCallback]'),s.parentElement.removeChild(s) 即可可以把修改后的静态文件放在博客里然后修改 busuanzi <script>src

网上搜到的很多解决办法是把 id="busuanzi_container_*" 删掉这样当然就不会被隐藏了只不过这个隐藏本意是在出错时不把错误或者无意义的内容显示给访客保留这一行为还是挺好的

修改 live2d 的解决方法

总之就是把 innerHTML += 换成 createElementappendChildxiazeyu/live2d-widget.js#61 改就行

live2d 的代码还是挺长的直接修改 minify 后的代码不太好如果是 hexo 插件的话要修改应该也蛮麻烦的总之如果你知道怎么改比较好的话可以改不然的话还是推荐改 busuanzi

问题启示

不要修改原 DOM 中的 innerHTML

直接修改 DOM 元素的 innerHTML 会让其中的元素都重新渲染加载?创建?不仅指向其中元素的变量会失效也可能导致画面闪烁等问题例如导致 live2d-widget 修复这一问题的不是 busuanzi 失效而是 CSS 闪烁

所以如果是添加 DOM 元素应当避免修改 innerHTML而应当使用 document.createElementNode.appendChild() 以及 removeChildreplaceChildinsertBefore 等 API

不要依赖于指向 DOM 元素的变量长时间不改变

如果 DOM 因各种原因部分重建指向 DOM 元素的变量很可能不再指向当前 DOM 中的元素所以最好不要在过了一段时间后例如在 callback 中再次使用指向 DOM 元素的变量而应当再次获取这一元素

不要 silently fail

在处理异常尤其是未知的异常时即使不 throw 出去也最好用 console.error 等方法记录下来记录在 console 中的错误信息并不会显示给普通用户但可以给寻找问题所在的用户提供宝贵的提示信息

使用 Header Editor 在对原环境最小修改的情况下进行调试

这次使用 Header Editor 调试还是我临时想到的知道有这么个插件还是以前用 mahjong-helper当然现在已经没在用了一开始还用 pastebin 上传代码效率极低后来才想起来本地跑个 http server 就可以了..用这个插件来调试还是挺方便的

留心依赖版本

一开始我还在 GitHub 上搜到一个 busuanzi.pure.js调了一会儿才发现这个代码和 https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js 不一样..

live2d 的版本也要注意开头包含时间的注释因为最新版本已经把问题修复了