温馨提示:
本文所述内容具有依赖性,可能因软硬条件不同而与预期有所差异,故请以实际为准,仅供参考。
一、背景
在网站开发中,懒加载是一项非常有用的应用,可以大大节省服务器资源,也可以降低访客的连接可感时间 ---
惰性加载(英语:Lazy loading、Infinite Scroll,又称延迟加载懒加载、无限滚动、瀑布流),是一种设计模式,被运用在软件设计和网页设计当中,对于网页界面,其特征为用户透过鼠标,滚动浏览页面,直到元素即将显示到窗口时,才会自动加载更多内容;有多数网站采用这项网页设计,例如Google 图片搜索、Google+、Facebook、Twitter、Pinterest 和维基百科的 Flow 讨论系统。也有结合无限滚动和多页,两著特性的网页设计。
而对于数据结构而言,惰性加载是指从一个数据对象通过方法获得里面的一个属性对象时,这个对应对象实际并没有随其父数据对象创建时一起保存在运行空间中,而是在其读取方法第一次被调用时才从其他数据源中加载到运行空间中,这样可以避免过早地导入过大的数据对象但并没有使用的空间占用浪费。--- 维基百科
二、问题
在本站主题 ArmxMod for Typecho 中,集成了图片懒加载和瀑布流两种懒加载技术,来达到优化的目的。其中在图片懒加载运用中,遇到了一个问题,就是页面定位不准,表现为在含图文章点击右侧按钮会有偏移,具体原因就是页面还没滚动到图片位置,图片不会加载,也就是说对浏览器来说图片是不存在的,导致点击滚动时图片突然加载占位,造成位置偏移。因此,就要考虑在图片加载前预先占据一定空间,这样不论是否滚动到图片,都不会有位置的偏移。
经过研究, ArmxMod for Typecho 提供了两种占位选择,一种是后端生成尺寸大小一样的空白图片,并转码 base64 显示到前端;一种是前端利用 JavaScript 实时生成尺寸大小一样的空白图片。
三、方案
1、后端 PHP 占位
I、思路
服务器先行对图片进行分析(getimagesize
),获取到图片格式、尺寸大小信息,然后生成一张相同格式、尺寸的空白图片(gd
),最后将图片转为 base64 代码格式(base64_encode
)返回前端。
II、步骤
① 分析图片
$img_data= getimagesize($imgurl);
if($img_data){
echoImg($img_data[0],$img_data[1],$img_data['mime'],$path,$imgurl);
}
其中 $img_data[0]
是图片的长度,$img_data[1]
是图片的宽度,$img_data['mime']
是图片类型,这三个参数通过 PHP 自带的 getimagesize
函数即可获得。getimagesize
函数说明,可以参考《php getimagesize 函数 - 获取图像信息》。
这里还有一个隐藏的问题需要注意,就是如果图片是在远程服务器上的,那么可能会出现图片获取失败的情况,造成后续动作都无法执行,因此建议在分析图片时加以验证。
最简单的方法就是将图片下载到本地,分析完成后删除图片(也可不删),然后将生成的空白图片缓存,后续占位时先找缓存,找到就可以直接使用。
② 生成图片
function echoImg($width,$height,$extname,$p,$url){
......
ob_start();
$img = imagecreate($width,$height);
$background_color = imagecolorallocate($img,245,245,245);
imagefilledrectangle($img,0,0,$width -1,$height -1,$background_color);
if($extname == 'gif'){
imagegif($img);
} else if($extname == 'jpg' || $extname == 'jpeg'){
imagejpeg($img);
} else if($extname =='wbmp'){
imagewbmp($img);
} else {
imagepng($img);
}
imagedestroy($img);
$img_data = ob_get_contents();
ob_end_clean();
file_put_contents($realpath,$img_data,FILE_APPEND);
return $options->themeUrl.'/'.$basecache;
......
}
注意,上面的代码展示的是大致思路,并不是完整的代码,具体应用要具体分析。
可以看到,在这里使用了:
ob_start()
.......
ob_end_clean();
这是因为 PHP 生成图片时是直接输出图片流,所以要进行图片转码的话需要把图片流保持下来,否则会直接在前端显示。
③ 图片转码
base64_encode
需要读图片完整的进制流,所以如果不需要缓存,那么在生成图片时,可以直接转码:
......
$img_data = ob_get_contents();
$img_base64 = base64_encode($img_data);
ob_end_clean();
return 'data:'.$extname.';base64,'.$img_base64;
......
如果启用缓存了,则是读出图片流,然后转码:
......
$img_bin = file_get_contents($realpath);
$imginfo = getimagesize($realpath);
$img_base64 = base64_encode($img_bin);
return 'data:'.$imginfo['mime'].';base64,'.$img_base64;
......
前端引用
经过上面的处理后,后端返回给前端的是图片的 base64
格式,浏览器原生支持,所以可以直接指定 src
,如:
echo '<img src="'.$img_decode.'" />';
2、前端 JS 占位
I、思路
JavaScript 占位我们直接采用已经编译好的开源项目 holder.js,holder.js
使用也比较简单,页面引用 js 文件,指定图片大小,holder.js
就会在页面加载时帮我们自动生成 base64
格式的占位图。
II、步骤
① 分析图片
......
$img_data= getimagesize($imgurl);
if($img_data){
replaceImg($img_data[0],$img_data[1]);
}
......
不论采用 PHP 后端还是 JavaScript 前端,都需要先获得图片信息才能生成准确的占位信息,使用 PHP 自带函数会比 JavaScript 更简单。
② 替换图片
function replaceImg($width,$height){
......
return 'data-src="'.$imgurl.'" data-original="holder.js/'.$width.'x'.$height.'"';
......
可以看到,使用 holder.js
是非常简单的,这里需要注意的是如果懒加载使用的是 lazyload.js
,那么 data-src
是不能够被用来占位(会出现 404),所以我们使用了 data-original
,这也是 holder.js
为兼容懒加载而新增的字段。
前端页面在加载时,holder.js
会主动去识别形如 holder.js/100x100
的图片源(data-original
),进而进行 base64
替换,需要注意的是,图片源必须是 holder.js/
开头,不要画蛇添足改成 holder.js
的实际目录,如 https://vircloud.net/usr/themes/armx/js/holder.js/100x100
,这会导致无法成功替换。
③ 前端引用
<script type="text/javascript" data-no-instant="true" src="<?php echo $jsurl;?>js/holder.js"></script>
前端还需要引用 holder.js
方能成功占位。
四、总结
对于体验来说,图片占位并不是可有可无的。如果从前端、后端都要最省资源,那么直接指定固定大小的空白图片就可以缓解定位问题。
而要实现准确定位则可参考上述两个方案,当然了,方法并不局限于这两个,甚至于还可以将两者相结合来达到优化的目的,具体看需求和服务器配置如何了。
关于直接引用占位图片还是采用 base64
代码,前者会使前端请求数量增加,而后者会增加页面大小,我是建议采用 base64
代码,多个请求往往会比体积大一点点的单个请求所消耗时间要多。