微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

减少HTTP请求之合并图片详解(大型网站优化技术)

2015-12-13 20:10 前端大全

(点击上方,可快速关注)


作者:Kelly

网址:http://www.cnblogs.com/it-cen/p/4618954.html


一、相关知识讲解


看过雅虎的前端优化35条建议,都知道优化前端是有多么重要。页面的加载速度直接影响到用户的体验。80%的终端用户响应时间都花在了前端上,其中大部分时间都在下载页面上的各种组件:图片,样式表,脚本,Flash等等。


减少组件数必然能够减少页面提交的HTTP请求数。这是让页面更快的关键。减少页面组件数的一种方式是简化页面设计。但有没有一种方法可以在构建复杂的页面同时加快响应时间呢?嗯,确实有鱼和熊掌兼得的办法。


这里我们就拿雅虎的第一条建议:尽量减少HTTP请求数里的减少图片请求数量 进行讲解。


我们都知道,一个网站的一个页面可能有很多小图标,例如一些按钮、箭头等等。当加载html文档时,只要遇到有图片的,都会自动建立起HTTP请求下载,然后将图片下载到页面上,这些小图片可能也就是十几K大甚至1K都不到,假如我们的一个页面有一百个小图标,我们在加载页面时,就要发送100个HTTP请求,如果你的网站访问量很大并发量也很高,假如上百万访问量,那发起的请求就是千万级别了,服务器是有一定的压力的,并且一个用户的一个页面要发起那么多请求,是很耗时的。


所以,我们优化的方案就是:将这些十几K、几K的小图标合并在一张图片里,然后用CSS的background-image和background-position属性来定位要显示的部分。


二、代码实现


1、思路:


将一个文件夹里的图标,自动生成在一张图片里面,同时自动生成对应的css文件,我们只要在HTML里的标签中添加相应的属性值就能显示图片了。


2、实现过程:


<?php

//自己定义一个根目录

define('ROOT', $_SERVER['DOCUMENT_ROOT'].'iconwww');

//这个是图片的目录

define('RES_BASE_URL', 'http://localhost:8080/iconwww/img');

/**

* 生成背景图的函数

*/

function generateIcon() {

//网站根目录

$webRoot = rtrim(ROOT, '/');

//背景图目录

$root = "$webRoot/img/bg";

//Php-SPL库中 的 目录文件遍历器

$iterator = new DirectoryIterator($root);

//开始遍历该背景图目录下的目录,我们是把想生成背景图的目录,放在bg目录中以各个模块的目录分类存放

foreach ($iterator as $file) {

//遇到目录遍历

if (!$file->isDot() && $file->isDir()) {

//取得文件名

$fileName = $file->getFilename();

generateIconCallback("$root/$fileName", "$webRoot/img/$fileName", "$webRoot/css/$fileName.css");

}

}

}

/**

* 用户生成合并的背景图和css文件的函数

* @param string $dir 生成背景图的图标所在的目录路径

* @param string $bgSavePath 背景图所保存的路径

* @param string $cssSavePath css保存的路径

*/

