关于博客中不蒜子与live2D冲突的解决及其根因查找

不蒜子统计数据不展示

很久以前——至少是一年前——我就知道了博客中的「不蒜子」统计功能有些问题,具体原因不记得了,仅仅是记得在哪里看到过,说是和博客主题中使用的另一个什么功能有关。嗯,现在我知道了,是启用了「live2D」导致的。
网络上有很多解决方案,我也不想再一个个写出来了,如果你想的话,可以直接滑到最下面的参考链接,去看看别人写的具体修复方法。

如果你除了直接解决之外,还想大概了解一下原因的话,那么不妨继续往下看。

在博客的模板源代码中,不蒜子的统计信息所在的DOM,默认是 display: none 隐藏的,等待不蒜子获取到数据之后,分别查找对应的DOM并填充数据之后,会修改样式为 display: inline 进而展示出来。
这就是正常情况下的步骤。
那么不正常的情况呢?看一下不蒜子的这段代码吧。

1
2
3
4
5
6
7
8
ready(function() {
try {
a(b),
scriptTag.parentElement.removeChild(scriptTag)
} catch (c) {
bszTag.hides()
}
})

上面的 a(b) 函数调用,其中的参数 b 就是统计数据对象,而函数 a 做的事情就是前面说的填充数据而后展示元素。这一步是没问题的,有问题的是下一段代码 scriptTag.parentElement.removeChild(scriptTag)。在未启用「live2D」时,这段代码正常运行;而启用了「live2D」后,这段代码出现了错误,所以程序进入了错误捕获的代码块 bszTag.hides(),这个方法就是隐藏统计数据所在元素的方法。

不蒜子的对象方法

如此看来,你应该也就明白可以如何来修复它了吧。

解决方案

我查到的修复方式不止一种,但说到底,最终针对的也都是上面提到的错误之处。

有人添加一步判断 scriptTag.parentElement && scriptTag.parentElement.removeChild(scriptTag),来确保代码不会出错;
另一部分则是去掉统计数据所在元素的容器元素,以 NexT 主题为例,找到 layout/_partials/footer.njk 文件,在文件中找到 <div class="busuanzi-count"> 标签,把这个标签下的直接子元素的 ID busuanzi_container_xxxx 删除,以及 layout/_partials/post/post-meta.njk 文件中 ID 为 busuanzi_container_page_pv 的元素删除该 ID 属性。这一种方案的本质其实就是让 bszTag.hides() 失去效果;从上面的截图可以看到, hides() 方法是通过容器元素的ID来找到DOM并设置隐藏的,找不到对应的容器元素,自然也就无法再将其隐藏了。
不蒜子容器ID

那么到这里就结束了……吗?

探究根因

很显然并没有。刚刚讲了为什么不蒜子统计会被隐藏掉,但是深究一下,上面的那段代码为什么会出现错误?为什么在启用 live2D 之前没有问题,启用了 live2D 之后就出错了呢?

如果仅仅只看不蒜子的代码,完全不会想到它有什么问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
bszCaller = {
fetch: function(a, b) {
var c = "BusuanziCallback_" + Math.floor(1099511627776 * Math.random());
window[c] = this.evalCall(b),
a = a.replace("=BusuanziCallback", "=" + c),
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)
},
evalCall: function(a) {
return function(b) {
ready(function() {
try {
a(b),
scriptTag.parentElement.removeChild(scriptTag)
} catch (c) {
bszTag.hides()
}
})
}
}
},
bszCaller.fetch(/* busuanzi jsonp fetch url */, function(a) {
bszTag.texts(a),
bszTag.shows()
}),

不蒜子创建了一个 <script> 标签,然后添加了请求地址,通过 JSONP 的方式获取到统计数据,在设置完数据后,把添加的这个标签移出DOM。这么看下来,是不是完全没有问题?

既然不蒜子的代码看起来没有问题,而且只有启用「live2D」才出现错误,那么自然也需要查看一下「live2D」的源码了。不过我其实是先找到了可能的原因,而后再从 live2D 的代码中搜索关键词查找出来的,毕竟 live2D 的代码都是压缩后的,我也没有一点点分析的耐心。

我是这么想的,不蒜子出现问题时,错误的源头是 scriptTag.parentElement 为空,也就是说 scriptTag 元素不存在父元素。这一点其实很可疑,甚至一度让我怀疑人生:明明前面刚把它添加到 <head> 元素里面,现在你告诉我说,它没有父元素了?

经过了一段时间的自我怀疑、代码检查、debug之后,我终于发现了问题所在:在代码执行到 scriptTag.parentElement.removeChild(scriptTag) 这一句时,scriptTag<head> 里面那个之前创建的 <script> 标签已经不是同一个 DOM 对象了!

1
2
3
4
5
6
7
8
scriptTag = document.createElement("SCRIPT"),
// 创建时增加 ID 属性
scriptTag.id = 'BusuanziScriptTag',
// ...

// 移除代码之前,检查一下是不是同一个 DOM
console.debug(scriptTag === document.head.getElementById('BusuanziScriptTag')); // false
scriptTag.parentElement.removeChild(scriptTag)

是不是很不可思议?但是结果就是这样,明明应该是同一个元素的,为什么会不一样呢?是什么原因改变了它?
我能想到的答案就是,<head> 元素的内容被重写了。被谁、如何重写的?答案是 innerHTML 属性。
是的,就是它,innerHTML,这个属性返回 DOM 元素内的 HTML 标签字符串文本,也可以把一段 HTML 字符串添加到 DOM 元素中解析为它的子元素。

而现在,我知道了它的另一个特性,被 innerHTML 重写过的内容,其子元素会产生一个新的 DOM 对象。

于是,找到了问题可能的答案,我就在 live2d-widget 这个包的代码中搜索了 innerHTML 这个关键词,然后找到了一段代码:
live2d使用innerHTML在head中添加样式

于是疑惑终于解除了,我们也都学习到了新的知识点,innerHTML 属性的修改会产生新的 DOM 对象,并导致原本的 DOM 对象被移除无效化。

参考链接

关于和不蒜子计数冲突的问题
Hexo博客不蒜子和live2d看板娘冲突问题解决方案
解决不蒜子(busuanzi)不显示数字问题记录
修补hexo-next不蒜子不显示问题
Hexo NexT 主题不蒜子相关问题汇总