最近,我一直痴迷于通过延迟加载优化性能。最近,我写了关于如何 延迟加载 Google 地图 以及如何 延迟加载响应式 Google Adsense 的文章。现在该轮到 Disqus 了,这是一项在您的网站上嵌入评论的服务。这是一项很棒的服务。它消除了开发您自己的本地评论系统、处理垃圾邮件等的麻烦。最近,我一直在一个项目中实施这个小部件。
问题
从布局上看,评论通常扮演次要角色。在许多情况下,访客根本看不到评论,因为他们不会滚动到评论所在的位置。但猜猜看?默认情况下,无论如何它们都会加载到网站上。浪费的带宽是一个问题。
看看 Disqus 官方推荐的技术实现
<div id="disqus_thread"></div>
<script>
(function() {
var d = document, s = d.createElement('script');
s.src = '//username.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
他们这么说:“将以下代码放在您希望 Disqus 加载的位置”。假设您是一位优秀的开发者,并且通常在结束标签 </body>
之前插入所有 <script src="..."></script>
片段。然后,有一天,您决定在您的网站上实现 Disqus 评论,并将上述代码放在文档中间的评论部分。
会发生什么?开始下载的第一个 JavaScript 文件是 username.disqus.com/embed.js
。这并不一定意味着它将首先下载,但它是浏览器关注的 JavaScript 文件列表中的第一个。难道不应该将第一个文件保留给您网站的主要 JavaScript 文件吗?当您的主要 JavaScript 文件加载延迟时,可能会出现许多问题(例如“休眠”的 <button>
等),尤其是在您开发该网站时没有遵循优雅降级或渐进增强原则的情况下。
这还会干扰您网站上的其他外部资源,例如图像和 CSS 文件。想象一下,您在 2G 网络条件下使用智能手机,并且正在等待评论小部件加载,因为您是为了看小猫的照片而来的。
我做了一个测试。事实证明,带有零条评论的 Disqus 小部件重达2.49 MB!一堆 JavaScript、CSS、图像和字体文件的网络请求,在许多情况下不合理地减慢了为您网站的其他可能至关重要的部分或功能提供服务的效率。

解决方案:小型 JavaScript 插件
为了能够延迟加载 Disqus,我开发了一个小型 JavaScript 插件来完成这项工作。无论评论区域位于视口的上方还是下方,如果没有必要,它都不会加载。

插件本身是一小段 JavaScript 代码。我制作了两个版本:原生版本和 jQuery 版本。我称之为disqusLoader。您可以在这里获取这些文件
- disqusloader.js;无依赖项(IE 9+)。
压缩并 gzip 后大小为 779 字节。 - jquery.disqusloader.js;依赖于 jQuery。
压缩并 gzip 后大小为 569 字节。
以下是设置方法。首先,您需要将一个元素插入到 HTML 中,您希望评论部分位于该元素中
<div class="disqus"></div>
然后,像这样初始化插件
// vanilla
disqusLoader( '.disqus', { scriptUrl: '//username.disqus.com/embed.js' });
// jQuery
$.disqusLoader( '.disqus', { scriptUrl: '//username.disqus.com/embed.js' });
“太好了,但是 Disqus 特定的配置怎么办?”您可能会问。当然,还有一个可用的参数可以接受 Disqus 原生值。还有一些其他与插件相关的选项
var options =
{
scriptUrl: '//username.disqus.com/embed.js',
/*
@type: string (url)
@default: none
@required
URL of Disqus' executive JS file. The value is memorized on the first function call
and ignored otherwise because Disqus allows only one instance per page at the time.
*/
laziness: 1,
/*
@type: int (>=0)
@default: 1
Sets the laziness of loading the widget: (viewport height) * laziness . For example:
0 - widget load starts when at the least a tiny part of it gets in the viewport;
1 - widget load starts when the distance between the widget zone and the viewport is no more than the height of the viewport;
2 - 2x viewports, etc.
*/
throttle: 250,
/*
@type: int (milliseconds)
@default: 250
Defines how often the plugin should make calculations during the
processes such as resize of a browser's window or viewport scroll.
250 = 4 times in a second.
*/
/*
@type: function
@default: none
Disqus-native options. Check Disqus' manual for more information.
*/
disqusConfig: function()
{
this.page.title = 'Page Title';
this.page.url = 'http://url.to/your-website';
this.page.identifier = 'unique-identifier';
}
};
// vanilla
disqusLoader( '.disqus', options );
// jQuery
$.disqusLoader( '.disqus', options );
自己查看一下
您也可以在 GitHub 上参与贡献或关注该项目。
Disqus 回调函数
回调函数很棒,因为您可以对用户的操作做出反应。Disqus 官方文档中只有一种回调函数。查看 `embed.js` 文件的源代码,您可以看到更多预定义类型的回调函数

但是,看起来只有两个启用了:onNewComment
和 onReady
。这足以实现微小但明显的改进:加载指示器。
补充加载指示
加载 Disqus 小部件通常包括两个部分
- 加载 `embed.js` 文件
- 加载内部资源并执行其他类型的网络请求
Disqus 本身负责第二步,他们使用动画图像来指示这一点。但是第一步呢?加载外部 JavaScript 文件可能需要数十秒,原因有很多。这里的问题是,无论网络状况如何,用户都会被告知您的网站上有一个评论功能。用户体验在于细节!

技术方法很简单:一个新的 HTML 元素和一个 JavaScript 回调函数,用于帮助隐藏该元素
<div class="disqus-placeholder">Loading comments...</div>
<div class="disqus"></div>
// vanilla
disqusConfig: function()
{
this.callbacks.onReady = [function()
{
var el = document.querySelector( '.disqus-placeholder' );
if( el.classList )
el.classList.add( 'is-hidden' ); // IE 10+
else
el.className += ' ' + 'is-hidden'; // IE 8-9
}];
}
// jQuery
disqusConfig: function()
{
this.callbacks.onReady = [function()
{
$( '.disqus-placeholder' ).addClass( 'is-hidden' );
}];
}
.disqus-placeholder.is-hidden { display: none; }
您可以在演示页面上 查看其运行效果。但首先,清除浏览器的缓存并限制网络速度。
一次加载多个实例或站点?
在研究这项技术时,我发现了一些今天存在的重要的限制……
在一个页面上无法使用多个脚本源
一旦脚本文件(例如 //username.disqus.com/embed.js
)加载,就会创建全局变量 window.DISQUS
,但前提是它之前没有设置过(这是一个不好的迹象,但让我们深入了解一下)。所以我做了一个测试。我从源脚本 A 初始化了小部件。然后为将来的变量 window.DISQUS = undefined
释放了一些空间,并初始化了源 B 的小部件。但是,结果一团糟:回调函数被多次触发,评论被复制等。显然,Disqus 的当前代码库并非旨在支持多个变量并与每个小部件实例单独操作。
在一个页面上无法同时使用多个小部件
在 DISUQS
对象中有一个可用的公共 JavaScript 方法函数 reset()
可用。如果您有任何关于 Disqus 的技术经验,您可能知道小部件被插入到一个元素中,该元素的 id
值为 disqus_thread
。我用两个元素做了一个测试:在第一个元素中加载小部件,删除 ID 属性,并将其附加到第二个元素。最后,我调用了 reset 函数,期望第二个实例出现在第一个实例旁边。但是,调用该函数来加载新实例也会破坏任何之前初始化的小部件。不幸的是,今天 Disqus 仅设计为在给定时间内使用单个实例。
可以实时重新加载小部件
有一个好消息!即使无法同时拥有多个小部件实例,您仍然可以销毁旧的实例并加载新的实例。让我们用最典型的情况——**选项卡**——来将这个理论付诸实践。您需要做的就是在每次激活新选项卡时调用插件。
<div class="tabcontent" data-disqus-id="venus" data-disqus-title="Venus"></div>
<div class="tabcontent" data-disqus-id="earth" data-disqus-title="Earth"></div>
<div class="tabcontent" data-disqus-id="mars" data-disqus-title="Mars"></div>
// call this function every time a tab is clicked:
var initDisqus = function( content )
{
disqusLoader( content,
{
scriptUrl: '//username.disqus.com/embed.js',
disqusConfig: function()
{
this.page.identifier = content.getAttribute( 'data-disqus-id' );
this.page.title = content.getAttribute( 'data-disqus-title' );
}
});
}
您可以查看实际效果或在演示页面上查看完整代码。
结束语
这篇文章并不是关于Disqus的缺陷。而是关于我们开发者犯的错误。我们的世界充满了工具,如何使用它们取决于我们自己。尽管这些工具解决了特定问题,但如果我们在实施时没有采取适当的措施,它们通常还会带来一些其他问题。每次选择最简单的路径都会导致用户流失、转化率下降和跳出率上升。您已经可以延迟加载Disqus、Google Adsense、Google Maps、社交媒体按钮,您还可以开发和分享自定义技术。请负责任!
非常棒!!!我认为客户的Disqus中有一些类似的东西。老实说,我以前从未想过这是一个大问题,因为我也没有测试过Disqus的加载速度。我们实际上建议只有在用户选择的情况下才加载评论,这样自托管或外部服务评论的成本就会降低,并且必须有相应的意图,但这是一个真正引人注目的替代方案,它具有许多相同的好处,并且可以帮助那些可能说出或分享精彩内容但出于某种原因没有点击的用户。
这是一个很棒的想法,不知何故我从未想过。与Lewis Cowles所说类似,在我看到这篇文章之前,我从未想过这个问题。一旦话题被提出,我就开始测试加载速度,在很多情况下,它比整个页面的资源加起来还要大。
还有关于人们将脚本放在他们希望显示评论的位置而不是优先放在页脚的说明……是的,我确实这么做了。
非常感谢这个项目以及这篇文章。我延迟加载了很多东西,但出于某种原因,评论从未进入我的考虑范围,现在我有了可以帮助我实现它的东西。谢谢!
我没有看到你所说的情况。Disqus在我的网站上并没有占用2.49 MB(更像是250kb)。
此外,虽然页面加载时会加载Disqus的某些部分,但评论本身已经延迟加载,只有当它们显示在视口中时才会加载。至少在我的Chrome浏览器上是这样的……
四分之一兆字节……想想看(无论最初引用的尺寸是多少)。在缓慢的连接或3G或不稳定的1格WiFi下,250kb并不是最佳体验。只有在需要时加载肯定会改善他们的体验,不是吗?
完全同意TrueTheWeb的观点。
我实际上对这篇文章中引用的2.49MB感到惊讶,并去我的网站上检查了一下。
显然这是虚假宣传。文章应该说明网站的正确负担,并让读者判断这是否可以接受。
这完全不可接受。
对于普通用户来说,Disqus脚本加载大约814.32KB的数据(没有评论的评论块)。如果您以管理员身份登录Disqus系统,则需要下载超过2MB的数据。
可以实现这个功能吗?
当然可以,但最好添加那个轻量级的依赖项……哈哈
太酷了
您正在检查视口。
但第二重要的是URL
例如,如果您发送指向评论的链接,它通常看起来像
URL#comment-2924759399
。在这种情况下,由于只加载了Disqus,它会滚动页面到评论处。
您的插件缺少此功能,即检查URL是否指向评论。
window.location.hash
是您的朋友。也许这个插件需要一些贡献。我非常确定您可以找到视口测试,并在页面加载完成后,如果存在与正则表达式/[#]comment[-]\d+/
匹配的哈希值,则触发插件。它还需要一个回调函数,在加载后滚动到评论,以及一些状态,以便您只检测一次评论,但这对于作者来说几乎算不上什么,您只需要做一点工作和思考……刚刚将延迟加载脚本添加到我们的Disqus评论中(示例在此处)。总的来说,工作做得很好,实现了它的承诺。不过,在添加脚本时,我还有一些观察结果,供其他人参考
在您的说明中,您提到要创建一个DIV“。您应该补充说明,脚本随后会动态地将`id=disqus_thread`注入到该DIV中。我正在向DIV添加我自己的自定义ID,因为我希望它充当Disqus容器的包装器并动态显示/隐藏它,但控制台一直返回`document.getElementByID(…)`为空。我花了一些时间才意识到发生了什么。
如果`.disqus` DIV的样式最初设置为`display:none`,则脚本将不会延迟加载评论,而只是在页面加载后立即加载它。同样,我试图在页面上最初隐藏评论包装器,但由于这种行为,我不得不绕过它。
有时评论会在用户接近页面上的评论容器之前加载。例如,在这个页面上,向下滚动一半就会延迟加载评论。
感谢您的辛勤工作!
您好,我是Disqus。
首先,感谢您使用Disqus并花时间改进其开发人员体验。当我阅读您的文章时,我意识到有一些点可能需要来自一级来源的澄清,所以这里有它们:)
主要不是因为Disqus使用的所有资产和脚本都得到了非常大量的缓存,所以您实际上并没有浪费太多带宽,如果有的话。
完全误导。首先,浏览器会将脚本附加到其优先级列表的末尾,因为从脚本创建的脚本元素是异步加载的。这就是我们使用内联脚本方法的原因。如今,使用常青浏览器,我们只需添加`async`属性即可获得相同的好处,并获得浏览器的向前扫描,以便它更早地检测到文件(但仍然将其放在低优先级队列中,因为`async`属性)。您可以在Ilya Grigorik的这篇优秀文章中了解更多信息:https://www.igvita.com/2014/05/20/script-injected-async-scripts-considered-harmful/
这再次完全错误。您看到的内容可能是我们扩展的社区功能的预加载,这些功能仅在页面上的所有内容(包括整个Disqus评论小部件)加载完成后才会预加载。这只会发生在桌面浏览器上,因为为了节省移动设备上的带宽,并且因为我们实际上无法在移动设备上显示侧边栏。对于一个充满评论的页面,核心大小约为220kb。这包括embed.js、加载微调器和许多其他内容。此外,这被大量缓存,所以您只需支付一次此价格。即使在不同页面上的任何后续加载也便宜得多,因为您几乎所有内容都在缓存中。
我还想补充一点,为了进一步优化,我们实际上会延迟加载所有图像(包括用户头像)、丰富的媒体内容(如YouTube嵌入等),除非您向下滚动,这样我们认为您很可能会看到它们。我们对代码突出显示功能也做了类似的事情:除非我们知道评论中存在要突出显示的代码,否则我们根本不会加载该库,这样您就不会为此付出代价。
您始终可以在您的`#disqus_thread` div中放置一个字符串,例如“Disqus评论”,它将自动替换为实际内容。我们过去确实有一个文本,上面写着“由Disqus提供支持的评论”,其中Disqus部分是一个链接,但后来人们认为我们试图为Google获取反向链接,因此我们将其删除了(不,我们没有试图为Google排名获取反向链接,我们只是错过了`rel=nofollow`属性)。
并非如此。将
window.DISQUS
设置为null
不会使已存在且已执行的代码消失。它只是让这些代码难以或无法访问。这就是为什么当我们意识到您已经在页面上加载了 Disqus 时,我们会故意跳过重新定义事物。这可以节省您的页面,而不是我们:)实际上,无需从上一个元素中删除 id,然后将其赋予一个新的元素。有一个非官方支持的(但现在有效并且已经有效一段时间)的
disqus_container_id
,您可以设置想要 Disqus 加载到的容器的 id。再次强调,这不是官方支持的,因此如果有一天它停止工作,我们会提醒您:)这是为了防止泄漏,因为
DISQUS.reset
,顾名思义,旨在重置小部件而不是创建新的。代码中有一些部分假设它将是页面上唯一活动的 Disqus 小部件,但整体架构实际上允许有多个小部件。我们主要担心的是可用性方面,因此我们关闭了这条路径。我们很想知道您为什么想在同一页面上同时加载多个小部件。是的,正如您建议的那样,这已在公开文档中记录,我们在其中告诉人们有关
DISQUS.reset
的信息。这样做存在的危险是,使用与之前相同的 URL 的多个标识符很容易破坏您的主题。您的评论主题将被拆分,并且难以恢复。有关更多信息,请参阅此处:https://help.disqus.com/customer/en/portal/articles/2158629-use-configuration-variables-to-avoid-split-threads-and-missing-comments像 Disqus 这样的公司存在是为了让您少担心一件事。我们是一支敬业且经验丰富的工程师和其他人员组成的团队,他们非常关心加载性能、与我们嵌入的页面良好配合、尽可能减小占用空间以及提供出色的缓存和用户体验。我们并不完美,是的,但至少我们是一支致力于让您页面和体验的这一部分变得很棒的团队,因此您无需担心。您可以放心,我们负责任,并且在可预见的未来将保持这种状态;)