function generateIconCallback($dir, $bgSavePath, $cssSavePath) {

$shortDir = str_replace('\\', '/', substr($dir, strlen(ROOT-1)));

//返回文件路径信息

$pathInfo = pathinfo($bgSavePath.'.png');

$bgSaveDir = $pathInfo['dirname'];

//确保目录可写

ensure_writable_dir($bgSaveDir);

//背景图名字

$bgName = $pathInfo['filename'];

//调用generateIconCallback_GetFileMap()函数生成每一个图标所需要的数据结构

$fileMap = array('a' => generateIconCallback_GetFileMap($dir));

$iterator = new DirectoryIterator($dir);

foreach ($iterator as $file) {

if ($file->isDot()) continue;

if ($file->isDir()) {

//二级目录也要处理

$fileMap['b-'.$file->getFilename()] = generateIconCallback_GetFileMap($file->getRealPath());

}

}

ksort($fileMap);

//分析一边fileMap,计算整个背景图的大小和每一个图标的offset

//初始化偏移量和背景图

$offsetX = $offsetY = $bgWidth = 0;

//设定每个小图标之间的距离

$spaceX =$spaceY = 5;

//图片最大宽度

$maxWidth = 800;

$fileMd5List =array();

//这里需要打印下$fileMap就知道它的数据结构了

foreach ($fileMap as $k1 => $innerMap) {

foreach ($innerMap as $k2 => $itemList) {

//行高姐X轴偏移量初始化

$offsetX = $lineHeight = 0;

foreach ($itemList as $k3 => $item) {

//变量分别是:图标的宽度,高度,类型,文件名,路径,MD5加密字符串

list($imageWidth, $imageHeight, $imageType, $fileName, $filePathname, $fileMd5) = $item;

$fileMd5List []= $fileMd5;

//如果图片的宽度+偏移量 > 最大宽度(800) 那就换行

if ($offsetX !== 0 && $imageWidth + $offsetX > $maxWidth) {

$offsetY += $spaceY + $lineHeight;

$offsetX = $lineHeight = 0;

}

//如果图片高度 > 当前行高 那就讲图片高度付给行高我们这的

if ($imageHeight > $lineHeight) $lineHeight = $imageHeight;

$fileMap[$k1][$k2][$k3] = array($imageWidth, $imageHeight, $offsetX, $offsetY, $imageType, $fileName, $filePathname);

//X轴偏移量的计算

$offsetX += $imageWidth + $spaceX;

if ($offsetX > $bgWidth) $bgWidth = $offsetX;

}

//Y轴偏移量的计算

$offsetY += $lineHeight + $spaceY;

}

}

//把右下两边多加了的空白距离给干掉

$bgWidth -= $spaceX;

$bgHeight = $offsetY - $spaceY;

$fileMd5List = implode("\n", $fileMd5List);

//生成背景图和 css文件

//资源路径

$resBaseUrl = RES_BASE_URL;

$suffix = base_convert(abs(crc32($fileMd5List)), 10, 36);

$writeHandle = fopen($cssSavePath, 'w');

fwrite($writeHandle, "/** bg in dir: $shortDir/ */\n[icon-$bgName]{background:url({$resBaseUrl}/$bgName.png?$suffix) no-repeat;display:inline-block;}");

//做图片,这些函数具体可以查看PHP手册

$destResource = imagecreatetruecolor($bgWidth, $bgHeight);

imagealphablending($destResource, false);

imagesavealpha($destResource, false);

$color = imagecolorallocatealpha($destResource, 255, 255, 255, 127);

imagefill($destResource, 0, 0, $color);

//对每一张小图片进行处理,生成在大背景图里,并生成css文件

foreach ($fileMap as $innerMap) {

foreach ($innerMap as $itemList) {

foreach ($itemList as $item) {

list($imageWidth, $imageHeight, $offsetX, $offsetY, $imageType, $fileName, $filePathname) = $item;

if ($imageType === IMAGETYPE_PNG) {

$srcResource = imagecreatefrompng($filePathname);

} else if ($imageType === IMAGETYPE_JPEG) {

$srcResource = imagecreatefromjpeg($filePathname);

}

imagecopy($destResource, $srcResource, $offsetX, $offsetY, 0, 0, $imageWidth, $imageHeight);

imagedestroy($srcResource);

//写入css

$posX = $offsetX === 0 ? 0 : "-{$offsetX}px";

$posY = $offsetY === 0 ? 0 : "-{$offsetY}px";

fwrite($writeHandle, "\n[icon-$bgName=\"$fileName\"]{width:{$imageWidth}px;height:{$imageHeight}px;background-position:$posX $posY;}");

}

}

}

//压缩级别 7

imagepng($destResource, "$bgSavePath.png", 7);

imagedestroy($destResource);

fclose($writeHandle);

$shortCssSavePath = substr($cssSavePath, strlen(ROOT));

}

/**

* 将图片的信息处理成我们想要的数据结构

* @param [type] $dir [description]

* @return [type] [description]

*/

