Caupona 分茶 那篇文章中有着大量较大图片,直接全部加载会导致网页加载缓慢影响阅读,所以尝试为 Typecho 加了一点新功能

原理

在页面载入时,所有图片的 URL 都存储在 img 标签的 data-src 属性中,而 scr 属性则显示占位图:

<img data-src="/images/xxx.png" src="/images/loading.svg">

当图片在可视范围内时,将 data-src 属性的值赋给 src 属性,完成加载:

<img data-src="/images/xxx.png" src="/images/xxx.png">

实现

我们首先要解决的问题是:判断图片是否位于可视区域内

offsetTop:元素到 offsetParent1 顶部的距离

offsetHeight:元素的高度(包含内边距 padding 和边框 border)

scrollTop:元素内容垂直滚动的像素数

window.innerHeight:文档可视区域的高度

我们可知:

  1. document.documentElement.scrollTop + window.innerHeight > document.querySelector("img").offsetTop 的时候图片位于可视区域内或位于可视区域以上
  2. document.querySelector("img").offsetTop + document.querySelector("img").offsetHeight > document.documentElement.scrollTop 的时候图片位于可视区域内或可视区域以下

当以上两个表达式同时为真时,图片一定位于可视区域内

在解决了图片是否位于可视区域内问题之后,我们就可以开始写 js 了

// 定义函数 lazyLoad,参数 selector 为选择器
function lazyLoad(selector) {
    // 获取可视区域高度
    var windowHeight = window.innerHeight;
    // 获取可视区域以上的高度
    var scrollTop = document.documentElement.scrollTop;

    // 获取所有需要懒加载的图片
    var images = document.querySelectorAll(selector);

    // 使用 forEach() 遍历所有需要懒加载的图片
    images.forEach((img) => {
        // 判断图片是否位于可视区域内
        if (scrollTop + windowHeight > img.offsetTop && img.offsetTop + img.offsetHeight > scrollTop) {
            // 图片在可视区域内则将 data-src 属性的值赋给 src 属性
            img.src = img.getAttribute("data-src");
        }
    });
}

完成了 lazyLoad 函数后,我们可以使用 document.addEventListener(event, function, useCapture) 方法监听滚动事件:

document.addEventListener("scroll", lazyLoad.call(this, img#lazyLoad));

为了能让浏览者进入页面后可以直接看到可视区域内的图片,需要使用 window.onload 在页面加载完成后执行一次 lazyLoad 函数:

window.onload = function() {
    lazyLoad("img");
    document.addEventListener("scroll", lazyLoad.bind(this, "img"));
}

修改 Typecho

我们主要需要修改的是 ~/var/Utils/HyperDown.phpparseInline 函数

找到下面两部分:

return $self - > makeHolder(
    "<img src=\"{$url}\" alt=\"{$escaped}\" title=\"{$escaped}\">"
);
$result = isset($self - > _definitions[$matches[2]]) ?
    "<img src=\"{$self->_definitions[$matches[2]]}\" alt=\"{$escaped}\" title=\"{$escaped}\">"
    : $escaped;

将 img 标签的 src 属性值修改成占位图并添加 data-src 属性:

return $self - > makeHolder(
    "<img data-src=\"{$url}\" src=\"https://xxx.com/loading.svg\" alt=\"{$escaped}\" title=\"{$escaped}\">"
);
$result = isset($self - > _definitions[$matches[2]]) ?
    "<img data-src=\"{$self->_definitions[$matches[2]]}\" src=\"https://xxx.com/loading.svg\" alt=\"{$escaped}\" title=\"{$escaped}\">"
    : $escaped;

之后在你的外观的 footer.php 中的合适位置添加 js:

// 定义函数 lazyLoad,参数 selector 为选择器
function lazyLoad(selector) {
    // 获取可视区域高度
    var windowHeight = window.innerHeight;
    // 获取可视区域以上的高度
    var scrollTop = document.documentElement.scrollTop;

    // 获取所有需要懒加载的图片
    var images = document.querySelectorAll(selector);

    // 使用 forEach() 遍历所有需要懒加载的图片
    images.forEach((img) => {
        // 判断图片是否位于可视区域内
        if (scrollTop + windowHeight > img.offsetTop && img.offsetTop + img.offsetHeight > scrollTop) {
            // 图片在可视区域内则将 data-src 属性的值赋给 src 属性
            img.src = img.getAttribute("data-src");
        }
    });
}

window.onload = function() {
    lazyLoad("img");
    document.addEventListener("scroll", lazyLoad.bind(this, "img"));
}

还要记得修改外观其他图片的 img 标签

最后,在后台评论设置中的“允许使用的HTML标签和属性”追加一条:<img class data-src src alt title>

如果只需要正文和评论的图片懒加载可以在刚刚修改的 ~/var/Utils/HyperDown.php 里为 img 标签加上 class,修改 js 选择器选择指定的图片


注意

如果你使用了 Smilies 插件(https://github.com/jzwalk/Smilies)请注意修改此插件 showsmilies 函数代码,插件在允许评论区显示表情图片的操作会为 commentsHTMLTagAllowed 追加一个 <img> 标签,你在评论设置中放行的 HTML 标签会被覆盖

/**
 * 解析表情图片
 * 
 * @access public
 * @param string $content 评论内容
 * @return string
 */
public static function showsmilies($content,$widget,$lastResult)
{
    $content = empty($lastResult) ? $content : $lastResult;

    $options = Helper::options();
    //允许图片标签
    $options->commentsHTMLTagAllowed .= '<img src="" alt="" style=""/>';
    $archive = $widget instanceof Widget_Archive;

    if ($widget instanceof Widget_Abstract_Comments || $archive && $options->plugin('Smilies')->postmode) {
        $arrays = self::parsesmilies($archive);
        $content = str_replace($arrays['2'],$arrays['3'],$content);
    }

    return $content;
}

脚注

  1. 最近的祖先元素