由于在 Web 中图片是在 DOM 结构渲染完成以后调用的,如果网速较慢,在 DOM 结构渲染完成时图片尚未加载,此时图片的高度会默认为 0;当图片加载完成后,会重新计算进行重绘,所以会出现抖动现象(图片下面的内容被挤下去)。Google 也针对这种页面布局抖动提出了累积布局偏移(Cumulative Layout Shift)这一指标用来测量真实用户体验中发生偏移的频率。

虽然对内容没什么影响,但对用户体验来说还是比较差的,特别是网络比较差的情况下。

以前用的解决办法

为了解决这个问题,以前一直都在用一个笨办法,直接在图片外加一个 wrapper,利用 CSS 的特性,使用 padding-bottom 来占位。举一个例子:

<div class="img-wrapper">
  <img src="/atpx.com/img.png">
</div>
.img-wrapper {
  position: relative;
  width: 100%;
  height: 0;
  padding-bottom: 56.25%; 
}

img {
  position: absolute;
  top: 0;
  left: 0;
  max-width: 100%;
}

简单来说就是提前给图片一个父元素 <div>,设置 padding-bottom,计算图片的 宽/高*100%,若图片为 16:9,则 padding-bottom 的值为 56.25%. 由于 CSS 是在 DOM 结构渲染时就已经排好版了,所以后续加载的图片只需要在父元素里伸展躺平就行了。

这样写的好处就是简单、兼容性高,缺点也显而易见,如果图片的大小不一样,每张图片都要单独设置。我还真就每张图片这样设置 🙃,因为 Typecho 的 Markdown 中支持引入 HTML 语句,所以过去两年我的每篇文章就像下面这样:

[内容]

!!!
<div class="img-wrapper" style="padding-bottom:23.33%">
!!!
![example](/images/atpx.com.png)
!!!
</div>
!!!

[内容]

这肯定不是一个长久之计,如果我以后要将博客转移到其他平台将会是个大麻烦。

曲线解决办法

这两年断断续续的尝试过几次解决办法:

  • 自定义代码块,PHP 替换
  • 服务器后端返回图片宽高
  • JS 获取图片宽高,生成图片
  • 图片宽高信息写入文件名,通过正则匹配获取

迫于太麻烦/太菜等原因,最后都没用多久或者放弃。这两天又在思考,能不能从哪里找个突破口,简单的方式实现。最后把目光放到了 Markdown 上,Markdown 中引入一张图片的写法如下:

![alt text](/images/atpx.com.png "title text")

但一直以来 title 属性我都没用上,那么是否可以利用下,于是经过一天的探索终于优雅的解决了图片占位的问题。

首先是面临一个小问题,Typecho 本身用的 SegmentFault 自己编写的 Hyperdown 解析器,不知道是有 bug 还是我以前哪里魔改过,加上 title 内容后解析错误,输出 html 内容为:

<img src="/images/atpx.com.png" alt=" title=" title="" text""="">

通过插件换成 Parsedown 解析器后正常解析:

<img src="/images/atpx.com.png" alt="alt text" title="title text">

接下来的操作思路就很清晰了,首先在主题 function.php 文件中添加一个函数用来加上 img-wrapper 并替换 padding-bottom 的值为 title 的值:

/**
 * Stop html page shakes
 * written by ATP
 * website: www.atpx.com
 */
function getContent($content) {
    $pattern = '/\<img.*?src\=\"(.*?)\".*?alt\=\"(.*?)\".*?title\=\"(.*?)\"[^>]*>/i';
    $replacement = '<div class="img-wrapper" style="padding-bottom:$3%"><img src="$1" alt="$2"></div>';
    $content = preg_replace($pattern, $replacement, $content);
    return $content;
}

甚至可以顺便加入懒加载的属性,目前这个属性浏览器的支持也很不错了:

<img src="$1" alt="$2" loading="lazy"> 

然后在输出文章内容的 post.php 文件中找到 <?php $this->content(); ?>,替换为:

<?php echo getContent($this->content); ?>

之后只需要在写文章插入图片时在 title 里写上图片的宽高百分比即可,如:

![alt text](/images/atpx.com.png "56.25")

最后输出的 html 如下:

<div class="img-wrapper" style="padding-bottom:56.25%">
    <img src="/images/atpx.com.png" alt="alt text">
</div>

虽然并不是自动化的解决问题,但目前的方式我还是比较满意了,也能在提升一点写作效率的同时保证 Markdown 文章的多平台一致性。