function generateIconCallback_GetFileMap($dir) {

$map = $sort = array();

$iterator = new DirectoryIterator($dir);

foreach($iterator as $file) {

if(!$file->isFile()) continue;

$filePathname = str_replace("\\", '/', $file->getRealPath());

//这些函数可以查看PHP手册

$imageInfo = getimagesize($filePathname);

$imageWidth = $imageInfo[0];

$imageHeight = $imageInfo[1];

$imageType = $imageInfo[2];

if(!in_array($imageType, array(IMAGETYPE_JPEG, IMAGETYPE_PNG))) {

$fileShortName = substr($filePathname, strlen(ROOT) - 1);

echo "<p> $fileShortName 图片被忽略: 因为图片类型不是png|jpg.</p>";

continue;

}

//这是我们的图片规格,行高分别有 16 32 64 128 256 99999

foreach(array(16, 32, 64, 128, 256, 99999) as $height) {

if($imageHeight <= $height) {

$mapKey = $height;

break;

}

}

if(!isset($map[$mapKey])) $map[$mapKey] = array();

$filePathInfo = pathinfo($filePathname);

$map[$mapKey] []= array($imageWidth, $imageHeight, $imageType, $filePathInfo['filename'], $filePathname, md5_file($filePathname));

$sort[$mapKey] []= str_pad($imageHeight, 4, '0', STR_PAD_LEFT) . $filePathInfo['filename'];

}

foreach($map as $k => $v) array_multisort($map[$k], SORT_ASC, SORT_NUMERIC, $sort[$k]);

ksort($map, SORT_NUMERIC);

return $map;

}

/**

* 判断目录是否可写

* @param string $dir 目录路径

*/

function ensure_writable_dir($dir) {

if(!file_exists($dir)) {

mkdir($dir, 0766, true);

@chmod($dir, 0766);

@chmod($dir, 0777);

}

else if(!is_writable($dir)) {

@chmod($dir, 0766);

@chmod($dir, 0777);

if(!@is_writable($dir)) {

throw new BusinessLogicException("目录不可写", $dir);

}

}

}

generateIcon();

?>

<!DOCTYPE html>

<html>

<head>

<link rel="stylesheet" type="text/css" href="css/Pink.css">

<title></title>

</head>

<body>

<div>我们直接引入所生成的css文件,并测试一下是否成功</div>

<br>

<div>这里在span标签 添加属性 icon-Pink ,值为About-40,正常显示图片</div>

<span icon-Pink="About-40"></span>

</body>

</html>


调用以上代码,我们的浏览器是这样显示的:



然后css目录生成了Pink.css文件:



img目录下生成了Pink.png文件:



看看生成的背景图是长啥样子:



接下来我们再看一下所生成的图片大小与Pink文件夹里所有小图片总和的大小,对它们做个比较:



从上图可以看出,我们生成的图片的大小明显小于文件夹所有图片的大小,所以在将100个小图标下载下来的速度 会明显小于 将背景图下载下来和将CSS下载下来的速度。


当访问量大时,或者小图片的量大时,会起到很明显的优化效果!!!


代码中的每一个点都基本上有注释,很方便大家去理解,只要大家用心去看,肯定能将这一网站优化技术用到自己的项目中。


本次博文就写到这!!!


如果此博文中有哪里讲得让人难以理解,欢迎留言交流,若有讲解错的地方欢迎指出。


如果您觉得您能在此博文学到了新知识,请为我顶一个,如文章中有解释错的地方,欢迎指出。


互相学习,共同进步!



【今日微信公号推荐↓】

 
前端大全 更多文章 5个典型的JavaScript面试题(上) Limu:JavaScript的那些书 Web开发:我希望得到的编程学习路线图 JavaScript基础工具清单 常用排序算法之JavaScript实现
猜您喜欢 大飞哥带你感受华商文化 有料丨如何实施数字化云平台的集成与交付(PPT) 性能自动化充电、断电之痛​——小松鼠的救赎之路 为了 5 分钱把程序搞的面目全非,恭喜了! 从Gartner IT成熟度模型谈Linux运维